Realm(Java)数据库使用文档(Threading)


Realm(Java)数据库使用文档(目录)

Realm使得在多个线程上处理数据变得毫不费力,而不必担心一致性或性能,因为 对象 objects查询 queries 始终在自动更新。您可以对不同线程中的活动对象进行操作,对其进行读写操作,而无需担心其他线程对这些相同对象所做的事情。 如果需要更改数据,则可以使用事务。另一个线程中的其他对象将近乎实时地更新(更新将作为事件在Looper上进行调度,因此Looper线程将在事件处理后立即进行更新)。

唯一的限制是您不能在线程之间随机传递Realm对象。如果在另一个线程上需要相同的数据,则需要在另一个线程上查询该数据。此外,您可以使用Realms反应式体系结构观察更改。请记住,所有对象在线程之间都是最新的-数据更改时,Realm会通知您。

13.1 线程示例

假设我们有一个显示客户列表的应用程序。在后台线程(Android IntentService)中,我们轮询新客户的远程端点,然后将其保存到Realm。 当后台线程添加新客户时,UI线程中的数据将自动更新。UI线程通过RealmChangeListener通知,此时我们告诉UI小部件进行自我更新。无需重新查询,因为Realm可以使所有内容保持最新

// in a Fragment or Activity, etc

// 只要不对查询结果进行垃圾回收,就只会触发侦听器,因此请保留对其的强大类引用。
private RealmResults<Customer> 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<Customer> 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线程就会自动拥有新数据。如果您需要对这一更改做出回应,只需像上面一样添加一个侦听器即可。

13.2 跨线程使用Realm

跨线程使用Realm的唯一规则是记住Realm,RealmObject和RealmResults实例不能跨线程传递。而是使用异步查询异步事务将操作转移到后台线程,并将所有结果返回给您。

当您想从其他线程访问相同数据时,可以获取一个新的Realm实例(即Realm.getInstance(RealmConfiguration config)或其重构方法),并通过查询获取对象。

这些对象将映射到磁盘上的相同数据,并且可以从任何线程读取和写入。

13.3 Android框架线程

使用这些类时要小心:

  • AsyncTask
  • IntentService

AsyncTask类包含执行后台线程的doInBackground()方法。 IntentService类包含在工作线程中执行的onHandleIntent(Intent intent)方法。

如果您需要在这两种方法中使用Realm,则应打开Realm,执行工作,然后在退出之前关闭Realm。 以下是几个示例。

13.3.1 异步任务

如下所示,在doInBackground方法中打开和关闭Realm。

private class DownloadOrders extends AsyncTask<Void, Void, Long> {
    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以获取订单并执行一些操作。
    }
}

13.3.2 IntentService

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();
        }
    }
}

13.4 多进程支持

可以从多个进程访问realm,但有一些限制。当从同一APK中的不同进程访问同一Realm时,包括通知在内的所有内容均应正常工作。

13.5 冻结对象

该功能是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<Person> frozenPersons = frozenRealm.where(Person.class).findAll();
  frozenPersons.isFrozen(); // true
  Person frozenPerson = frozenPersons.first();
  frozenPerson.isFrozen(); // true
}).start();

最常见的模式是将更改侦听器附加到实时查询结果,然后冻结结果,然后再传递给进一步处理:

Realm liveRealm = Realm.getDefaultInstance();
RealmResults<Person> 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

请注意,缓存过多的冻结对象可能会对文件大小产生负面影响。
有关更多信息,请参见此处

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章