Realm使得在多个线程上处理数据变得毫不费力,而不必担心一致性或性能,因为 对象 objects 和 查询 queries 始终在自动更新。您可以对不同线程中的活动对象进行操作,对其进行读写操作,而无需担心其他线程对这些相同对象所做的事情。 如果需要更改数据,则可以使用事务。另一个线程中的其他对象将近乎实时地更新(更新将作为事件在Looper上进行调度,因此Looper线程将在事件处理后立即进行更新)。
唯一的限制是您不能在线程之间随机传递Realm对象。如果在另一个线程上需要相同的数据,则需要在另一个线程上查询该数据。此外,您可以使用Realms反应式体系结构观察更改。请记住,所有对象在线程之间都是最新的-数据更改时,Realm会通知您。
假设我们有一个显示客户列表的应用程序。在后台线程(Android IntentService)中,我们轮询新客户的远程端点,然后将其保存到Realm。 当后台线程添加新客户时,UI线程中的数据将自动更新。UI线程通过RealmChangeListener通知,此时我们告诉UI小部件进行自我更新。无需重新查询,因为Realm可以使所有内容保持最新。
// in a Fragment or Activity, etc
// 只要不对查询结果进行垃圾回收,就只会触发侦听器,因此请保留对其的强大类引用。
private RealmResults customers;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// ... 为简洁起见,省略了样板
realm = Realm.getDefaultInstance();
// get all the customers
customers = realm.where(Customer.class).findAllAsync();
// ... build a list adapter and set it to the ListView/RecyclerView/etc
// set up a Realm change listener
changeListener = new RealmChangeListener() {
@Override
public void onChange(RealmResults results) {
// 每当Realm数据库在任何线程上更改时,都会调用此方法。
// 请注意,更改侦听器仅在Looper线程上起作用。
// 对于非循环线程,您必须手动使用Realm.waitForChange()。
updateUi();
}
};
// 当客户结果发生变化(添加、删除、更新的项目等)时,告诉Realm通知我们的监听器。
customers.addChangeListener(changeListener);
}
// In a background service, in another thread
public class PollingService extends IntentService {
@Override
public void onHandleIntent(Intent intent) {
Realm realm = Realm.getDefaultInstance();
try {
// 去做一些网络电话/等,并获取一些数据并将其填充到“ json”变量中
String json = customerApi.getCustomers();
realm.beginTransaction();
realm.createObjectFromJson(Customer.class, json); // Save a bunch of new Customer objects
realm.commitTransaction();
// 此时,UI线程中的数据已经是最新的。
// ...
} finally {
realm.close();
}
}
// ...
}
后台服务将新Customer添加到realm后,Customer列表将在UI中自动更新,而无需您进行任何其他干预。单个对象也是如此。假设您只管理一个对象。只需在一个线程上进行更改,UI线程就会自动拥有新数据。如果您需要对这一更改做出回应,只需像上面一样添加一个侦听器即可。
跨线程使用Realm的唯一规则是记住Realm,RealmObject和RealmResults实例不能跨线程传递。而是使用异步查询或异步事务将操作转移到后台线程,并将所有结果返回给您。
当您想从其他线程访问相同数据时,可以获取一个新的Realm实例(即Realm.getInstance(RealmConfiguration config)或其重构方法),并通过查询获取对象。
这些对象将映射到磁盘上的相同数据,并且可以从任何线程读取和写入。
使用这些类时要小心:
AsyncTask
IntentService
AsyncTask
类包含执行后台线程的doInBackground()
方法。 IntentService
类包含在工作线程中执行的onHandleIntent(Intent intent)
方法。
如果您需要在这两种方法中使用Realm,则应打开Realm,执行工作,然后在退出之前关闭Realm。 以下是几个示例。
如下所示,在doInBackground方法中打开和关闭Realm。
private class DownloadOrders extends AsyncTask {
protected Long doInBackground(Void... voids) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
return orderId;
} finally {
realm.close();
}
}
protected void onPostExecute(Long orderId) {
// 回到主线程,使用orderId进行操作,例如查询Realm以获取订单并执行一些操作。
}
}
ChangeListeners
不能在IntentService
中工作。 即使是Looper
线程,每次onHandleIntent
的调用都是一个单独的事件,不会“循环”。这意味着可以注册更改侦听器,但是它们永远不会被触发。
如下所示,通过onHandleIntent方法打开和关闭Realm。
public class OrdersIntentService extends IntentService {
public OrdersIntentService(String name) {
super("OrdersIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
} finally {
realm.close();
}
}
}
可以从多个进程访问realm,但有一些限制。当从同一APK中的不同进程访问同一Realm时,包括通知在内的所有内容均应正常工作。
该功能是Realm 7.0版以来的新增功能,目前(翻译时间为2020.3.29)处于测试阶段。您正在查看Realm beta版本7.0.0-beta的文档。要查看最新的稳定版文档,请参阅Realm 6.0.2。
尽管在许多情况下实时更新线程受限的Realm Objects效果很好,但在某些情况下,例如, 当Realm是基于事件流的体系结构的一部分时,在该对象中,可以从多个线程读取对象,然后最终以处理后的形式将其发送到UI线程。在这些情况下,冻结Realm、RealmResults、RealmList或Realm对象可能是有益的。
冻结Realm会返回一个不变的版本,可以从任何线程读取和查询该版本。来自冻结Realm的所有派生对象也将冻结。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
new Thread(() -> {
RealmResults frozenPersons = frozenRealm.where(Person.class).findAll();
frozenPersons.isFrozen(); // true
Person frozenPerson = frozenPersons.first();
frozenPerson.isFrozen(); // true
}).start();
最常见的模式是将更改侦听器附加到实时查询结果,然后冻结结果,然后再传递给进一步处理:
Realm liveRealm = Realm.getDefaultInstance();
RealmResults liveResults = liveRealm.where(Person.class).findAllAsync();
liveResults.addChangeListener(result -> {
doExpensiveWorkOnOtherThread(result.freeze());
});
无法在冻结的Realm上开始写事务,修改冻结的对象或向其添加更改侦听器。所有这些方法都将引发IllegalStateException。要修改冻结的对象,请在实时Realm中对其进行查询,然后对其进行修改。
Realm liveRealm = Realm.getDefaultInstance();
Person frozenPerson = liveRealm.where(Person.class).findFirst().freeze();
frozenPerson.addChangeListener(listener); // Throws
new Thread(() -> {
Realm bgLiveRealm = Realm.getDefaultInstance();
bgLiveRealm.beginTransaction();
frozenPerson.setName("Jane"); // Throws
Person bgLivePerson = bgLiveRealm.where(Person.class).equalTo("id", frozenPerson.getId()).findFirst();
bgLivePerson.setName("Jane");
bgLiveRealm.commitTransaction();
bgLiveRealm.close();
}).start();
一旦冻结,就无法解冻对象。使用isFrozen()查询对象的状态。即使对于其他线程限制的Realm对象,此方法也是线程安全的。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
liveRealm.isFrozen(); // false;
frozenRealm.isFrozen(); // true
new Thread(() -> {
liveRealm.isFrozen(); // false;
frozenRealm.isFrozen(); // true
}).start();
只要产生冻结对象的活动Realm保持打开状态,冻结对象就可用。在使用冻结的对象完成所有线程之前,必须注意不要关闭实时Realm。在关闭实时Realm之前,可以关闭冻结的Realm。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
Person frozenPerson = frozenRealm.where(Person.class).findFirst();
frozenPerson.isValid(); // true
// 关闭实时Realm,也关闭冻结的数据
liveRealm.close();
frozenPerson.isValid(); // false
请注意,缓存过多的冻结对象可能会对文件大小产生负面影响。
有关更多信息,请参见此处。