Realm クライアントのオブジェクトは、 が 同期 したリモート変更などのデータ変更を反映するように自動的に更新され、基礎のデータが変更されるたびにサブスクライブできる通知イベントを発行する ライブ オブジェクト です。
最近のアプリは、データが変更されたときに、その変更がどこで行われたかにかかわらずReactを実行できるようになりました。 ユーザーが新しい項目をリストに追加するときに、UI を更新したり、通知を表示したり、メッセージをログに記録したりすることができます。 そのアイテムがアップデートされたら、その可視化状態を変更したり、ネットワークリクエストを起動したりする必要があるかもしれません。 最後に、誰かがアイテムを削除した場合は、UI からそれを削除する必要がある可能性があります。 Realmの通知システムを使用すると、変更の原因となった書込み (write) とはReactに、データ内の変更を監視し、対応することができます。
Realm は 3 種類の通知を発行します。
特定の Realm が書込みトランザクションをコミットするたびにRealm 通知が表示されます。
挿入、アップデート、削除など、コレクション内の任意の Realm オブジェクトが変更されるたびに行われるコレクション通知。
アップデートや削除など、特定の Realm オブジェクトが変更されるたびにオブジェクト通知。
自動更新
ルーパー に関連付けられたスレッドでアクセスされるRealmオブジェクトは、基礎となるデータの変更を反映するために定期的に自動的に更新されます。
Android UI スレッドには常に Looperインスタンスが含まれています。 他のスレッドで Realm オブジェクトを長期間にわたって保持する必要がある場合は、そのスレッドに対してLooperを構成する必要があります。
警告
リソース リークを避けるために、非ループ スレッドでは常に Realm インスタンスを閉じてください
ルーパーのないスレッド上の Realm は、バージョンを自動的に進めることはありません。これにより、メモリ内とディスク上の邦土のサイズが増大する可能性があります。非 loader スレッドで邦土インスタンスを使用するのは可能な限り避けましょう。非 ループ スレッドで邦土を開く場合は、使用が完了したらその邦土を閉じてください。
Realm 変更リスナーの登録
通知ハンドラーは Realm 全体で登録できます。 Realm は、Realm に関連する書込みトランザクションがコミットされるたびに、通知ハンドラを呼び出します。 ハンドラーは変更に関する情報を受け取りません。
これは、変更が行われたことを待ちたいが、何が変更されたかを具体的に把握する必要がない場合に便利です。 たとえば、概念実証アプリは多くの場合、この通知タイプを使用し、変更があった場合に UI 全体を更新します。 アプリがより高度になり、パフォーマンスが重視されるにつれて、アプリ開発者はより細かい通知に移行します。
例
たとえば、リアルタイムの連携アプリを記述しているとします。 アプリがコラボレーション アクティビティでバケット利用していることを認識するには、変更が行われたときに発動するインジケーターが必要です。 その場合、Realm 通知ハンドラーは、インジケーターを制御するコードを動作させるのに優れた方法になります。 次のコードは、 addchangeLister()を使用して変更の 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() } }
重要
自動更新
新しい変更が Realm に書き込まれると、 を含むすべてのスレッドは、 インスタンスとLooper RealmObjectインスタンスを自動的に更新します。RealmResultその結果、 RealmChangeListenerに応答するときにそれらのオブジェクトを再度取得する必要はありません。これらのオブジェクトはすでに更新され、画面に再構築される準備ができているためです。
コレクション変更リスナーの登録
Realm 内の特定のコレクションに通知ハンドラーを登録できます。 ハンドラーは、最後の通知以降の変更の説明を受け取ります。 具体的には、この説明は 3 つのインデックスのリストで構成されています。
削除されたオブジェクトのインデックス。
挿入されたオブジェクトのインデックス。
変更されたオブジェクトのインデックス。
メソッドまたはremoveChangeListener() removeAllChangeListeners()メソッドを呼び出して通知配信を停止します。次の場合にも通知は停止します。
リスナーが登録されているオブジェクトは、ガベージで収集されます。
Realm インスタンスが閉じます。
通知が必要な限り、リッスンしているオブジェクトへの強力な参照を保持します。
重要
順序は重要
コレクション通知ハンドラーでは、削除、挿入、変更の順に常に変更を適用します。 削除前に挿入を処理すると、予期しない動作が発生する可能性があります。
Realm はコレクションを取得した後に初期通知を発行します。 その後は、書込みトランザクション (write transaction) によってコレクション内のオブジェクトが追加、変更、または削除されるたびに、Realm はコレクション通知を非同期に送信します。
Realm 通知と違い、コレクション通知には変更に関する詳細情報が含まれます。 これにより、変更に対する高度で選択的な対応が可能になります。 コレクション通知は、UI でコレクションを表すリストやその他のビューを管理するために必要なすべての情報を提供します。
次のコードは、 addCheckLister()を使用してコレクションの変更を監視する方法を示しています。
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)
オブジェクト変更リスナーの登録
Realm 内の特定のオブジェクトに通知ハンドラーを登録できます。 Realm はハンドラーに次のように通知します。
オブジェクトが削除された場合。
オブジェクトのプロパティのいずれかが変更された場合。
ハンドラーは、変更されたフィールドとオブジェクトが削除されたかどうかに関する情報を受け取ります。
メソッドまたはremoveChangeListener() removeAllChangeListeners()メソッドを呼び出して通知配信を停止します。次の場合にも通知は停止します。
リスナーが登録されているオブジェクトは、ガベージで収集されます。
Realm インスタンスが閉じます。
通知が必要な限り、リッスンしているオブジェクトの強力な参照を保持します。
次のコードは、Realm にクラスの新しいインスタンスを作成し、 addCheckLister()を使用してそのインスタンスの変更を監視する方法を示しています。
// 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.removeCheckLister()に渡すことで、変更リスナーの登録を解除できます。 Realm.removeAllTimeLists() を使用して、Realm またはそのリンクされたオブジェクトやコレクションの変更に対して現在サブスクライブしているすべての変更リスナーの登録を解除できます。
カスタム RAM 上のシステムアプリでの 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 セキュリティ ルールを調整する必要があります。 これは、AOSP の一部として提供されるツール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 ore 以降での変更
Android ore 以降、Google は SELinux の構成方法を変更しました。デフォルトのセキュリティ ポリシーは、はるかにモジュール化されています。その詳細については、こちらをご覧ください。
通知制限の変更
4 レベルよりも深いネストされたドキュメントの変更では、変更通知はtriggerされません。
5 レベル以上の変更をリッスンする必要があるデータ構造がある場合は、次の回避策があります。
スキーマをリファクタリングしてネストを減らします。
ユーザーがデータを手動で更新できるようにするには、「push-to-refresh」のようなものを追加します。