Realm-Android

来源:互联网 发布:维氏刀具在淘宝上买 编辑:程序博客网 时间:2024/06/08 13:40



Realm Java 让你能够高效地编写 app 的模型层代码,保证你的数据被安全、快速地存储。参考下列示例来开始你的 Realm 之旅:
// Define you model class by extending RealmObjectpublic class Dog extends RealmObject {    private String name;    private int age;    // ... Generated getters and setters ...}public class Person extends RealmObject {    @PrimaryKey    private long id;    private String name;    private RealmList<Dog> dogs; // Declare one-to-many relationships    public Person(long id, String name) {        this.id = id;        this.name = name;    }    // ... Generated getters and setters ...}// Use them like regular java objectsDog dog = new Dog();dog.setName("Rex");dog.setAge(1);// Initialize RealmRealm.init(context);// Get a Realm instance for this threadRealm realm = Realm.getDefaultInstance();// Query Realm for all dogs younger than 2 years oldfinal RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();puppies.size(); // => 0 because no dogs have been added to the Realm yet// Persist your data in a transactionrealm.beginTransaction();final managedDog = realm.copyToRealm(dog); // Persist unmanaged objectsPerson person = realm.createObject(Person); // Create managed objects directlyperson.getDogs().add(managedDog);realm.commitTransaction();// Listeners will be notified when data changespuppies.addChangeListener(new RealmChangeListener<RealmResults<Dog>>() {    @Override    public void onChange(RealmResults<Dog> results) {        // Query results are updated in real time        puppies.size(); // => 1    }});// Asynchronously update objects on a background threadrealm.executeTransactionAsync(new Realm.Transaction() {    @Override    public void execute(Realm bgRealm) {        Dog dog = bgRealm.where(Dog.class).equals("age", 1).findFirst();        dog.setAge(3);    }}, new Realm.Transaction.OnSuccess() {    @Override    public void onSuccess() {    // Original queries and Realm objects are automatically updated.    puppies.size(); // => 0 because there are no more puppies younger than 2 years old    managedDog.getAge();   // => 3 the dogs age is updated    }});

快速入门

下载 Realm Android
或者在 GitHub 的 realm-java 页面查看源代码.

前提

  • 目前我们还不支持 Android 以外的 Java 环境;
  • Android Studio >= 1.5.1 ;
  • 较新的 Android SDK 版本;
  • JDK 版本 >=7;
  • 我们支持 Android API 9 以上的所有版本(Android 2.3 Gingerbread 及以上)。

安装

Realm 作为一个 Gradle 插件来安装需要如下两个步骤:

第一步: 在项目的 build.gradle 文件中添加如下 class path 依赖。

buildscript {    repositories {        jcenter()    }    dependencies {        classpath "io.realm:realm-gradle-plugin:2.1.1"    }}

项目的 build.gradle 文件在如下位置:
Project level build.gradle file

第二步: 在 app 的 build.gradle 文件中应用 realm-android 插件。

apply plugin: 'realm-android'

app的 build.gradle 文件在如下位置:
Application level build.gradle file

请参考以下示例工程对两个 build.gradle 文件的修改:

  • Project level build.gradle
  • Application level build.gradle

其它构建系统

构建工具 Maven 和 Ant 已不再被支持。如果你仍然有充足的理由需要得到这些构建工具的支持,请在如下两个链接留言说明你的理由以便我们决定是否应该添加对它们的支持。

  • Maven 支持
  • Ant 支持

v0.88 开始,我们放弃了对 Eclipse 的支持。我们强烈建议你切换到Android Studio。如果你想继续使用 Eclipse,可以考虑使用 Realm 的v0.87.5版本。不过请注意,我们不会再对该版本进行更新。

ProGuard

Realm 已经集成了 ProGuard 的配置。你不需要针对 Realm 做 ProGuard 的改动。

Realm浏览器

我们提供了一个独立的应用程序 Realm Browser来读写.realm 数据库。

你可以通过 Tools > Generate demo database 生成一个测试数据库。

查阅 StackOverflow 上的这个答案 获得有关你的应用程序 Realm 文件路径的详细说明。

Realm Browser 已经上架Map App Store。你也可以从我们 github 页面下载。

Realm Browser 目前不能运行在 Windows 或者 Linux 环境中。但你可以使用Stetho-Realm。Stetho 是 Facebook 提供的桥接安卓调试和 Chrome 浏览器的组件。

API手册

你可以查阅 API 手册获得所有关于类、方法及其它细节的信息。

示例

请查看我们的示例来获得有关 Realm 的常用功能。有关编译和运行示例的细节可以参考这里。

introExample 包含了如何使用当前的API的简单例子。

gridViewExample 用来展示如何使用 Realm 作为 GridView 的后端存储。它同时也展示了如何用 JSON 来填充数据库。另外还有怎么通过 ABI splits 来缩小 APK 体积。

threadExample 展示了如何在多线程环境中使用 Realm。

The adapterExample 展示了如何便捷地使用 RealmBaseAdapterRealmRecyclerViewAdapter 绑定RealmResults 到安卓的ListView 和 RecyclerView。

jsonExample 展示了 Realm 与 JSON 相关的功能。

encryptionExample 向你展示如何使用加密的 Realm。

rxJavaExamples 展示了如何与 RxJava 结合使用 Realm。

unitTestExample 展示了如何写与 Realm 相关的单元测试。

获得帮助

  • 你编程时需要帮助?:在 StackOverflow 上提问。我们在实时监控并且回答 SO 上的相关问题!
  • 要报告一个 bug?:在我们的 GitHub 仓库里新建一个问题,并且尽可能地按问题模板将 bug 描述清楚。
  • 需要一个新功能?:在我们的 GitHub 仓库里新建一个问题,并且尽可能地按问题模板将该功能描述清楚。
  • 想知道接下来将会发生什么?:看看我们的 changelog。这里有我们即将发布的改动以及修改历史。

模型

Realm 数据模型定义需要继承自 RealmObject 类。

public class User extends RealmObject {    private String          name;    private int             age;    @Ignore    private int             sessionId;    // Standard getters & setters generated by your IDE…    public String getName() { return name; }    public void   setName(String name) { this.name = name; }    public int    getAge() { return age; }    public void   setAge(int age) { this.age = age; }    public int    getSessionId() { return sessionId; }    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }}

Realm 数据模型不仅仅支持 private 成员变量,你还可以使用 publicprotected 以及自定义的成员方法。

public class User extends RealmObject {    public String name;    public boolean hasLongName() {      return name.length() > 7;    }    @Override    public boolean equals(Object o) {      // Custom equals comparison    }}

字段类型

Realm 支持以下字段类型:booleanbyteshortintlongfloatdoubleStringDatebyte []。整数类型shortintlong 都被映射到 Realm 内的相同类型(实际上为 long )。再者,还可以使用 RealmObject 的子类和RealmList<? extends RealmObject> 来表示模型关系。

Realm 对象中还可以声明包装类型(boxed type)属性,包括:BooleanByteShortIntegerLongFloatDouble。通过使用包装类型,可以使这些属性存取空值(null)。

@Required修饰类型和空值(null)

某些时候,空值(null)对于属性并不合适。这时可以使用注解 @Required 告诉 Realm 强制禁止空值(null)被存储。只有BooleanByteShortIntegerLongFloatDoubleStringbyte[] 以及 Date 可以被 @Required 修饰。在其它类型属性上使用 @Required 修饰会导致编译失败。基本数据类型(primitive types)不需要使用注解@Required,因为他们本身就不可为空。RealmObject 属性永远可以为空。

忽略的属性

注解 @Ignore 意味着一个字段不应该被保存到 Realm。某些时候输入的信息包含比模型更多的字段,而你不希望处理这些未使用的数据字段,你可以用@Ignore 来标识这些你希望被 Realm 忽略的字段。

Auto-Updating Objects

RealmObject 是实时的、自动更新的底层数据的映射视图。你不需要去重新获得对象已取得其最新版本。对于数据的改动会即时反应到相关的对象或者查询结果。

final Dog myDog;realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        myDog = realm.createObject(Dog.class);        myDog.setName("Fido");        myDog.setAge(1);    }});realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        Dog myPuppy = realm.where(Dog.class).equalTo("age", 1).findFirst();        myPuppy.setAge(2);    }});myDog.getAge(); // => 2

RealmObjectRealmResults 的设计不仅仅是为了更快和更有效率,它们同时也让代码更加简洁以及反应性更强。举例来说,假设你的 Activity 或者 Fragment 依赖于某个RealmObject 或者RealmResults,你无需担心何时去刷新或者重新获取它们以更新 UI——它们会自动更新。

你可以通过订阅 Realm notifications 来得知 Realm 的数据在何时被更新从而刷新你的 UI。

索引(Index)属性

注解 @Index 会为字段增加搜索索引。这会导致插入速度变慢,同时数据文件体积有所增加,但能加速查询。因此建议仅在需要加速查询时才添加索引。目前仅支持索引的属性类型包括:StringbyteshortintlongbooleanDate

主键 (primary keys)

