多线程情况下的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
public class City extends RealmObject {
private String city;
private int id;
// getters and setters left out ...
}

// Insert from a string
realm.beginTransaction();
realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
realm.commitTransaction();

// Insert multiple items using a InputStream
InputStream is = new FileInputStream(new File("path_to_file"));
realm.beginTransaction();
try {
realm.createAllFromJson(City.class, is);
realm.commitTransaction();
} catch (IOException e) {
realm.cancelTransaction();
}

通知

当后台线程向 Realm 添加数据,UI 线程或者其它线程可以添加一个监听器来获取数据改变的通知。监听器在 Realm 数据改变的时候会被触发。

public class MyActivity extends Activity {
private Realm realm;
private RealmChangeListener realmListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
reamlListener = new RealmChangeListener<Realm>() {
@Override
public void onChange(Realm realm) {
// ... do something with the updates (UI, etc.) ...
}};
realm.addChangeListener(realmListener);
}

@Override
protected void onDestroy() {
super.onDestroy();
// Remove the listener.
realm.removeChangeListener(realmListener);
// Close the realm instance.
realm.close();
}
}

  • 移除所有监听器。
    realm.removeAllChangeListeners();
    除了在 Realm 实例上添加监听器以外,您还可以在 RealmObject 和 RealmResults 实例上添加监听器。
    你可以通过下面的方式来监视对象和查询结果的改变。另外,当监听回调函数被调用时,相应的数据已经被更新,我们不需要去做刷新操作。
    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)
.schemaVersion(2) // Must be bumped when the schema changes
.migration(new MyMigration()) // Migration to run instead of throwing an exception
.build()
// Example migration adding a new class
RealmMigration migration = new RealmMigration() {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

// DynamicRealm exposes an editable schema
RealmSchema schema = realm.getSchema();

// Migrate to version 1: Add a new class
// Example:
// public Person extends RealmObject {
// private String name;
// private int age;
// // getters and setters left out for brevity
// }
if (oldVersion == 0) {
schema.create("Person")
.addField("name", String.class)
.addField("age", int.class);
oldVersion++;
}

// Migrate to version 2: Add a primary key + object references
// Example:
// public Person extends RealmObject {
// private String name;
// @PrimaryKey
// private int age;
// private Dog favoriteDog;
// private RealmList<Dog> dogs;
// // getters and setters left out for brevity
// }
if (oldVersion == 1) {
schema.get("Person")
.addField("id", long.class, FieldAttribute.PRIMARY_KEY)
.addRealmObjectField("favoriteDog", schema.get("Dog"))
.addRealmListField("dogs", schema.get("Dog"));
oldVersion++;
}
}
}

如果没有旧 Realm 数据文件存在,那么迁移并不需要,在这种情况下,Realm 会创建一个新的以 .realm 为后缀,基于新的对象模型的数据文件。在开发和调试过程中,假如您需要频繁改变数据模型,并且不介意损失旧数据,您可以直接删除 .realm 文件(这里包含所有的数据!)而不用关心迁移的问题。

RealmConfiguration config = new RealmConfiguration.Builder(context)
.deleteRealmIfMigrationNeeded()
.build()

加密

Realm 文件可以通过传递一个512位(64字节)的密钥参数给 Realm.getInstance().encryptionKey() 来加密存储在磁盘上。

byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder(context)
.encryptionKey(key)
.build();

Realm realm = Realm.getInstance(config);

与 Android 相关

  • 适配器(Adapter)

RealmBaseAdapter 可以与 ListView 配合使用。
RealmRecyclerViewAdapter 可以与 RecyclerView 配合使用。

要使用这些适配器需要添加如下的依赖:

dependencies {
compile 'io.realm:android-adapters:1.2.1'
}
  • Intents

因为Realm 中不可以直接通过 intent 传递 RealmObject,所以一般只传递 RealmObject 的标识符。

// Assuming we had a person class with a @PrimaryKey on the 'id' field ...
Intent intent = new Intent(getActivity(), ReceivingService.class);
intent.putExtra("person_id", person.getId());
getActivity().startService(intent);

在接受方(Activty、Service、IntentService、BroadcastReceiver 及其它)从 bundle 中解析出这个主键然后打开 Realm 查询得到这个 RealmObject。

// in onCreate(), onHandleIntent(), etc.
String personId = intent.getStringExtra("person_id");
Realm realm = Realm.getDefaultInstance();
Person person = realm.where(Person.class).equalTo("id", personId).findFirst();
// do something with the person ...
realm.close();

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> {
protected Long doInBackground(Void... voids) {
// Now in a background thread.

// Open the Realm
Realm realm = Realm.getDefaultInstance();
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
realm.close();
return orderId;
}

protected void onPostExecute(Long orderId) {
// Back on the Android mainThread
// do something with orderId such as query Realm
// for the order and perform some operation with it.
}
}

IntentService
在 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();
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
realm.close();
}
}

对其它库的支持

  • 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);
List<Repo> repos = service.listRepos("octocat");

// Copy elements from Retrofit to Realm to persist them.
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();
  • RxJava

Realm 包含了对 RxJava 的原生支持。如下类可以被暴露为一个 Observable:Realm, RealmResults, RealmObject, DynamicRealm and DynamicRealmObject。

// Combining Realm, Retrofit and RxJava (Using Retrolambda syntax for brevity)
// Load all persons and merge them with their latest stats from GitHub (if they have any)
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asObservable()
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));

请注意异步查询不会阻塞当前线程,如上代码会立即返回一个 RealmResults 实例。如果您想确定该 RealmResults 已经加载完成请使用 filter operator 和 RealmResults.isLoaded() 方法。通过判断 RealmResults 是否已经加载可以得知查询是否已经完成。

最佳实践

  • 防止出现 ANR

一般来说 Realm 的读写是足够快的,甚至在 UI 线程中读写也不是问题。但是,写事务是互相阻塞的,所以为了避免 ANR 的出现,我们建议你在后台线程中执行写操作。

  • 控制 Realm 实例的生命周期

RealmObjects 和 RealmResults 在访问其引用数据时都是懒加载的。因为这个原因,请不要关闭你的 Realm 实例如果你仍然需要访问其中的 Realm 对象或者查询结果。为了避免不必要的 Realm 数据连接的打开和关闭,
Realm 内部有一个基于引用计数的缓存。这表示在同一线程内调用 Realm.getDefaultInstance() 多次是基本没有开销的,并且底层资源会在所有实例都关闭的时候才被释放。
以 UI 线程举例,最简单安全的途径是,在你所有的 Activity 和 Fragment 初始化时取得 Realm 实例,并在它们销毁时关闭 Realm 实例。

// Setup Realm in your Application
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}

// onCreate()/onDestroy() overlap when switching between activities so onCreate()
// on Activity 2 will be called before onDestroy() on Activity 1.
public class MyActivity extends Activity {
private Realm realm;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
}

@Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}

// Use onStart()/onStop() for Fragments as onDestroy() might not be called.
public class MyFragment extends Fragment {
private Realm realm;

@Override
public void onStart() {
super.onStart();
realm = Realm.getDefaultInstance();
}

@Override
public void onStop() {
super.onStop();
realm.close();
}
}
  • 重用 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
    }

    // ...
    }
Contents
  1. 1. 多线程情况下的Realm
  2. 2. 使用JSON数据
  3. 3. 通知
  4. 4. 迁移(Migrations)
  5. 5. 加密
  6. 6. 与 Android 相关
  7. 7. Android Framework 多线程 API 相关
  8. 8. 对其它库的支持
  • 最佳实践