Android 进阶之第三方库的介绍 Realm [二] 进阶用法
多线程情况下的Realm
我们可以实时地在不同线程中读取和写入Realm对象,不用担心其它线程会对同一对象进行操作。在使用事务改变对象时,在下一次事件循环中,另一线程中指向同一对象的数据会被即时更新。唯一局限是不能随意跨线程传递 Realm 对象。如果您在另一线程使用同一对象,请在哪个线程使用查询重新获得该对象。请谨记所有的 Realm 对象都会在不同线程中保持更新——Realm 会在数据改变时通知您。
使用JSON数据
对于JSON,我们可以直接将它添加到Realm中,这些 JSON 对象可以是一个 String、一个 JSONObject 或者是一个 InputStream。Realm 会忽略 JSON 中存在但未定义在 Realm 模型类里的字段。
单独对象可以通过 Realm.createObjectFromJson() 添加。对象列表可以通过 Realm.createAllFromJson() 添加。
// A RealmObject that represents a city |
通知
当后台线程向 Realm 添加数据,UI 线程或者其它线程可以添加一个监听器来获取数据改变的通知。监听器在 Realm 数据改变的时候会被触发。
public class MyActivity extends Activity { |
- 移除所有监听器。除了在 Realm 实例上添加监听器以外,您还可以在 RealmObject 和 RealmResults 实例上添加监听器。
realm.removeAllChangeListeners();
你可以通过下面的方式来监视对象和查询结果的改变。另外,当监听回调函数被调用时,相应的数据已经被更新,我们不需要去做刷新操作。最后,这些监听器同样会在监听对象的引用对象改变时被触发,请见示例:public class MyActivity extends Activity {
private Realm realm;
private RealmChangeListener puppiesListener;
private RealmChangeListener dogListener;
private RealmResults<Dog> puppies;
private Dog dog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
puppiesListener = new RealmChangeListener<RealmResults<Dog>>() {
@Override
public void onChange(RealmResults<Dog> puppies) {
// ... do something with the updated puppies instance
}};
// Find all the puppies
puppies = realm.where(Dog.class).lessThanOrEqualTo("age", 2).findAll();
puppies.addChangeListener(puppiesListener);
dogListener = new RealmChangeListener<Dog>() {
@Override
public void onChange(Dog dog) {
// ... do something with the updated Dog instance
}};
dog = realm.where(Dog.class).equals("name", "Fido").findFirst();
dog.addChangeListener(dogListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove the listeners
puppies.removeChangeListener(puppiesListener);
dog.removeChangeListener(dogListener);
// Close the realm instance.
realm.close();
}
}Person person = realm.where(Person.class).findFirst();
person.getDogs(); // => 2 - Assume there are 2 dogs in the list
person.addChangeListener(new RealmChangeListener<Person>() {
@Override
public void onChange(Person person) {
// React to the change in the Person instance.
// This will also get called when any referenced dogs are updated.
}
});
Dog dog = person.getDogs().get(0);
realm.beginTransaction();
dog.setAge(5);
realm.commitTransaction();
// Person change listener is called on the next iteration of the run loop because
// a referenced dog object changed.
总结如下:
- 可以对Realm添加监听
- 可以对RealmObject,RealmResults添加监听,在这种情况下对应的数据发生变化或者引用发生变化都会出发监听响应事件。
迁移(Migrations)
下面例子使得相应的 migration 代码在有迁移需要的时候被自动执行。
RealmConfiguration config = new RealmConfiguration.Builder(context) |
// Example migration adding a new class |
如果没有旧 Realm 数据文件存在,那么迁移并不需要,在这种情况下,Realm 会创建一个新的以 .realm 为后缀,基于新的对象模型的数据文件。在开发和调试过程中,假如您需要频繁改变数据模型,并且不介意损失旧数据,您可以直接删除 .realm 文件(这里包含所有的数据!)而不用关心迁移的问题。
RealmConfiguration config = new RealmConfiguration.Builder(context) |
加密
Realm 文件可以通过传递一个512位(64字节)的密钥参数给 Realm.getInstance().encryptionKey() 来加密存储在磁盘上。
byte[] key = new byte[64]; |
与 Android 相关
- 适配器(Adapter)
RealmBaseAdapter 可以与 ListView 配合使用。
RealmRecyclerViewAdapter 可以与 RecyclerView 配合使用。
要使用这些适配器需要添加如下的依赖:
dependencies { |
- Intents
因为Realm 中不可以直接通过 intent 传递 RealmObject,所以一般只传递 RealmObject 的标识符。
// Assuming we had a person class with a @PrimaryKey on the 'id' field ... |
在接受方(Activty、Service、IntentService、BroadcastReceiver 及其它)从 bundle 中解析出这个主键然后打开 Realm 查询得到这个 RealmObject。
// in onCreate(), onHandleIntent(), etc. |
Android Framework 多线程 API 相关
当使用下列 API 时需要格外注意:
- AsyncTask
- IntentService
AsyncTask 的 doInBackground() 方法会运行在一个后台线程。IntentService 的 onHandleIntent(Intent intent) 方法会运行在一个后台工作线程。
如果需要在这些方法中使用 Realm,在对 Realm 的调用结束后关闭 Realm 实例。
AsyncTask
在 doInBackground 方法中打开并关闭 Realm,如下所示:
private class DownloadOrders extends AsyncTask<Void, Void, Long> { |
IntentService
在 onHandleIntent() 方法中打开并关闭 Realm,如下所示:
public class OrdersIntentService extends IntentService { |
对其它库的支持
GSON
GSON 是 Google 开发的 JSON 处理库。GSON 与 Realm 可以无缝配合使用。// Using the User class
public class User extends RealmObject {
private String name;
private String email;
// getters and setters left out ...
}
Gson gson = new GsonBuilder().create();
String json = "{ name : 'John', email : 'john@corporation.com' }";
User user = gson.fromJson(json, User.class);Retrofit
Realm 可以与 Retrofit 1.x 和 2.x 无缝配合工作。但注意 Retrofit 不会自动将对象存入 Realm。
你需要通过调用 Realm.copyToRealm() 或 Realm.copyToRealmOrUpdate() 来将它们存入 Realm。
GitHubService service = restAdapter.create(GitHubService.class); |
- RxJava
Realm 包含了对 RxJava 的原生支持。如下类可以被暴露为一个 Observable:Realm, RealmResults, RealmObject, DynamicRealm and DynamicRealmObject。
// Combining Realm, Retrofit and RxJava (Using Retrolambda syntax for brevity) |
请注意异步查询不会阻塞当前线程,如上代码会立即返回一个 RealmResults 实例。如果您想确定该 RealmResults 已经加载完成请使用 filter operator 和 RealmResults
最佳实践
- 防止出现 ANR
一般来说 Realm 的读写是足够快的,甚至在 UI 线程中读写也不是问题。但是,写事务是互相阻塞的,所以为了避免 ANR 的出现,我们建议你在后台线程中执行写操作。
- 控制 Realm 实例的生命周期
RealmObjects 和 RealmResults 在访问其引用数据时都是懒加载的。因为这个原因,请不要关闭你的 Realm 实例如果你仍然需要访问其中的 Realm 对象或者查询结果。为了避免不必要的 Realm 数据连接的打开和关闭,
Realm 内部有一个基于引用计数的缓存。这表示在同一线程内调用 Realm.getDefaultInstance() 多次是基本没有开销的,并且底层资源会在所有实例都关闭的时候才被释放。
以 UI 线程举例,最简单安全的途径是,在你所有的 Activity 和 Fragment 初始化时取得 Realm 实例,并在它们销毁时关闭 Realm 实例。
// Setup Realm in your Application |
- 重用 RealmResults 和 RealmObjects
在 UI 线程和其它拥有 Looper 的线程中,RealmObject 和 RealmResults 都会在 Realm 数据改变时自动刷新。这意味着你不需要在 RealmChangeListener 中重新获取这些对象。它们已经被更新并且准备好被重绘在屏幕上了。public class MyActivity extends Activity {
private Realm realm;
private RealmResults<Person> allPersons;
private RealmChangeListener realmListener = new RealmChangeListener<Realm>() {
@Override
public void onChange(Realm realm) {
// Just redraw the views. `allPersons` already contain the
// latest data.
invalidateView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
realm.addRealmChangeListener(listener);
allPerson = realm.where(Person.class).findAll(); // Create the "live" query result
setupViews(); // Initial setup of views
invalidateView(); // Redraw views with data
}
// ...
}