@PrimaryKey 可以用来定义字段为主键,该字段类型必须为字符串(String)或整数(shortintlong)以及它们的包装类型(ShortIntLong)。不可以存在多个主键。使用支持索引的属性类型作为主键同时意味着为该字段建立索引。

当创建 Realm 对象时,所有字段会被设置为默认值。为了避免与具有相同主键的另一个对象冲突,建议创建一个 unmanaged 对象,为字段的赋值,然后用 copyToRealm() 方法将该对象复制到 Realm。

主键的存在意味着可以使用 copyToRealmOrUpdate() 方法,它会用此主键尝试寻找一个已存在的对象,如果对象存在,就更新该对象;反之,它会创建一个新的对象。当copyToRealmOrUpdate() 的调用对象没有主键时,会抛出异常。

使用主键会对性能产生影响。创建和更新对象将会慢一点,而查询则会变快。很难量化这些性能的差异,因为性能的改变跟你数据库的大小息息相关。

Realm.createObject()会返回一个所有字段被设置为默认值的新对象。如果该模型类存在主键,那么有可能返回对象的主键的默认值与其它已存在的对象冲突。建议创建一个非托管(unmanaged)Realm 对象,并给其主键赋值,然后调用copyToRealm() 来避免冲突。

final MyObject obj = new MyObject();obj.setId(42);obj.setName("Fish");realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        // This will create a new object in Realm or throw an exception if the        // object already exists (same primary key)        // realm.copyToRealm(obj);        // This will update an existing object with the same primary key        // or create a new object if an object with no primary key = 42        realm.copyToRealmOrUpdate(obj);    }});

字符串(String)和包装类型(ShortIntLong)的主键可以被赋予空值(null);除非它们同时被@Required 修饰。

定制对象(Customizing Objects)

你几乎可以把 RealmObject 当作 POJO 使用。只需扩展 RealmObject,将相应属性声明为 public。不需要 setter 和 getter, 而是直接访问属性。

public class Dog extends RealmObject {    public String name;    public int age;}

你可以像使用其他类一样直接使用 Dog 类。createObject()copyToRealm() 可以帮助你创建一个托管给 Realm 的Dog 对象。

realm.executeTransaction(new Realm.Transaction() {    @Overrride    public void execute(Realm realm) {        Dog dog = realm.createObject(Dog.class);        dog.name = "Fido";        dog.age  = 5;    }};

如果需要的话,你可以在 setter 和 getter 中添加你自己的逻辑。例如你可能需要在保存之前验证值的合法性。另外,你还可以为你的 RealmObject 添加自定义方法。

限制

目前不支持 finaltransientvolatile 修饰的成员变量,这主要是为了 Realm 对象在非托管(unmanaged RealmObject)时候的行为差异化。

Realm 数据模型不可以继承自除了 RealmObject 以外的其它对象。你可以选择不声明默认无参数构造器,但是如果你声明了,那么该构造器必须为空构造器。这是目前 Realm 的一个限制。但你可以自由添加任意其它的构造器。

RealmModel 接口

除直接继承于 RealmObject 来声明 Realm 数据模型之外,还可以通过实现 RealmModel 接口并添加 @RealmClass 修饰符来声明。

@RealmClasspublic class User implements RealmModel {}

RealmObject 的所有方法都有其相对应的静态方法。

// With RealmObjectuser.isValid();user.addChangeListener(listener);// With RealmModelRealmObject.isValid(user);RealmObject.addChangeListener(user, listener);

关系

任意两个 RealmObject 可以相互关联。

public class Email extends RealmObject {    private String address;    private boolean active;    // ... setters and getters left out}public class Contact extends RealmObject {    private String name;    private Email email;    // ... setters and getters left out}

RealmObject 之间的关联总体来说并不怎么消耗系统开销。Realm 对关系对象的处理非常高效并且节约内存。

多对一

你只需要简单地声明一个 Realm 模型类的属性即可:

public class Contact extends RealmObject {    private Email email;    // Other fields…}

每个 Contact 对象都有 0 或 1 个 Email 对象。在 Realm 中,你可以任意在多个 Contact 对象中使用同一个 Email 对象。同理,这个例子也解释了怎样实现一对一关系。

设置一个类型为 RealmObject 的属性为空值(null)会清除该属性的引用,但并不会删除对应的 RealmObject

多对多

你可以通过使用 RealmList<T> 为一个对象关联0或多个其它对象。设想一个通讯录成员拥有多个 email:

public class Contact extends RealmObject {    public String name;    public RealmList<Email> emails;}public class Email extends RealmObject {    public String address;    public boolean active;}

RealmList 是 Realm 模型对象的容器,其行为与 Java 的普通 List 近乎一样。同一个 Realm 模型对象可以存在于多个RealmList 中。同一个 Realm 模型对象可以在同一个RealmList 中存在多次。你可以使用 RealmList 来表现一对多和多对多的数据关系。

你可以创建对象,然后使用 RealmList.add() 来添加 Email 对象到 Contact

realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        Contact contact = realm.createObject(Contact.class);        contact.name = "John Doe";        Email email1 = realm.createObject(Email.class);        email1.address = "john@example.com";        email1.active = true;        contact.emails.add(email1);        Email email2 = realm.createObject(Email.class);        email2.address = "jd@example.com";        email2.active = false;        contact.emails.add(email2);    }});

有时递归关系很有用,这在 Realm 是允许的。

