변경 사항에 대응 - Java SDK
이 페이지의 내용
Realm 클라이언트의 객체는 동기화 된 원격 변경 사항을 포함한 데이터 변경 사항을 반영하도록 자동으로 업데이트 되고 기본 데이터가 변경될 때마다 구독 할 수 있는 알림 이벤트를 내보내는 라이브 객체 입니다.
모든 최신 앱은 변경 사항이 발생한 위치에 관계없이 데이터가 변경되면 React 수 있어야 합니다. 사용자가 목록에 새 항목을 추가할 때 UI를 업데이트하거나, 알림을 표시하거나, 메시지를 기록해야 할 수 있습니다. 누군가 해당 항목을 업데이트하면 해당 항목의 시각적 상태를 변경하거나 네트워크 요청을 실행해야 할 수 있습니다. 마지막으로, 누군가 항목을 삭제하면 UI에서 해당 항목을 제거해야 할 수 있습니다. Realm의 알림 시스템을 사용하면 변경을 일으킨 쓰기가 무엇이든 데이터의 변경 사항을 관찰하고 React 수 있습니다.
Realm은 세 가지 종류의 알림을 내보냅니다.
특정 Realm이 쓰기 트랜잭션(write transaction)을 커밋할 때마다 Realm 알림 이 전송됩니다.
삽입, 업데이트, 삭제를 포함하여 컬렉션 의 Realm 객체 가 변경될 때마다 컬렉션 알림 을 받습니다.
업데이트 및 삭제를 포함하여 특정 Realm 객체가 변경될 때마다 객체 알림 을 보냅니다.
자동 새로 고침
루퍼 와 연결된 스레드에서 액세스하는 Realm 객체 주기적으로 자동 업데이트 되어 기본 데이터의 변경 사항을 반영합니다.
Android UI 스레드에는 항상 Looper
인스턴스가 포함되어 있습니다. 다른 스레드에 Realm 객체를 장기간 보관해야 하는 경우 해당 스레드에 대해 Looper
를 구성해야 합니다.
경고
리소스 유출을 방지하기 위해 항상 이벤트 루프가 아닌 스레드에서 Realm 인스턴스를 닫습니다.
루퍼가 없는 스레드의 Realm 자동으로 버전을 올리지 않습니다. 이렇게 하면 메모리와 디스크에서 영역 의 크기가 증가할 수 있습니다. 가능하면 Looper가 아닌 스레드에서는 영역 인스턴스를 사용하지 않는 것이 좋습니다. Looper 스레드가 아닌 스레드에서 영역 을 여는 경우 사용 이 끝나면 영역 을 닫습니다.
Realm 변경 리스너 등록
전체 Realm에서 알림 핸들러를 등록할 수 있습니다. Realm은 해당 영역과 관련된 쓰기 트랜잭션(write transaction)이 커밋될 때마다 알림 핸들러를 호출합니다. 핸들러는 변경 사항에 대한 정보를 수신하지 않습니다.
이는 변경 사항이 있는지 알고 싶지만 변경된 내용을 구체적으로 알고 싶지 않을 때 유용합니다. 예를 들어, 개념 증명 앱은 종종 이 알림 유형을 사용하며 변경 사항이 발생하면 전체 UI를 새로 고칩니다. 앱이 더욱 정교해지고 성능에 민감해짐에 따라 앱 개발자는 더욱 세분화된 알림으로 전환합니다.
예시
실시간 협업 앱을 작성한다고 가정해 보겠습니다. 앱이 협업 활동으로 번잡하다는 느낌을 주기 위해 변경 사항이 있을 때 표시등이 켜지도록 할 수 있습니다. 이 경우 Realm 알림 핸들러는 지표를 제어하는 코드를 구동하는 좋은 방법이 될 수 있습니다. 다음 코드는 addChangeListener()를 사용하여 Realm에서 변경 사항을 관찰하는 방법을 보여줍니다.
public class MyActivity extends Activity { private Realm realm; private RealmChangeListener realmListener; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); realm = Realm.getDefaultInstance(); realmListener = new RealmChangeListener<Realm>() { public void onChange(Realm realm) { // ... do something with the updates (UI, etc.) ... } }; // Observe realm notifications. realm.addChangeListener(realmListener); } protected void onDestroy() { super.onDestroy(); // Remove the listener. realm.removeChangeListener(realmListener); // Close the Realm instance. realm.close(); } }
class MyActivity : Activity() { private lateinit var realm: Realm private lateinit var realmListener: RealmChangeListener<Realm> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) realm = Realm.getDefaultInstance() realmListener = RealmChangeListener { // ... do something with the updates (UI, etc.) ... } // Observe realm notifications. realm.addChangeListener(realmListener) } override fun onDestroy() { super.onDestroy() // Remove the listener. realm.removeChangeListener(realmListener) // Close the Realm instance. realm.close() } }
중요
자동 새로 고침
Looper
를 포함하는 모든 스레드는 영역에 새 변경 사항이 기록될 때 RealmObject
및 RealmResult
인스턴스를 자동으로 새로 고칩니다. 결과적으로 React에 객체를 다시 가져올 필요가 RealmChangeListener
없습니다. 해당 객체는 이미 업데이트되어 화면에 다시 그려질 준비가 되었기 때문입니다.
컬렉션 변경 리스너 등록
Realm 내의 특정 컬렉션에 알림 핸들러를 등록할 수 있습니다. 핸들러는 마지막 알림 이후의 변경 사항에 대한 설명을 수신합니다. 특히 이 설명은 세 가지 인덱스 목록으로 구성됩니다.
삭제된 객체의 인덱스입니다.
삽입된 객체의 인덱스입니다.
수정된 객체의 인덱스입니다.
removeChangeListener()
또는 removeAllChangeListeners()
메서드를 호출하여 알림 전달을 중지합니다. 다음과 같은 경우에도 알림이 중지됩니다.
리스너가 등록된 객체는 가비지 수집을 합니다.
Realm 인스턴스가 닫힙니다.
알림이 필요한 기간 동안 수신 대기 중인 객체에 대한 강력한 참고를 유지하세요.
중요
알림 순서의 중요성
컬렉션 알림 핸들러에서는 항상 삭제, 삽입, 수정의 순서로 변경 사항을 적용합니다. 삭제하기 전에 삽입을 처리하면 예기치 않은 동작이 발생할 수 있습니다.
Realm은 컬렉션을 검색한 후 초기 알림을 보냅니다. 그 이후에는 쓰기 트랜잭션(write transaction)이 collection에서 객체를 추가, 변경 또는 제거할 때마다 Realm이 collection 알림을 비동기적으로 전달합니다.
Realm 알림과 달리 컬렉션 알림에는 변경 사항에 대한 자세한 정보가 포함되어 있습니다. 이를 통해 변화에 정교하고 선택적으로 대응할 수 있습니다. 컬렉션 알림은 UI에서 컬렉션을 나타내는 목록 또는 기타 보기를 관리하는 데 필요한 모든 정보를 제공합니다.
다음 코드는 addChangeListener()를 사용하여 컬렉션 의 변경 사항을 관찰하는 방법을 보여줍니다.
RealmResults<Dog> dogs = realm.where(Dog.class).findAll(); // Set up the collection notification handler. OrderedRealmCollectionChangeListener<RealmResults<Dog>> changeListener = (collection, changeSet) -> { // For deletions, notify the UI in reverse order if removing elements the UI OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (int i = deletions.length - 1; i >= 0; i--) { OrderedCollectionChangeSet.Range range = deletions[i]; Log.v("EXAMPLE", range.length + " dogs deleted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("EXAMPLE", range.length + " dogs inserted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("EXAMPLE", range.length + " dogs modified at " + range.startIndex); } }; // Observe collection notifications. dogs.addChangeListener(changeListener);
val dogs = realm.where(Dog::class.java).findAll() // Set up the collection notification handler. val changeListener = OrderedRealmCollectionChangeListener { collection: RealmResults<Dog>?, changeSet: OrderedCollectionChangeSet -> // For deletions, notify the UI in reverse order if removing elements the UI val deletions = changeSet.deletionRanges for (i in deletions.indices.reversed()) { val range = deletions[i] Log.v("EXAMPLE", "${range.length} dogs deleted at ${range.startIndex}") } val insertions = changeSet.insertionRanges for (range in insertions) { Log.v("EXAMPLE", "${range.length} dogs inserted at ${range.startIndex}") } val modifications = changeSet.changeRanges for (range in modifications) { Log.v("EXAMPLE", "${range.length} dogs modified at ${range.startIndex}") } } // Observe collection notifications. dogs.addChangeListener(changeListener)
객체 변경 리스너 등록
영역 내의 특정 객체에 대한 알림 핸들러를 등록할 수 있습니다. 영역은 다음 사항을 핸들러에게 알립니다.
객체가 삭제된 경우
객체의 속성이 변경된 경우
핸들러는 변경된 필드와 객체가 삭제되었는지 여부에 대한 정보를 니다.
removeChangeListener()
또는 removeAllChangeListeners()
메서드를 호출하여 알림 전달을 중지합니다. 다음과 같은 경우에도 알림이 중지됩니다.
리스너가 등록된 객체는 가비지 수집을 합니다.
Realm 인스턴스가 닫힙니다.
알림이 필요한 기간 동안 수신 중인 객체에 대한 강력한 참고를 유지합니다.
다음 코드는 Realm에서 클래스의 새 인스턴스 를 만들고 영역 ()를 사용하여 해당 인스턴스 의 변경 사항을 관찰하는 방법을 보여줍니다.
// Create a dog in the realm. AtomicReference<Dog> dog = new AtomicReference<Dog>(); realm.executeTransaction(transactionRealm -> { dog.set(transactionRealm.createObject(Dog.class, new ObjectId())); dog.get().setName("Max"); }); // Set up the listener. RealmObjectChangeListener<Dog> listener = (changedDog, changeSet) -> { if (changeSet.isDeleted()) { Log.i("EXAMPLE", "The dog was deleted"); return; } for (String fieldName : changeSet.getChangedFields()) { Log.i("EXAMPLE", "Field '" + fieldName + "' changed."); } }; // Observe object notifications. dog.get().addChangeListener(listener); // Update the dog to see the effect. realm.executeTransaction(r -> { dog.get().setName("Wolfie"); // -> "Field 'name' was changed." });
// Create a dog in the realm. var dog = Dog() realm.executeTransaction { transactionRealm -> dog = transactionRealm.createObject(Dog::class.java, ObjectId()) dog.name = "Max" } // Set up the listener. val listener = RealmObjectChangeListener { changedDog: Dog?, changeSet: ObjectChangeSet? -> if (changeSet!!.isDeleted) { Log.i("EXAMPLE", "The dog was deleted") } else { for (fieldName in changeSet.changedFields) { Log.i( "EXAMPLE", "Field '$fieldName' changed." ) } } } // Observe object notifications. dog.addChangeListener(listener) // Update the dog to see the effect. realm.executeTransaction { r: Realm? -> dog.name = "Wolfie" // -> "Field 'name' was changed." }
변경 리스너 등록 취소
변경 리스너를 Realm.removeChangeListener() 에 전달하여 변경 리스너의 등록을 취소할 수 있습니다. Realm.removeAllChangeListeners()를 사용하여 Realm이나 연결된 객체 또는 컬렉션의 변경 사항을 현재 구독하고 있는 모든 변경 수신기의 등록을 취소할 수 있습니다.
사용자 지정 ROM의 시스템 앱에서 Realm 사용
Realm은 여러 프로세스에서 알림과 Realm 파일 액세스를 지원하기 위해 명명된 파이프를 사용합니다. 이는 일반 사용자 앱의 경우 기본적으로 허용되지만 시스템 앱의 경우 허용되지 않습니다.
Android 매니페스트에서 android:sharedUserId="android.uid.system"
를 설정하여 시스템 앱을 정의할 수 있습니다. 시스템 앱으로 작업할 때 Logcat에서 다음과 같은 보안 위반을 볼 수 있습니다.
05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:99): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:100): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0
이 문제를 해결하려면 ROM에서 SELinux 보안 규칙을 조정해야 합니다. 이 작업은 IOSP의 일부로 제공되는 audit2allow
도구를 사용하여 수행할 수 있습니다.
기기에서 현재 정책을 가져옵니다.
adb pull /sys/fs/selinux/policy input.txt라는 텍스트 파일에 SELinux 오류를 복사합니다.
audit2allow
도구를 실행합니다.audit2allow -p policy -i input.txt 이 도구는 Realm을 사용할 수 있도록 기존 정책에 추가할 수 있는 규칙을 출력해야 합니다.
이러한 정책의 예는 아래와 같습니다.
# Allow system_app to create named pipes required by Realm # Credit: https://github.com/mikalackis/platform_vendor_ariel/blob/master_oreo/sepolicy/system_app.te allow system_app fuse:fifo_file create; allow system_app system_app_data_file:fifo_file create; allow system_app system_app_data_file:fifo_file { read write }; allow system_app system_app_data_file:fifo_file open;
참고
Android Oreo 이상의 변경 사항
Android Oreo 이후 Google은 SELinux를 구성하는 방식을 변경했습니다. 이제 기본 보안 정책이 훨씬 더 모듈화되었습니다. 자세한 내용은 여기를 참조하세요.
알림 한도 변경
중첩된 문서의 변경 내용이 4단계 아래로 내려가면 변경 알림이 트리거되지 않습니다.
5단계 아래 또는 그 이상의 변경 사항을 수신해야 하는 데이터 구조가 있는 경우 해결 방법은 다음과 같습니다.
스키마를 리팩터링하여 중첩을 줄입니다.
사용자가 수동으로 데이터를 새로 고칠 수 있도록 '푸시하여 새로 고침'과 같은 내용을 추가합니다.