public class Person extends RealmObject {    public String name;    public RealmList<Person> friends;    // Other fields…}

当然,在使用递归关系的时候,你要注意死循环的问题。Realm 并不会检查 RealmList 的循环嵌套。

设置一个类型为 RealmList 的属性为空值(null)会清空该列表,即列表长度变为 0。但并不会删除列表中的任何 RealmObjectRealmList 的获取器(getter)永不会返回 null。其返回对象永远是一个 RealmList 实例,但其长度有可能为0。

Realm支持关联查询。以如下模型举例:

public class Person extends RealmObject {  private String id;  private String name;  private RealmList<Dog> dogs;  // getters and setters}public class Dog extends RealmObject {  private String id;  private String name;  private String color;  // getters and setters}

每个 Person 对象都与多个 Dog 对象相关联,如下图所示:

Table Diagram

让我们通过关联查询来得到一些 Person 对象 ——

// persons => [U1,U2]RealmResults<Person> persons = realm.where(Person.class)                                .equalTo("dogs.color", "Brown")                                .findAll();

首先,请注意equalsTofield名称包含关联的路径,以“.”分隔。

以上的查询含义为“所有至少含有一个 color 为 Brown 的 Person”。请务必注意,这里的返回的 Person 中,有可能包含 color 不为 Brown 的 Dog 对象,因为在其RealmList 列表中,其它的 Dog 对象满足查询条件:

persons.get(0).getDogs(); // => [A,B]persons.get(1).getDogs(); // => [B,C,D]

我们来看看另外两个查询:

// r1 => [U1,U2]RealmResults<Person> r1 = realm.where(Person.class)                             .equalTo("dogs.name", "Fluffy")                             .findAll();// r2 => [U1,U2]RealmResults<Person> r2 = r1.where()                          .equalTo("dogs.color", "Brown")                          .findAll();

请注意第一个查询返回两个 Person 对象,因为它们都满足查询条件。每个 Person 对象都包含一个Dog 对象列表——列表中至少有一个Dog 对象满足查询条件。谨记我们是在寻找其拥有的 Dog 对象满足条件(namecolor)的Person,不是在针对 Dog 对象进行查询。因此第二个查询建立在第一个的Person 结果(r1)以及r1 的每个 PersonDog 列表之上。两个 Person 仍然满足第二个查询,但这次是color 满足查询条件。

我们再深入了解下这个概念,请看以下代码:

// r1 => [u1,u2]realmresults<user> r1 = realm.where(user.class)                             .equalto("dogs.name", "fluffy")                             .equalto("dogs.color", "brown")                             .findall();// r2 => [u2]realmresults<user> r2 = realm.where(user.class)                             .equalto("dogs.name", "fluffy")                             .findall()                             .where()                             .equalto("dogs.color", "brown")                             .findall();                             .where()                             .equalto("dogs.color", "yellow")                             .findall();

第一个查询表示找到所有的 Person 他至少有一个 Dog 的名字为 fluffy 并且找到所有Person 他至少有一个Dog 的颜色是 brown 然后返回这两个结果的交集。第二个查询表示找到所有的Person 他至少有一个Dog 的名字为 fluffy;然后在这个结果之上找到所有的Person 他至少有一个Dog 的颜色为 brown;最后在之前的结果之上找到所有的Person 他至少有一个Dog 的颜色为 yellow

我们来解释一下第一个查询以深入了解下这个行为。两个条件分别是equalto("dogs.name", "fluffy")equalto("dogs.color", "brown")u1u2 完全满足第一个条件 —— 我们称其c1 集合。u1u2 也同时完全满足第二个条件 —— 我们称其 c2 集合。查询中的逻辑与即是c1c2 的交集。c1c2 的交集就是 u1u2。因此,r1 就包含 u1u2

第二个查询不一样。我们来分别讲解。该查询第一部分看起来是这样的:realmresults<user> r2a = realm.where(user.class).equalto("dogs.name", "fluffy").findall()。它的结果包含u1u2。然后 r2b = r2a.where().equalto("dogs.color", "brown").findall(); 的结果仍然包含u1u2 (两个 Person 都有颜色为 brownDog)。最后的查询r2 = r2b.where().equalto("dogs.color", "yellow").findall(); 结果只包含u2,因为只有u2 同时有一个颜色为 brownDog 和一个颜色为yellowDog

写入

在任何时间都可以对对象进行访问和查询(读取事务是隐式的)。
所有的写操作(添加、修改和删除对象),必须包含在写入事务(transaction)中。写入事务可以提交或取消。在提交期间,所有更改都将被写入磁盘,并且,只有当所有更改可以被持久化时,提交才会成功。通过取消一个写入事务,所有更改将被丢弃。使用写入事务,会确保你的数据的一致性。

写入事务也用于确保线程安全:

// Obtain a Realm instanceRealm realm = Realm.getDefaultInstance();realm.beginTransaction();//... add or update objects here ...realm.commitTransaction();

当你在写入事务内处理 Realm 对象时,你可能会遇到想要放弃更改的情况。你可以简单地取消写入事务:

realm.beginTransaction();User user = realm.createObject(User.class);//  ...realm.cancelTransaction();

请注意,写入事务之间会互相阻塞,如果一个写入事务正在进行,那么其他的线程的写入事务就会阻塞它们所在的线程。同时在 U I线程和后台线程使用写入事务有可能导致 ANR 问题。可以使用异步事务(async transactions)以避免阻塞 UI 线程。

Realm 数据文件不会因为程序崩溃而损坏。当有异常在事务块中被抛出时,当前事务中所做出的数据修改会被丢弃。如果在该情况下程序需要继续运行,那么请调用 cancelTransaction() 来中止事务,或者使用 executeTransaction() 来执行事务。

由但得益于 Realm 的 MVCC 架构,当正在进行一个写入事务时读取操作并不会被阻塞!这意味着,除非你需要从多个线程进行并发写入操作,否则,你可以尽量使用更大的写入事务来做更多的事情而不是使用多个更小的写入事务。当写入事务被提交到 Realm 时,该 Realm 的所有其他实例都将被通知,读入隐式事务将自动刷新你每个 Realm 对象。

在 Realm 中的读写访问是符合 ACID 的.

创建对象

由于 Realm 对象都强依赖于 Realm,它们应该直接通过 Realm 被实例化:

realm.beginTransaction();User user = realm.createObject(User.class); // Create a new objectuser.setName("John");user.setEmail("john@corporation.com");realm.commitTransaction();

或者你可以先创建一个对象的实例,并在之后使用 realm.copyToRealm() 添加。Realm 对象支持多个构造函数,只要其中之一是公共无参数构造函数即可。

User user = new User("John");user.setEmail("john@corporation.com");// Copy the object to Realm. Any further changes must happen on realmUserrealm.beginTransaction();User realmUser = realm.copyToRealm(user);realm.commitTransaction();

当使用 realm.copyToRealm() 时,请注意只有返回的对象是由 Realm 管理的,这非常重要。对原始对象(umanaged Object)的任何改变都不会写入 Realm.

事务执行块(Transaction blocks)

除手动调用 realm.beginTransaction()realm.commitTransaction()realm.cancelTransaction() 之外你可以使用realm.executeTransaction() 方法,它会自动处理写入事物的开始和提交,并在错误发生时取消写入事物。

realm.executeTransaction(new Realm.Transaction() {@Overridepublic void execute(Realm realm) {User user = realm.createObject(User.class);user.setName("John");user.setEmail("john@corporation.com");}});

异步事务(Asynchronous Transactions)

事务会相互阻塞其所在的线程,在后台线程中开启事务进行写入操作可以有效避免 UI 线程被阻塞。通过使用异步事务,Realm 会在后台线程中进行写入操作,并在事务完成时将结果传回调用线程。

realm.executeTransactionAsync(new Realm.Transaction() {    @Override    public void execute(Realm bgRealm) {        User user = bgRealm.createObject(User.class);        user.setName("John");        user.setEmail("john@corporation.com");    }}, new Realm.Transaction.OnSuccess() {    @Override    public void onSuccess() {        // Transaction was a success.    }}, new Realm.Transaction.OnError() {    @Override    public void onError(Throwable error) {        // Transaction failed and was automatically canceled.    }});

OnSuccessOnError 并不是必须重载的,重载了的回调函数会在事务成功或者失败时在被调用发生的线程执行。回调函数是通过Looper 被执行的,所以在非Looper 线程中只有空(null)回调函数被允许使用。

RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {            @Override            public void execute(Realm bgRealm) {                User user = bgRealm.createObject(User.class);                user.setName("John");                user.setEmail("john@corporation.com");            }        }, null);

异步事务调用会返回一个 RealmAsyncTask 对象。当你退出 Activity 或者 Fragment 时可以使用该对象取消异步事务。如果你在回调函数中更新 UI,那么忘记取消异步事务可能会造成你的应用崩溃。

public void onStop () {    if (transaction != null && !transaction.isCancelled()) {        transaction.cancel();    }}

更新字符串和 byte 数组

Realm 的写操作针对的是整个字符串或 byte 数组属性而非该属性中的单独元素。假设你需要修改某 byte 数组中的第五个字符,你需要:

realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        bytes[] bytes = realmObject.binary;        bytes[4] = 'a';        realm.binary = bytes;    }});

原因是 Realm 的 MVCC 架构需要在确定旧版本数据可以被舍弃之前仍然保留旧版本的数据。

查询

Realm 中的所有读取(包括查询)操作都是延迟执行的,且数据绝不会被拷贝。

Realm 的查询引擎使用 Fluent interface 来构造多条件查询。

使用 User 类 -

public class User extends RealmObject {    @PrimaryKey    private String          name;    private int             age;    @Ignore    private int             sessionId;    // Standard getters & setters generated by your IDE…    public String getName() { return name; }    public void   setName(String name) { this.name = name; }    public int    getAge() { return age; }    public void   setAge(int age) { this.age = age; }    public int    getSessionId() { return sessionId; }    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }}

比如查找所有叫做 John 或 Peter 的用户,你可以这么写:

// Build the query looking at all users:RealmQuery<User> query = realm.where(User.class);// Add query conditions:query.equalTo("name", "John");query.or().equalTo("name", "Peter");// Execute the query:RealmResults<User> result1 = query.findAll();// Or alternatively do the same all at once (the "Fluent interface"):RealmResults<User> result2 = realm.where(User.class)                                  .equalTo("name", "John")                                  .or()                                  .equalTo("name", "Peter")                                  .findAll();

查询返回一个 RealmResults 实例,其中包含名叫 John 和 Peter 的用户。这些对象并非拷贝,也就是说你得到的是一个匹配对象引用的列表,你对匹配对象所有的操作都是直接施加于它的原始对象。RealmResults 继承自 Java 的AbstractList,行为类似。例如你可以通过 index 来访问其中的某个对象。

当查询没有任何匹配时,返回的 RealmResults 对象将不会为 null,取而代之的是它的 size() 方法将返回 0。

修改或删除 RealmResults 中任何一个对象都必须在写入事务中完成。

查询条件

Realm 支持以下查询条件:

  • between()greaterThan()lessThan()greaterThanOrEqualTo()lessThanOrEqualTo()
  • equalTo()notEqualTo()
  • contains()beginsWith()endsWith()
  • isNull()isNotNull()
  • isEmpty()isNotEmpty()

并非所有条件都适用于所有数据类型,具体请参考 RealmQuery API。

修饰符

字符串查询条件可以通过使用 Case.INSENSITIVE 修饰符来忽略字母 A-Z 和 a-z 的大小写。

逻辑运算符

每个查询条件都会被被隐式地被逻辑和(&)组合在一起,而逻辑或(or)需要显式地去执行 or()

使用 User 类 -

public class User extends RealmObject {    @PrimaryKey    private String          name;    private int             age;    @Ignore    private int             sessionId;    // Standard getters & setters generated by your IDE…    public String getName() { return name; }    public void   setName(String name) { this.name = name; }    public int    getAge() { return age; }    public void   setAge(int age) { this.age = age; }    public int    getSessionId() { return sessionId; }    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }}

你也可以将查询条件组合在一起,使用 beginGroup()(相当于左括号)和 endGroup()(相当于右括号):

RealmResults<User> r = realm.where(User.class)                            .greaterThan("age", 10)  //implicit AND                            .beginGroup()                                .equalTo("name", "Peter")                                .or()                                .contains("name", "Jo")                            .endGroup()                            .findAll();

此外,也可以用 not() 否定一个条件。该 not() 运算符可以与 beginGroup()/endGroup() 一起使用来否定子条件。

排序

当你执行完查询获得结果后,可以对它进行排序:

RealmResults<User> result = realm.where(User.class).findAll();result = result.sort("age"); // Sort ascendingresult = result.sort("age", Sort.DESCENDING);

链式查询

因为查询结果并不会被复制,且在查询提交时并不会被执行,你可以链式串起查询并逐步进行分类筛选:

RealmResults<Person> teenagers = realm.where(Person.class).between("age", 13, 20).findAll();Person firstJohn = teenagers.where().equalTo("name", "John").findFirst();

你也可以在子对象上使用链式查询。假设以上 Person 对象包含一个 Dog 对象列表:

public class Dog extends RealmObject {    private int age;    // getters & setters ...}public class Person extends RealmObject {    private int age;    private RealmList<Dog> dogs;    // getters & setters ...}

你可以查询找出所有年龄在 13 和 20 之间的 Person 并且他至少拥有一个 1 岁的 Dog

RealmResults<Person> teensWithPups = realm.where(Person.class).between("age", 13, 20).equalTo("dogs.age", 1).findAll();

请注意,查询链最终是建立在 RealmResults 上而非 RealmQuery。如果你在某存在的 RealmQuery 上添加更多的查询条件,那么你在修改查询本身,而非查询链。请参考关联查询。

查询结果的自动更新(Auto-Updating Results)

RealmResults 是对其所包含数据的自动更新视图,这意味着它永远不需要被重新查询获取。数据对象的改变会在下一次 Looper 事件中被反映到相应的查询结果。

final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();puppies.size(); // => 0realm.executeTransaction(new Realm.Transaction() {    @Override    void public execute(Realm realm) {        Dog dog = realm.createObject(Dog.class);        dog.setName("Fido");        dog.setAge(1);    }});puppies.addChangeListener(new RealmChangeListener() {    @Override    public void onChange(RealmResults<Dog> results) {      // results and puppies point are both up to date      results.size(); // => 1      puppies.size(); // => 1    }});

这对所有的 RealmResults 有效 —— 无论是否有过滤条件、是否是链式查询。

RealmResults 的这个特性不仅使得 Realm 快速高效,而且让你的代码更简洁。举例来说,假设你的 Activity 或者 Fragment 依赖于某个查询结果,你可以将相应的 Realm 对象或者RealmResults 保存为一个属性,你不需要在每次访问时确定其是否被更新 —— Realm 会保证这些。

你可以通过订阅 Realm notifications 来得知 Realm 数据更新了,进而刷新 UI 而不必重新查询获得 RealmResults

因为查询结果的自动更新特性,请不要依赖于固定的索引(indices)、不变的条目数。

按类型检索对象

从 Realm 中检索对象的最基本方法是 realm.where(Foo.class).findAll(),它返回了包含被查询模型类的所有对象的 RealmResults

另外还有提供排序功能的 findAll()。参见 realm.where(Foo.class).findAllSorted() 了解详情。

聚合

RealmResult 自带一些聚合方法:

RealmResults<User> results = realm.where(User.class).findAll();long   sum     = results.sum("age").longValue();long   min     = results.min("age").longValue();long   max     = results.max("age").longValue();double average = results.average("age");long   matches = results.size();

迭代

可以这样遍历 RealmResults

RealmResults<User> results = realm.where(User.class).findAll();for (User u : results) {    // ... do something with the object ...}

或者使用 for 循环:

RealmResults<User> results = realm.where(User.class).findAll();for (int i = 0; i < results.size(); i++) {    User u = results.get(i);    // ... do something with the object ...}

RealmResults 的自动更新会通过 looper 事件触发,但在事件到来之前,某些元素有可能不再满足查询条件或者其已被删除。

final RealmResults<User> users = getUsers();realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        users.get(0).deleteFromRealm(); // indirectly delete object    }});for (User user : users) {    showUser(user); // Will crash for the deleted user}

为避免该问题,可以使用 RealmResultsdeleteFromRealm() 方法:

final RealmResults<User> users = getUsers();realm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        users.deleteFromRealm(0); // Delete and remove object directly    }});for (User user : users) {    showUser(user); // Deleted user will not be shown}

删除

你可以从查询结果中删除数据:

// obtain the results of a queryfinal RealmResults<Dog> results = realm.where(Dog.class).findAll();// All changes to data must happen in a transactionrealm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        // remove single match        results.deleteFirstFromRealm();        results.deleteLastFromRealm();        // remove a single object        Dog dog = results.get(5);        dog.deleteFromRealm();        // Delete all matches        results.deleteAllFromRealm();    }});

异步查询(Asynchronous Queries)

可以使用后台线程进行查询。

Realm 的大部分查询都非常快——快到可以使用在UI线程中而感觉不到延迟。但如果需要进行非常复杂的查询或者在大量数据中进行查询,那么使用后台线程进行查询将会是一个不错的主意。

示例:查找名字为 “John” 或者 “Peter” 的用户。

创建异步查询

RealmResults<User> result = realm.where(User.class)                              .equalTo("name", "John")                              .or()                              .equalTo("name", "Peter")                              .findAllAsync();

请注意,这里的调用并不会阻塞,而是立即返回一个 RealmResults<User>。这很类似于标准 Java 中 Future 的概念。查询将会在后台线程中被执行,当其完成时,之前返回的 RealmResults 实例会被更新。

如果你希望当查询完成、RealmResults 被更新时获得通知,你可以注册一个 RealmChangeListener。这个监听器会在RealmResults 被更新时被调用(通常是在事务被提交后)。

注册回调

private RealmChangeListener callback = new RealmChangeListener<RealmResults<User>>() {    @Override    public void onChange(RealmResults<User> results) {        // called once the query complete and on every update    }};public void onStart() {    RealmResults<User> result = realm.where(User.class).findAllAsync();    result.addChangeListener(callback);}

请在退出 Activity 或者 Fragment 时移除监听器的注册以避免内存泄漏。

public void onStop () {    result.removeChangeListener(callback); // remove a particular listener    // or    result.removeChangeListeners(); // remove all registered listeners}

检查查询是否完成

RealmResults<User> result = realm.where(User.class).findAllAsync();if (result.isLoaded()) {  // Results are now available}

同步查询返回的 RealmResults 实例的 isLoaded 方法会永远返回 true

强制装载异步查询

你可以选择性地等待异步查询完成,而这将会阻塞当前线程,使查询变成同步(与 Future.get() 类似的概念)。

RealmResults<User> result = realm.where(User.class).findAllAsync();result.load() // be careful, this will block the current thread until it returns

非 Looper 线程

你可以在 Looper 线程中使用异步查询。异步查询需要使用 Handler 来传递查询结果。在没有 Looper 的线程中使用异步查询会导致 IllegalStateException 异常被抛出。

Realms

Realm(s) 是我们对数据库的称谓:它包含多个不同的对象,并对应磁盘中的一个文件。在使用之前,需要对 Realm 库进行初始化操作:

Realm.init(context);

你需要提供一个安卓的 Context 对象来对 Realm 进行初始化。初始化操作只要进行一次。继承 Application 并在重载的onCreate() 方法中进行初始化是个不错的主意。

public class MyApplication extends Application {  @Override  public void onCreate() {    super.onCreate();    Realm.init(this);  }}

请别忘了修改 AndroidManifest.xml:

<application  android:name=".MyApplication"  .../>

默认的 Realm

你可能已经注意到,我们总是通过Realm.getInstance(this)来访问我们已初始化的realm变量。该静态方法会为你的当前线程返回一个Realm实例,它对应了你Context.getFilesDir()目录中的default.realm文件。

该文件位于你应用的可写根目录中。默认情况下的Realm使用内部存储(internal storage),你的应用并不需要取得任何读写权限。一般来说,这个文件位于/data/data/<packagename>/files/

你可以通过realm.getPath()来获得该Realm的绝对路径。

请务必注意到Realm的实例是线程单例化的,也就是说,在同一个线程内多次调用静态方法获得针对同路径的Realm,会返回同一个Realm实例。

配置 Realm

RealmConfiguration 用来配置要被创建的 Realm 的各种特性。最简配置如下所示:

RealmConfiguration config = new RealmConfiguration.Builder().build();

如上配置的 Realm 会被存储在 Context.getFilesDir() 并且命名为 default.realm

一个典型的配置如下所示:

// The RealmConfiguration is created using the builder pattern.// The Realm file will be located in Context.getFilesDir() with name "myrealm.realm"RealmConfiguration config = new RealmConfiguration.Builder()  .name("myrealm.realm")  .encryptionKey(getKey())  .schemaVersion(42)  .modules(new MySchemaModule())  .migration(new MyMigration())  .build();// Use the configRealm realm = Realm.getInstance(config);

你还可以有多个RealmConfiguration。如此,你便可以控制Realm的版本、结构(schema)和路径。

RealmConfiguration myConfig = new RealmConfiguration.Builder()  .name("myrealm.realm")  .schemaVersion(2)  .modules(new MyCustomSchema())  .build();RealmConfiguration otherConfig = new RealmConfiguration.Builder()  .name("otherrealm.realm")  .schemaVersion(5)  .modules(new MyOtherSchema())  .build();Realm myRealm = Realm.getInstance(myConfig);Realm otherRealm = Realm.getInstance(otherConfig);

你可以使用 Realm.getPath() 来获取 Realm 文件的绝对路径。

很重要的一点是 Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例。

默认 RealmConfiguration

RealmConfiguration可以保存为默认配置。通过在自定义的Application设置默认的Realm配置,可以使你在代码中的其他地方更加方便地创建针对该默认配置的Realm。

public class MyApplication extends Application {  @Override  public void onCreate() {    super.onCreate();    // The Realm file will be located in Context.getFilesDir() with name "default.realm"    RealmConfiguration config = new RealmConfiguration.Builder().build();    Realm.setDefaultConfiguration(config);  }}public class MyActivity extends Activity {  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    Realm realm = Realm.getDefaultInstance();    // ... Do something ...    realm.close();  }}

In-Memory Realm

定义一个非持久化的、存在于内存中的 Realm 实例:

RealmConfiguration myConfig = new RealmConfiguration.Builder()    .name("myrealm.realm")    .inMemory()    .build();

这样就可以创建一个存在于“内存中的” Realm。“内存中的”Realm 在内存紧张的情况下仍有可能使用到磁盘存储,但是这些磁盘空间都会在Realm实例完全关闭的时候被释放。

请注意使用同样的名称同时创建“内存中的”Realm 和常规的(持久化)Realm 是不允许的。

当某个“内存中的”Realm 的所有实例引用都被释放,该 Realm 下的数据也同时会被清除。建议在你的应用生命周期中保持对“内存中的” Realm 实例的引用以避免非期望的数据丢失。

Dynamic Realm

对于普通的 Realm 来说,数据模型被定义成了 RealmObject 的子类。这样做保证了类型安全,但有时候某些数据模型在编译期是无法获得的。例如在处理数据迁移(migration)或CSV文件的时候。

DynamicRealm 是普通 Realm 的一个变种。它可以在没有RealmObject 子类的情况下操作 Realm 数据。其对数据的访问是基于字符串而非RealmObject 的定义。

创建 Dynamic Realm 使用与创建普通 Realm 相同的RealmConfiguration,但是它的创建过程会忽略对 schema、migration以及 schema 版本的检查。

RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();DynamicRealm realm = DynamicRealm.getInstance(realmConfig);// In a DynamicRealm all objects are DynamicRealmObjectsDynamicRealmObject person = realm.createObject("Person");// All fields are accessed using stringsString name = person.getString("name");int age = person.getInt("age");// An underlying schema still exists, so accessing a field that does not exist// will throw an exceptionperson.getString("I don't exist");// Queries still work normallyRealmResults<DynamicRealmObject> persons = realm.where("Person")    .equalTo("name", "John")    .findAll();

DynamicRealm 以类型安全和性能为代价,换来了更多的灵活性。请在你确实需要这些灵活性的情况下使用。

关闭Realm实例

Realm 实现了 Closeable 接口以便与释放 native 内存和文件描述符,请务必在使用完毕后关闭 Realm 实例。

Realm 实例是基于引用计数的, 也就是说假设你在同一个线程中调用了 getInstance() 两次,你需要同样调用close() 两次以关闭该实例。举例来说,如果你需要实现Runnable,简单地在函数开始的时候调用 getInstance(),在函数结束的时候调用 close() 即可!

对于UI线程,你可以选择在 onDestroy() 方法内调用 realm.close()

如果你想使用除 UI 线程外的 Looper 线程,可以参考以下代码:

public class MyThread extends Thread {    private Realm realm;    public void run() {        Looper.prepare();        try {            realm = Realm.getDefaultInstance();            //... Setup the handlers using the Realm instance ...            Lopper.loop();        } finally {            if (realm != null) {                realm.close();            }        }    }}

对于 AsyncTask,这里有个不错的例子可以参考:

protected Void doInBackground(Void... params) {    Realm realm = Realm.getDefaultInstance();    try {        // ... Use the Realm instance ...    } finally {        realm.close();    }    return null;}

如果你想使用 ThreadRunnable 进行一些短期任务,参考如下代码:

// Run a non-Looper thread with a Realm instance.Thread thread = new Thread(new Runnable() {    @Override    public void run() {        Realm realm = null;        try {            realm = Realm.getDefaultInstance();            // ... Use the Realm instance ...        } finally {            if (realm != null) {                realm.close();            }        }    }});thread.start();

如果你很幸运地工作在 minSdkVersion >= 19Java >= 7 之下,可以使用try-with-resources

try (Realm realm = Realm.getDefaultInstance()) {    // No need to close the Realm instance manually}

自动更新(Auto-Refresh)

如果 Realm 实例存在于一个带有 Looper 的线程,那么这个 Realm 实例即具有自动更新的功能。这意味这如果发生了 Realm 数据库的变化,那么该 Realm 实例会在下一个事件循环(event loop)中自动更新。这个便捷的功能使你不必花费太多的精力就能保证的UI与数据的实时同步。

如果 Realm 的实例所在线程没有绑定 Looper,那么该实例不会被更新直到你手动调用 waitForChange() 方法。请注意,不更新 Realm 以保持对旧数据的引用会造成而外的磁盘和内存开销。这也是为什么要在线程结束时调用close() 关闭 Realm 实例的一个重要原因。

如果你想确定当前 Realm 实例是否有自动更新功能,可以通过调用 isAutoRefresh() 方法查询。

查找 Realm 数据库文件

如果你想知道你应用的 Realm 文件的具体路径,请参见这个 StackOverflow上的答案。

线程(Threading)

其实对于跨线程使用 Realm,你需要知道的事情并不多。关键点是得益于对象和查询的即时更新特性,你不需要担心数据在多线程时的一致性和效率问题。

你可以实时在不同线程中读取和写入 Realm 对象,不用担心其它线程会对同一对象进行操作。你需要在改变对象时使用事务,在另一线程中指向同一对象的数据会被即时更新(更新会在下一次事件循环时进行)。

唯一局限是你不能随意跨线程传递 Realm 对象。如果你在另一线程使用同一对象,请在哪个线程使用查询重新获得该对象。请谨记所有的 Realm 对象都会在不同线程中保持更新——Realm 会在数据改变时通知你。

参考如下实例。

Realm 线程实例

假设我们的应用要展示一个用户列表。我们在一个后台线程中(一个安卓 IntentService)从远端获取新用户并将它们存储到 Realm 中。但后台线程存储新用户时,UI 线程中的数据会被自动更新。UI 线程会通过RealmChangeListener 得到通知,这时 UI 线程应刷新相应的控件。因为 Realm 的自动更新特性,无需重新查询数据。

// in a Fragment or Activity, etc@Overridepublic void onActivityCreated(Bundle savedInstanceState) {    // ... boilerplate omitted for brevity    realm = Realm.getDefaultInstance();    // get all the customers    RealmResults<Customer> 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<RealmResults<Customer>>() {        @Override        public void onChange(RealmResults<Customer> results) {            // This is called anytime the Realm database changes on any thread.            // Please note, change listeners only work on Looper threads.            // For non-looper threads, you manually have to use Realm.waitForChange() instead.            listAdapter.notifyDataSetChanged(); // Update the UI        }    };    // Tell Realm to notify our listener when the customers results    // have changed (items added, removed, updated, anything of the sort).    customers.addChangeListener(changeListener);}// In a background service, in another threadpublic class PollingService extends IntentService {    @Override    public void onHandleIntent(Intent intent) {        Realm realm = Realm.getDefaultInstance();        // go do some network calls/etc and get some data and stuff it into a 'json' var        String json = customerApi.getCustomers();        realm.beginTransaction();        realm.createObjectFromJson(Customer.class, json); // Save a bunch of new Customer objects        realm.commitTransaction();        // At this point, the data in the UI thread is already up to date.        // ...    }    // ...}

一旦后台服务添加了新用户,customer 列表会被自动更新,不需要你的任何动作。对于单个的 RealmObject 也是同样。假设你需要管理一个 Realm 对象,只需要在一个线程中更新它的数据,UI 线程会自动得到更新后的数据。如果你需要对数据更新作出回应,只需要添加一个listener,就像我们在以上代码中所作的一样。

这就是所有啦。

跨线程使用 Realm

请谨记:Realm、RealmObject 和RealmResults 实例都不可以跨线程使用。但是你可以使用异步查询和异步事务来将部分操作放入后台线程进行,待完成时调用线程被通知以获取结果。

当你需要跨线程访问同一部分数据时,只需简单地在该线程重新获取一个 Realm 实例(例如:Realm.getInstance(RealmConfiguration config) 或是其他类似方法),然后通过这个 Realm 实例来查询获得你需要的数据。查询获得的对象会映射到 Realm 中的相同数据,由此方法获得对象在其线程中任何地方都可读写!

Schemas

Realm 使用所有项目中的 Realm 模型类来创建 schema。但这个行为是可以改变的,例如,你可以通过使用 RealmModule 让 Realm 只包含所有模型类的一个子集。

// Create the module@RealmModule(classes = { Person.class, Dog.class })public class MyModule {}// Set the module in the RealmConfiguration to allow only classes defined by the module.RealmConfiguration config = new RealmConfiguration.Builder()  .modules(new MyModule())  .build();// It is possible to combine multiple modules to one schema.RealmConfiguration config = new RealmConfiguration.Builder()  .modules(new MyModule(), new MyOtherModule())  .build();

共享schemas

库(library)开发者请注意: 在库中使用到的 Realm 必须通过 RealmModule 来暴露和使用其 schema。

这样可以防止库项目自动生成默认 RealmModule 从而避免和 app 生成的默认 RealmModule 冲突。库项目也是通过RealmModule 来向 app 项目暴露自己的 Realm 模型类。

// Library must create a module and set library = true. This will prevent the default// module from being created.// allClasses = true can be used instead of listing all classes in the library.@RealmModule(library = true, allClasses = true)public class MyLibraryModule {}// Library projects are therefore required to explicitly set their own module.RealmConfiguration libraryConfig = new RealmConfiguration.Builder()  .name("library.realm")  .modules(new MyLibraryModule())  .build();// Apps can add the library RealmModule to their own schema.RealmConfiguration config = new RealmConfiguration.Builder()  .name("app.realm")  .modules(Realm.getDefaultModule(), new MyLibraryModule())  .build();

目前你不可以在一个 Realm 文件里声明多个 RealmModule。如果你有多个 RealmModule,你需要将它们声明在多个 Realm 文件中以确保每个文件只有一个RealmModule 声明。

这里有一个如何使用在库和 app 项目间使用RealmModule 的完整例子。

JSON

你可以直接将 JSON 对象添加到 Realm 中,这些 JSON 对象可以是一个 String、一个 JSONObject 或者是一个 InputStream。Realm 会忽略 JSON 中存在但未定义在 Realm 模型类里的字段。单独对象可以通过 Realm.createObjectFromJson() 添加。对象列表可以通过 Realm.createAllFromJson() 添加。

// A RealmObject that represents a citypublic class City extends RealmObject {    private String city;    private int id;    // getters and setters left out ...}// Insert from a stringrealm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");    }});// Insert multiple items using an InputStreamrealm.executeTransaction(new Realm.Transaction() {    @Override    public void execute(Realm realm) {        try {            InputStream is = new FileInputStream(new File("path_to_file"));            realm.createAllFromJson(City.class, is);        } catch (IOException e) {            throw new RuntimeException();        }    }});

Realm 解析 JSON 时遵循如下规则:
* 使用包含空值(null)的 JSON 创建对象:
* 对于非必须(可为空值的属性),设置其值为 null
* 对于必须(不可为空值的属性),抛出异常;
* 使用包含空值(null)的 JSON 更新对象:
* 对于非必须(可为空值的属性),设置其值为 null
* 对于必须(不可为空值的属性),抛出异常;
* 使用不包含对应属性的 JSON:
* 该属性保持不变

通知(Notifications)

Listener 只工作于 Looper 线程。对于非 Looper 线程请使用 Realm.waitForChange()

当后台线程向 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 实例上添加监听器以外,你还可以在 RealmObjectRealmResults 实例上添加监听器。你可以通过这样的方式来监视对象和查询结果的改变。另外,当监听回调函数被调用时,相应的数据已经被更新,你不需要去做刷新操作。

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 listperson.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.

迁移(Migrations)

所有数据库都要处理模型改变的情况。Realm 的数据模型用标准 Java 对象来定义,改变数据模型只需要改变数据对象定义即可。

如果没有旧 Realm 数据文件存在,那么代码的改变即会反应到相应的 Realm 数据文件改变。但如果已经有旧版本的 Realm 数据文件存在,Realm 会抛出异常提示数据库文件需要迁移。请在相应的RealmConfiguration 设置 schema 版本和 migration 代码来正确处理并避免该异常抛出。

RealmConfiguration config = new RealmConfiguration.Builder()    .schemaVersion(2) // Must be bumped when the schema changes    .migration(new MyMigration()) // Migration to run instead of throwing an exception    .build()

如上示例使得相应的 migration 代码在迁移需要的时候被自动执行。我们提供了相关 API 用来升级已保存的 schema 以及对应之前 schema 的数据。

// Example migration adding a new classRealmMigration 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()    .deleteRealmIfMigrationNeeded()    .build()

加密

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

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

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

这保证了所有永久性存储在磁盘上的数据都是通过标准 AES-256 加密的。每次创建新的 Realm 实例的时候,都需要提供相同的密钥。

参考 examples/encryptionExample。这个例子演示了如何通过 Android KeyStore 来安全地存储密钥。

与 Android 相关

Realm 可以无缝地引入安卓开发。你需要谨记 RealmObject 的线程限制。当你需要跨 activity、service或者 broadcast receiver 传递 Realm 对象的时候,请牢记这一点。

适配器(Adapter)

Realm提供了一些抽象的工具类来方便地将 OrderedRealmCollectionRealmResultsRealmList 都实现了这个借口)展示到UI控件上。

  • RealmBaseAdapter 可以与 ListView 配合使用。参见示例.
  • RealmRecyclerViewAdapter 可以与 RecyclerView 配合使用。参见示例.

你需要在 app 的 build.gradle 中添加额外的依赖以使用这些适配器。

dependencies {compile 'io.realm:android-adapters:1.4.0'}

Intents

因为你不可以直接通过 intent 传递 RealmObject,我们建议你只传递 RealmObject 的标识符。举个常用例子,假如你的对象拥有一个主键,请通过 intent 的 bundle 来传递这个主键的值。

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

可以参考 threading example 中的 Object Passing 部分。该示例展示了在安卓开发中常用的如何传递 id 并且得到对应的RealmObject

Android Framework 多线程 API 相关

当你使用下列 API 时请小心:

  • AsyncTask
  • IntentService

AsyncTaskdoInBackground() 方法会运行在一个后台线程。IntentServiceonHandleIntent(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();        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) {        // Back on the Android mainThread        // do something with orderId such as query Realm        // for the order and perform some operation with it.    }}

IntentService

ChangeListener 在 IntentService 中不会工作。尽管 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();        // 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();    }}

对其它库的支持

这个章节描述了怎样与其它 Android 流行库搭配使用 Realm。

GSON

GSON 是 Google 开发的 JSON 处理库。GSON 与 Realm 可以无缝配合使用。

// Using the User classpublic 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);

GridViewExample 展示了如何配合 GSON 使用 Realm。

序列化(Serialization)

你有时需要序列化与反序列化一个 Realm 对象以便与其它库(比如 Retrofit)相配合。因为 GSON使用成员变量值而非 getter 和 setter,所以你无法通过 GSON 的一般方法来序列化 Realm 对象。

你需要为 Realm 模型对象自定义一个 JsonSerializer 并且将其注册为一个 TypeAdapter。

请参考这个 Gist。

数组(Primitive lists)

某些 JSON API 会以数组的形式返回原始数据类型(例如 String 和 integer),Realm 暂时不支持对这种数组的处理。但你可以通过自定义TypeAdapter来处理这种情况。

这个 Gist展示了如何将 JSON 中的整型数组存入 Realm。类似地,你可以用这个方法来处理其它原始数据类型数组。

Troubleshooting

Realm 对象属性可能会包含循环引用。在这种情况下,GSON 会抛出 StackOverflowError。例如如下 Realm 对象拥有一个Drawable 属性:

public class Person extends RealmObject {    @Ignore    Drawable avatar;    // other fields, etc}

Person 类含有一个 Android Drawable 并且被 @Ignore 修饰。当 GSON 序列化时,Drawable 被读取并且造成了堆栈溢出。(GitHub Issue)。添加如下代码以避免类似问题:

public boolean shouldSkipField(FieldAttributes f) {  return f.getDeclaringClass().equals(RealmObject.class) || f.getDeclaringClass().equals(Drawable.class);}

请注意对 Drawable.class 的判定语句,它告诉 GSON 跳过这个属性的序列化以避免堆栈溢出错误。

Jackson-databind

Jackson-databind 用来绑定 JSON 数据和其对应的 Java 类。

Jackson 需要使用发射进行工作。这与 Realm 的 RxJava 支持有冲突,因为作为可选功能,RxJava 有可能不再工程依赖库中。这会导致类似如下的异常抛出:

java.lang.NoClassDefFoundError: rx.Observableat libcore.reflect.InternalNames.getClass(InternalNames.java:55)

为了修正这个问题你可以添加 RxJava 依赖到工程中或者创建一个假类文件如下所示:

package rx;public class Observable {    // Dummy class required for Jackson-Databind support if    // RxJava is not a project dependency.}

这个问题也同时报告给了 Jackson 项目。

Kotlin

Realm 完全兼容 Kotlin 语言,但有些地方需要注意:

  • 你的模型类需要是开放的(open)。
  • 你可能需要在某些情况下添加注解 @RealmCLass 以保证编译通过。这是由于当前 Kotlin 注解处理器的一个限制。
  • 很多 Realm API 引用了 Java 类。你必须在编译依赖中添加 org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}

参见示例。

Parceler

Parceler 可以帮助对象自动生成支持Parcelable 接口的样板代码。因为 Realm 的代理类,你需要以下设置以便应用 Parceler 到 Realm 对象。

// All classes that extend RealmObject will have a matching RealmProxy class created// by the annotation processor. Parceler must be made aware of this class. Note that// the class is not available until the project has been compiled at least once.@Parcel(implementations = { PersonRealmProxy.class },        value = Parcel.Serialization.BEAN,        analyze = { Person.class })public class Person extends RealmObject {// ...}

如果你使用 Gradle 来获取 Parceler,请确保存在以下配置代码:

compile "org.parceler:parceler-api:1.0.3"apt "org.parceler:parceler:1.0.3"

请注意目前在使用 Parceler 的时候有如下的限制:

  1. 如果你的模型包含 RealmList,那么你需要注册一个特殊 adapter
  2. 一旦对象被打包(parcelled),它将变为一个有当前数据快照,不再被 Realm 管理的一个 unmanaged 对象。之后该对象的数据变化不会被 Realm 写入。

Retrofit

Retrofit 是一个由Square 开发,保证类型安全(typesafe)的 REST API 处理工具。

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

Robolectric

通过 Robolectric 库可以让你在不使用真实设备或者模拟器的情况下直接在 Java 虚拟机上进行 JUnit 测试。但目前 Roboletrics 不支持带有原生库的测试。而 Realm 包含使用 C++ 的原生库,所以你目前不可以通过 Roboletrics 测试使用 Realm 的项目。

你可以关注这个 Robolectric 的新功能请求。

RxJava

RxJava 是 Netflix 发布的一个Reactive 的扩展 库以支持观察者模式。

Realm 包含了对 RxJava 的原生支持。如下类可以被暴露为一个 ObservableRealm,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<E>.isLoaded() 方法。通过判断 RealmResults 是否已经加载可以得知查询是否已经完成。

参考 RxJava sample project。

配置

RxJava 是可选依赖,这意味着 Realm 不会自动包含它。这样做的好处是你可以选择需要的 RxJava 版本以及防止过多的无用方法被打包。如果你要使用相关功能,请手动添加 RxJava 到build.gradle 文件。

dependencies {  compile 'io.realm:realm-android:2.1.1'  compile 'io.reactivex:rxjava:1.1.0'}

你也可以通过继承 RxObservableFactory 来决定 Observable 的生成方式,然后通过 RealmConfiguration 进行配置。

RealmConfiguration config = new RealmConfiguration.Builder()  .rxFactory(new MyRxFactory())  .build()

如果没有 RxObservableFactory 被定义,RealmObservableFactory 会被默认使用,它支持 RxJava <= 1.1.*。

测试和调试

参考 unitTestExample 来获得与 JUnit3、JUnit4、Robolectric、Mockito 和 PowerMock 结合使用 Realm 的示例。

Happy hacking!欢迎在realm-java与我们真实存在的人类程序员交流!

在 Android Studio 中调试

当你使用 Android Studio 或者 IntelliJ 调试的时候请留神:调试视图中显示的变量值可能会造成误导。

举个例子,在 Android Studio 中查看一个RealmObject的所有属性。你会发现这些属性与期望不符。这时因为 Realm 为每个 RealmObject 创建了代理类,通过使用代理类的 getter 和 setter 方法来存取数据,从而原始对象的属性不会被赋值。请参考(更多信息)。请查看访问器的返回以获得正确数据,入下图所示:

Android Studio, IntelliJ Debug RealmObject

上图断点在113行。三个值被观察:person 变量,访问器person.getName()person.getAge()。代码107行到111行改变了 person 实例的 name 和 age,这些值在事务中被持久化。当运行暂停在113行时,可以观察到,调试器中显示的 person 属性是不正确的,但是访问器person.getName()person.getAge()的返回正确。请注意,.toString() 方法同样会输出正确的结果。

NDK Debugging

Realm 包含 native 代码。我们建议你使用崩溃报告工具如 Crashlytics 来记录 native 错误。更多信息意味着我们能帮你更快地解决问题。

默认的 stack trace 信息量较小,不利于解决 NDK 崩溃问题。 Crashlytics 允许你收集更多的崩溃信息。请参考 steps outlined in this guide。

添加如下代码到你工程根目录的 build.gradle 文件。请注意不需要添加 androidNdkOutandroidNdkLibsOut

crashlytics {  enableNdk true}

目前的限制

我们尽可能在减少使用 Realm 时的限制,但目前为止,仍有如下这些限制。

请参阅我们的 GitHub issues 获取更多关于已知问题的信息。

概要

Realm 的目标是在可扩展性和运行效率之间取得一个平衡,因此在保存数据方面有一些客观存在的限制,例如:

  1. 类名长度的上限是 57 个字符。Realm-java 在存储时会为对象名添加 class_ 前缀,你可以通过 Realm browser 看到;
  2. 成员变量名长度上限是 63 个字符;
  3. 不支持嵌套事务(transaction),使用嵌套事务会导致抛出异常;
  4. Stringbyte [] 大小不能超过 16MB;

字符串排序与查询(Sorting and querying on String)

针对字符串的排序和忽略大小写匹配只支持字符集 ‘Latin Basic’、’Latin Supplement’、’Latin Extended A’、’Latin Extended B’ (UTF-8 range 0-591)。另外在使用equalTo()contains()endsWith()beginsWith() 进行忽略大小写查询时,它们仅在字符为英文环境(English locale)时起效。

针对字符串的排序,只支持字符集 ‘Latin Basic’、’Latin Supplement’、’Latin Extended A’、’Latin Extended B’ (UTF-8 range 0-591)。如果排序对象不在这些字符集的范围内,那么RealmResults 的顺序并不会被改变。

多线程(Threads)

Realm数据文件本身支持多线程并发访问。但是Realm实例、RealmObjectRealmQuery以及RealmResults不可以跨线程使用。请参考多线程示例获取更多在多线程环境中使用Realm的信息。更多信息。

Realm 文件不支持多进程访问

尽管 Realm 文件支持多线程访问,但还不支持多进程访问。不同进程请使用不同的 Realm 文件拷贝。我们很快会提供对于多进程的支持。

RealmObject’s hashCode

RealmObject是实时更新的对象,它们有可能因为其它线程对同对象的改动而更新。尽管两个 Realm 对象在调用RealmObject.equals() 返回true 时会有相同的哈希值,但它们的哈希值并不是不变的。因此,不应该将它们放入HashMapHashSet 等依赖于不变哈希值的容器中。

最佳实践

防止出现 ANR

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

控制 Realm 实例的生命周期

RealmObjectsRealmResults 在访问其引用数据时都是懒加载的。因为这个原因,请不要关闭你的 Realm 实例如果你仍然需要访问其中的 Realm 对象或者查询结果。为了避免不必要的 Realm 数据连接的打开和关闭,Realm 内部有一个基于引用计数的缓存。这表示在同一线程内调用Realm.getDefaultInstance() 多次是基本没有开销的,并且底层资源会在所有实例都关闭的时候才被释放。

以 UI 线程举例,最简单安全的途径是,在你所有的 Activity 和 Fragment 初始化时取得 Realm 实例,并在它们销毁时关闭 Realm 实例。

// Setup Realm in your Applicationpublic class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().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 的线程中,RealmObjectRealmResults 都会在 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    }    // ...}

配方

我们特别制作了些特别的 Realm 配方,经常去这里找找看,没准有你需要的。如果你需要这里没有的配方,请在GitHub创建一个问题。

  • Building an Android Clustered Map View
  • Building an Android Search Controller
  • Building a Grid Layout With RecyclerView and Realm

Realm 移动端平台 (The Realm Mobile Platform)

Realm 移动端平台(RMP)扩展了 Realm 移动端数据库,实现了在设备间的 Realm 数据同步。它们的基本 API 是完全相同的,RMP 增加了一些 API 来实现相关的同步功能。

开启 Realm 移动端平台支持 (Enabling Realm Mobile Platform)

请添加如下代码到你的 app 的 build.gradle 文件中:

realm {  syncEnabled = true;}

完成!

创建用户并登录 (Creating and Logging in Users)

Realm SyncUser 对象是 Realm 对象服务器的核心组件,它与每个需要同步的 Realm 数据库相关联。SyncUser 对象支持的登录方式包括常规的用户名/密码登录和第三方的登录。

创建用户并登录需要如下两个信息:

  • 相应的 Realm 登录服务器的 URL;
  • 相关的校验信息(例如用户名/密码或者访问秘钥等)。

  • A URL (as a string) of a Realm Authentication Server to connect to.
  • Credentials for an authentication mechanism that describes the user as appropriate for that mechanism (i.e., username/password, access key, etc).

Realm 通过这些信息来创建 SyncUser 对象。

创建校验信息 (Creating a Credential)

参考 Realm Object Server Authentication 来获得第三方登录相关信息。

用户名/密码
SyncCredentials myCredentials = SyncCredentials.usernamePassword(username, password, true);

第三个参数为 true 表示在登录之前首先创建用户。请注意,如果用户已存在,该参数应设置为 false

Google
String token = "..."; // a string representation of a token obtained by Google Login APISyncCredentials myCredentials = SyncCredentials.google(token);
Facebook
String token = "..."; // a string representation of a token obtained by Facebook Login APISyncCredentials myCredentials = SyncCredentials.facebook(token);

登录 (Logging in the User)

有了校验信息以后我们就可以登录 Realm 对象服务了。

String authURL = "http://my.realm-auth-server.com:9080/auth";SyncUser user = SyncUser.login(myCredentials, authURL);

用户相关的操作 (Working with Users)

对于普通 Realm,我们使用 RealmConfiguration 来进行配置。安卓 RMP 使用 SyncConfiguration 来对其独有的配置进行扩展。这些独有配置包括一个已验证的用户和一个 Realm 对象服务的 URL。同步服务器的 URL 中的波浪号(~)会被替换为相关用户的唯一标识符。Realm 默认会自动配置同步 Realm 文件的存储路径,当然你也可以指定路径。

SyncUser user = getUserFromLogin();String serverURL = "realm://my.realm-server.com/~/default";SyncConfiguration configuration = new SyncConfiguration.Builder(user, serverURL).build();

你也可以得到当前已登录并且登录状态仍然有效的用户。

SyncUser user = SyncUser.getCurrentUser();

得到用 JSON 表示的用户与其校验信息。

String userJson = user.toJson();

你可以通过这个 JSON 串来获取一个 SyncUser 实例而无需从新经过第三方登录服务:

SyncUser user = SyncUser.fromJson(userJson);

Realm saves the current user using a UserStore. The default UserStore is backed by a private Shared Preference file, but this behaviour can be overridden usingSyncManager.setUserStore(userStore).

It is important to remember that users - and their credentials - are considered sensitive data.

退出登录 (Logging Out)

退出登录很简单:

user.logout();

当用户退出登录,同步将会停止。只有在相关所有的 Realm 实例都被关闭的前提下,才可以退出登录。当用户退出登录后,SyncConfigurtion 将会失效,无法在使用它打开 Realm。

打开一个同步 Realm 实例 (Opening a Synchronized Realm)

一旦 SyncConfiguration 被创建,就可以像打开一个普通 Realm 实例一样打开一个同步 Realm 实例:

Realm realm = Realm.getInstance(syncConfiguration);

日志 (Logging)

有时候调试同步 Realm 会比较麻烦,获取更多的日志信息会对调试有很大的帮助。使用如下代码来允许 Realm 输出更多的调试信息:

RealmLog.setLevel(Log.VERBOSE);

错误处理 (Error reporting)

你可以为每个 SyncConfiguration 注册一个错误处理器来处理相关错误:

SyncConfiguration configuration = new SyncConfigurtion.Builder(user, serverURL)  .errorHandler(new Session.ErrorHandler() {    void onError(Session session, ObjectServerError error) {        // do some error handling    }  })  .build();

你也可以为所有 SyncConfiguration 注册一个全局的错误处理器:

SyncManager.setDefaultSessionErrorHandler(myErrorHandler);

数据迁移 (Migrations)

同步 Realm 支持自动数据迁移。但目前只支持增加性的数据改动,例如新的 schema 增加了一个类,或是某个类中添加了一个字段。非增加性的 schema 变动会导致异常被抛出。

数据迁移会被自动出发,无须添加自定义 migration 块或是 schema 版本。

冲突处理 (Conflict Resolution)

请参考 Realm Object Server documentation。

FAQ

我怎么才能查看我的Realm文件的数据?

StackOverflow 上的这个答案介绍了怎么找到你的 Realm 文件。你可以通过Realm Browser 来查看数据内容

Realm 基础库有多大?

大部分情况下,在你 release 版本的 apk 文件里,Realm 只占用 800KB 空间。我们发布的版本支持的处理器架构包括 ARM7、ARMv7、ARM64、x86、MIPS。所以发布的库文件本身看起来会稍微大一些。但安卓系统在安装 apk 时只会安装针对该设备处理器架构的原生库,安装后占用空间会比 apk 文件本身还要小一些。

你可以通过将 APK 针对不同平台分离打包以减少其大小。 添加如下代码到 build.gradle

splits {    abi {        enable true        reset()        include 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64'    }}

根据你设置的每个 ABI,会生成一个独立 APK 文件。参考Android Tools documentation。

我们的这个实例也演示了同样的功能 GitHub。

Realm 是开源的吗?

是的!Realm 的底层 C++ 引擎和上层 SDK 都是在 Apache 2.0 协议下的开源软件。Realm 还包括一个闭源的 Realm 移动端平台扩展,但如果你只需要 Realm 移动端数据库功能,你讲不要需要它。

普通 Java 对象和 Realm 对象之间有什么区别?

主要区别在于普通 Java 对象本身会包含其数据,但 Realm 对象不会。Realm 对象通过 get 或者 set 方法来直接从 Realm 数据库中存取数据。

Realm 对象实例可以是托管(managed)或是非托管(unmanaged)的。

  • 托管对象 是被 Realm 持久化的,会自动更新且被限制在了指定线程。因为它会在 Java 堆上占用更少空间,所以总体上它比非托管对象更轻量。

  • 非托管对象 就和普通的 Java 对象一样。它们没有被持久化且不会自动更新。它们没有线程限制。

托管对象和非托管对象之间可以通过 Realm.copyToRealm()Realm.copyFromRealm() 来相互转换。

为什么模型类需要继承于 RealmObject?

原因是我们需要针对模型类添加一些易用的接口。另外它让我们比较容易在内部使用范型(generic)从而提高代码的易读性和易用性。如果你因为种种原因不想使用继承,你也可以通过实现RealmModel 接口来定义对象。

RealmProxy类是做什么用的?

我们使用 RealmProxy 类来保证 Realm 对象本身不存储任何实际的数据,进而通过直接访问 Realm 数据来存取数据。

对于你项目中的模型类,Realm 的注解处理器(Annotation processor)会生成相应的 RealmProxy 类。该代理类继承于你的模型类,代理类是你在调用Realm.createObject() 时实际返回的类型。

我为什么需要使用事务(transaction)才能写入Realm数据库?

Transactions 是为了保证对数据的原子操作。它使得你可以保证多个数据被一次改变,或者保持不变。通过事务你可以精确控制你对每次需要改变多少数据(例如一次改变多个对象)。

基于 SQL 的数据库如 SQLite,插入多条数据通常是被自动隐性包含在事务中一次完成的。但在 Realm 中,写入事务永远是显性的。

怎么处理 out-of-memory 异常?

Realm 基于我们的原生嵌入式存储引擎。该引擎会在原生内存堆(native memory)上而不是Java虚拟机的内存堆分配内存。如果你的应用在内存管理上的存在问题导致 Realm 无法分配内存,io.realm.internal.OutOfMemoryError 异常会被抛出。请一定不要忽略这个异常!请不要使用空catch 语句块来忽略这个异常!这么做有可能导致你的 Realm 数据库文件损坏!在该异常抛出时终止应用程序是不会损坏数据库的。如果你遇到了该异常,请检查应用是否有内存泄漏,或者其它内存使用异常的问题。

Realm 数据库文件大小

一般来说,Realm 数据文件比同等情况下的 SQLite 数据文件更小。

为了你数据的连续性,Realm 会同时保存数据的不同版本。如果你在一个线程中读取了 Realm 数据之后阻塞了该线程,同时又在其它线程中写入了 Realm,那么在第一个线程中的 Realm 不会被更新,并且因此一个非最新的 Realm 数据版本会被保存(这可能并不是你需要的数据)。以上会导致你的 Realm 文件体积增大(当然,Realm 会重用这些额外的磁盘空间占用,或者你可以通过调用compactRealmFile 来释放这部分磁盘空间)。

但我运行我的 app 时,看到有指向 Mixpanel 的网络连接,那是什么?

Realm 在字节码处理器运行的时候会收集匿名数据。我们保证这个过程是完全匿名的。它通过告诉我们你在使用哪个 Realm 版本、你的操作系统是什么帮助我们提高Realm的质量。它不会在你的生产环境中执行,也不会在你的用户设备上执行。它只会在注解处理器运行的时候执行。你可以在我们的源代码中看到它具体收集了哪些信息。

Couldn’t load “librealm-jni.so”

如果你的 app 还包含其它原生库,并且它们没有提供对 arm64 平台的支持,arm64 的安卓设备在加载 Realm 的 librealm-jni.so 时可能会失败。这是因为安卓无法同时加载32位和64位的原生库。最佳解决方案是为所有原生库提供 arm64 版本。但在你使用第三方闭源库的时候这不太好实现。参考VLC and Realm Library conflicts。

一个解决方案是在编译期通过配置 gradle 将 Realm 的 arm64 库排除在外:

android {    //...    packagingOptions {        exclude "lib/arm64-v8a/librealm-jni.so"    }    //...}

更多信息请参考 Mixing 32- and 64-bit Dependencies in Android。

怎样备份和恢复 Realm?(How to back up and restore Realm?)

Realm 作为文件存储在文件系统中。你可以通过调用 getPath() 得到 Realm 文件的路径。你只需备份或者恢复该文件即可。但请注意,备份恢复前应务必保证所有 Realm 实例关闭,否则可能损坏数据。

你还可以通过realm.writeCopyTo(File)来备份 Realm 数据库

0 0
原创粉丝点击