Realm Java
原文 官网 https://realm.io/cn/docs/java/latest/Realm Java 让你能够高效地编写 app 的模型层代码,保证你的数据被安全、快速地存储。参考下列示例来开始你的 Realm 之旅:
// Define you model class by extending the RealmObject public class Dog extends RealmObject { @Required // Name cannot be null private String name; private int age; // ... Generated getters and setters ... } public class Person extends RealmObject { @Required // Name is not nullable private String name; private String imageUrl; // imageUrl is an optional field private RealmList<Dog> dogs; // A person has many dogs (a relationship) // ... Generated getters and setters ... } // Use them like regular java objects Dog dog = new Dog(); dog.setName("Rex"); dog.setAge(1); Log.v(TAG, "Name of the dog: " + dog.getName()); // Create a RealmConfiguration which is to locate Realm file in package's "files" directory. RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build(); // Get a Realm instance for this thread Realm realm = Realm.getInstance(realmConfig); // Query Realm for all dogs less than 2 years old final 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 easily realm.beginTransaction(); realm.copyToRealm(dog); realm.commitTransaction(); // Listeners will be notified when data changes puppies.addChangeListener(new RealmChangeListener<RealmResults<Dog>>() { @Override public void onChange(RealmResults<Dog> results) { // Queries are updated in real time puppies.size(); // => 1 } }); // Query and update the result asynchronously in another thread realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { // begin & end transcation calls are done for you Dog theDog = realm.where(Dog.class).equals("age", 1).findFirst(); theDog.setAge(3); } }, new Realm.Transaction.Callback() { @Override public void onSuccess() { // Original Queries and Realm objects are automatically updated. puppies.size(); // => 0 because there are no more puppies (less than 2 years old) dog.getAge(); // => 3 the dogs age is updated } });
快速入门
下载 Realm Android 或者在 GitHub 的 realm-java 页面查看源代码.
前提
Realm 作为一个 Gradle 插件来安装需要如下两个步骤:
第一步: 在项目的 build.gradle 文件中添加如下 class path 依赖。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:0.90.1"
}
}
项目的 build.gradle
文件在如下位置:
第二步: 在 app 的 build.gradle 文件中应用 realm-android
插件。
apply plugin: 'realm-android'
app的 build.gradle
文件在如下位置:
请参考以下示例工程对两个 build.gradle
文件的修改:
其它构建系统
构建工具 Maven 和 Ant 已不再被支持。如果您仍然有充足的理由需要得到这些构建工具的支持,请在如下两个链接留言说明您的理由以便我们决定是否应该添加对它们的支持。
从 v0.88
开始,我们放弃了对 Eclipse 的支持。如果您想继续使用 Eclipse,可以考虑使用 Realm 的 v0.87.5
版本。不过请注意,我们不会再对该版本进行更新。
从 Realm 0.88.0 版本开始,Realm 已经集成了 ProGuard 的配置。您不需要针对 Realm 做 ProGuard 的改动。
我们提供了一个独立的应用程序 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 手册获得所有关于类、方法及其它细节的信息。
请查看我们的示例来获得有关 Realm 的常用功能。您只需要在 Android Studio 中 Import Project
然后点击 run
.
introExample 包含了如何使用当前的API的简单例子。
gridViewExample 用来展示如何使用 Realm 作为 GridView 的后端存储。它同时也展示了如何用 JSON 来填充数据库。另外还有怎么通过 ABI splits 来缩小 APK 体积。
threadExample 展示了如何在多线程环境中使用 Realm。
adapterExample 展示了如何以一个非常便捷的方式使用 RealmBaseAdapter 绑定 RealmResults 到安卓的 ListView。
jsonExample 展示了 Realm 与 JSON 相关的功能。
encryptionExample 向您展示如何使用加密的 Realm。
rxJavaExamples 展示了如何与 RxJava 结合使用 Realm。
unitTestExample 展示了如何写与 Realm 相关的单元测试。
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
成员变量,您还可以使用 public
、protected
以及自定义的成员方法。
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 支持以下字段类型:boolean
、byte
、short
、int
、long
、float
、double
、String
、Date
和byte []
。整数类型 short
、int
和 long
都被映射到 Realm 内的相同类型(实际上为 long
)。再者,还可以使用 RealmObject
的子类和 RealmList extends RealmObject>
来表示模型关系。
Realm 对象中还可以声明包装类型(boxed type)属性,包括:Boolean
、Byte
、Short
、Integer
、Long
、Float
和Double
。通过使用包装类型,可以使这些属性存取空值(null)。
@Required
修饰类型和空值(null)某些时候,空值(null)对于属性并不合适。这时可以使用注解 @Required
告诉 Realm 强制禁止空值(null)被存储。只有 Boolean
、 Byte
、 Short
、 Integer
、 Long
、 Float
、 Double
、 String
、 byte[]
以及 Date
可以被 @Required
修饰。在其它类型属性上使用 @Required
修饰会导致编译失败。基本数据类型(primitive types)不需要使用注解 @Required
,因为他们本身就不可为空。RealmObject
属性永远可以为空。
注解 @Ignore
意味着一个字段不应该被保存到 Realm。某些时候输入的信息包含比模型更多的字段,而您不希望处理这些未使用的数据字段,您可以用 @Ignore
来标识这些您希望被 Realm 忽略的字段。
RealmObject
是实时的、自动更新的底层数据的映射视图。您不需要去重新获得对象已取得其最新版本。对于数据的改动会即时反应到相关的对象或者查询结果。
realm.beginTransaction();
Dog myDog = realm.createObject(Dog.class);
myDog.setName("Fido");
myDog.setAge(1);
realm.commitTransaction();
Dog myPuppy = realm.where(Dog.class).equalTo("age", 1).findFirst();
realm.beginTransaction();
myPuppy.setAge(2);
realm.commitTransaction();
myDog.getAge(); // => 2
RealmObject
和 RealmResults
的设计不仅仅是为了更快和更有效率,它们同时也让代码更加简洁以及反应性更强。举例来说,假设您的 Activity 或者 Fragment 依赖于某个 RealmObject
或者 RealmResults
,您无需担心何时去刷新或者重新获取它们以更新 UI——它们会自动更新。
您可以通过订阅 Realm notifications 来得知 Realm 的数据在何时被更新从而刷新您的 UI。
注解 @Index
会为字段增加搜索索引。这会导致插入速度变慢,同时数据文件体积有所增加,但能加速查询。因此建议仅在需要加速查询时才添加索引。目前仅支持索引的属性类型包括:String
、byte
、short
、int
、long
、boolean
和Date
。
@PrimaryKey
可以用来定义字段为主键,该字段类型必须为字符串(String
)或整数(short
、int
或 long
)以及它们的包装类型(Short
、Int
或 Long
)。不可以存在多个主键。使用支持索引的属性类型作为主键同时意味着为该字段建立索引。
当创建 Realm 对象时,所有字段会被设置为默认值。为了避免与具有相同主键的另一个对象冲突,建议创建一个 standalone 对象,为字段的赋值,然后用 copyToRealm()
方法将该对象复制到 Realm。
主键的存在意味着可以使用 createOrUpdate()
方法,它会用此主键尝试寻找一个已存在的对象,如果对象存在,就更新该对象;反之,它会创建一个新的对象。
使用主键会对性能产生影响。创建和更新对象将会慢一点,而查询则会变快。很难量化这些性能的差异,因为性能的改变跟您数据库的大小息息相关。
Realm.createObject()
会返回一个所有字段被设置为默认值的新对象。如果该模型类存在主键,那么有可能返回对象的主键的默认值与其它已存在的对象冲突。建议创建一个独立的(standalone)Realm 对象,并给其主键赋值,然后调用 copyToRealm()
来避免冲突。
MyObject obj = new MyObject();
obj.setId(42);
obj.setName("Fish");
realm.beginTransaction();
// This will create a new one in Realm
// realm.copyToRealm(obj);
// This will update a existing one with the same id or create a new one instead
realm.copyToRealmOrUpdate(obj);
realm.commitTransaction();
字符串(String
)和包装类型(Short
、Int
或 Long
)的主键可以被赋予空值(null
);除非它们同时被 @Required
修饰。
目前不支持 final
、transient
和 volatile
修饰的成员变量,这主要是为了 Realm 对象在不被 Realm 管理(Standalone RealmObject)时候的行为差异化。
Realm 数据模型不可以继承自除了 RealmObject
以外的其它对象。
除直接继承于 RealmObject
来声明 Realm 数据模型之外,还可以通过实现 RealmModel
接口并添加 @RealmClass
修饰符来声明。
@RealmClass
public class User implements RealmModel {
}
RealmObject
的所有方法都有其相对应的静态方法。
// With RealmObject
user.isValid();
user.addChangeListener(listener);
// With RealmModel
RealmObject.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
为一个对象关联0或多个其它对象。
public class Contact extends RealmObject {
private RealmList<Email> emails;
// Other fields…
}
RealmList
是 Realm 模型对象的容器,其行为与 Java 的普通 List
近乎一样。同一个 Realm 模型对象可以存在于多个 RealmList
中。同一个 Realm 模型对象可以在同一个 RealmList
中存在多次。您可以使用 RealmList
来表现一对多核多对多的数据关系。
使用 Contact 和 Email 类:
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
}
您可以通过标准的 getter 和 setter 来访问 RealmList
.
realm.beginTransaction();
Contact contact = realm.createObject(Contact.class);
contact.setName("John Doe");
Email email1 = realm.createObject(Email.class);
email1.setAddress("[email protected]");
email1.setActive(true);
contact.getEmails().add(email1);
Email email2 = realm.createObject(Email.class);
email2.setNumber("[email protected]");
email2.setActive(false);
contact.getEmails().add(email2);
realm.commitTransaction();
有时递归关系很有用,这在 Realm 是允许的。
public class Person extends RealmObject {
private String name;
private RealmLink<Person> friends;
// Other fields…
}
当然,在使用递归关系的时候,您要注意死循环的问题。Realm 并不会检查 RealmList 的循环嵌套。
设置一个类型为 RealmList
的属性为空值(null)会清空该列表,即列表长度变为 0。但并不会删除列表中的任何 RealmObject
。RealmList
的获取器(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
}
每个 User
对象都与多个 Dog
对象相关联,如下图所示:
让我们通过关联查询来得到一些 User 对象 ——
// users => [U1,U2]
RealmResults<User> users = realm.where(User.class)
.equalTo("dogs.color", "Brown")
.findAll();
首先,请注意equalsTo
的field
名称包含关联的路径,以“.”分隔。
以上的查询含义为“所有至少含有一个 color 为 Brown 的 User”。请务必注意,这里的返回的 User 中,有可能包含 color 不为 Brown 的 Dog 对象,因为在其 RealmList
列表中,其它的 Dog 对象满足查询条件:
users.get(0).getDogs(); // => [A,B]
users.get(1).getDogs(); // => [B,C,D]
我们来看看另外两个查询:
// r1 => [U1,U2]
RealmResults<User> r1 = realm.where(User.class)
.equalTo("dogs.name", "Fluffy")
.findAll();
// r2 => [U1,U2]
RealmResults<User> r2 = r1.where()
.equalTo("dogs.color", "Brown")
.findAll();
请注意第一个查询返回两个 User
对象,因为它们都满足查询条件。每个 User
对象都包含一个 Dog
对象列表——列表中至少有一个 Dog
对象满足查询条件。谨记我们是在寻找其拥有的 Dog
对象满足条件(name
和 color
)的 User
,不是在针对 Dog
对象进行查询。因此第二个查询建立在第一个的 User
结果(r1
)以及 r1
的每个 User
的 Dog
列表之上。两个 User
仍然满足第二个查询,但这次是 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();
第一个查询表示找到所有的 User
他至少有一个 Dog
的名字为 fluffy
并且找到所有 User
他至少有一个 Dog
的颜色是 brown
然后返回这两个结果的交集。第二个查询表示找到所有的 User
他至少有一个 Dog
的名字为 fluffy
;然后在这个结果之上找到所有的 User
他至少有一个 Dog
的颜色为 brown
;最后在之前的结果之上找到所有的 User
他至少有一个 Dog
的颜色为 yellow
。
我们来解释一下第一个查询以深入了解下这个行为。两个条件分别是equalto("dogs.name", "fluffy")
和 equalto("dogs.color", "brown")
。u1
和 u2
完全满足第一个条件 —— 我们称其 c1
集合。u1
和 u2
也同时完全满足第二个条件 —— 我们称其 c2
集合。查询中的逻辑与即是 c1
与 c2
的交集。c1
与 c2
的交集就是 u1
和 u2
。因此,r1
就包含 u1
和 u2
。
第二个查询不一样。我们来分别讲解。该查询第一部分看起来是这样的:realmresults
。它的结果包含 u1
和 u2
。然后 r2b = r2a.where().equalto("dogs.color", "brown").findall();
的结果仍然包含 u1
和 u2
(两个 User
都有颜色为 brown
的 Dog
)。最后的查询 r2 = r2b.where().equalto("dogs.color", "yellow").findall();
结果只包含 u2
,因为只有 u2
同时有一个颜色为 brown
的 Dog
和一个颜色为 yellow
的 Dog
。
在任何时间都可以对对象进行访问和查询(读取事务是隐式的)。 所有的写操作(添加、修改和删除对象),必须包含在写入事务(transaction)中。写入事务可以提交或取消。在提交期间,所有更改都将被写入磁盘,并且,只有当所有更改可以被持久化时,提交才会成功。通过取消一个写入事务,所有更改将被丢弃。使用写入事务,会确保您的数据的一致性。
写入事务也用于确保线程安全:
// Obtain a Realm instance
Realm 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 的 MVCC 架构,当正在进行一个写入事务时读取操作并不会被阻塞!这意味着,除非您需要从多个线程进行并发写入操作,否则,您可以尽量使用更大的写入事务来做更多的事情而不是使用多个更小的写入事务。当写入事务被提交到 Realm 时,该 Realm 的所有其他实例都将被通知,读入隐式事务将自动刷新您每个 Realm 对象。
在 Realm 中的读写访问是符合 ACID 的.
由于 Realm 对象都强依赖于 Realm,它们应该直接通过 Realm 被实例化:
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("[email protected]");
realm.commitTransaction();
或者您可以先创建一个对象的实例,并在之后使用 realm.copyToRealm() 添加。Realm 对象支持多个构造函数,只要其中之一是公共无参数构造函数即可。
User user = new User("John");
user.setEmail("[email protected]");
// Copy the object to Realm. Any further changes must happen on realmUser
realm.beginTransaction();
User realmUser = realm.copyToRealm(user);
realm.commitTransaction();
当使用 realm.copyToRealm()
时,请注意只有返回的对象是由 Realm 管理的,这非常重要。对原始对象(Standalone Object)的任何改变都不会写入 Realm.
除手动调用 realm.beginTransaction()
、realm.commitTransaction()
和 realm.cancelTransaction()
之外你可以使用 realm.executeTransaction() 方法,它会自动处理写入事物的开始和提交,并在错误发生时取消写入事物。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("[email protected]");
}
});
事务会相互阻塞其所在的线程,在后台线程中开启事务进行写入操作可以有效避免 UI 线程被阻塞。通过使用异步事务,Realm 会在后台线程中进行写入操作,并在事务完成时将结果传回调用线程。
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("[email protected]");
}
}, 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.
}
});
OnSuccess
和 OnError
并不是必须重载的,重载了的回调函数会在事务成功或者失败时在被调用发生的线程执行。回调函数是通过 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("[email protected]");
}
}, null);
异步事务调用会返回一个 RealmAsyncTask
对象。当你退出 Activity 或者 Fragment 时可以使用该对象取消异步事务。如果你在回调函数中更新 UI,那么忘记取消异步事务可能会造成你的应用崩溃。
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
Realm 的写操作针对的是整个字符串或 byte 数组属性而非该属性中的单独元素。假设你需要修改某 byte 数组中的第五个字符,你需要:
realm.beginTransaction();
byte[] bytes = realmObject.binary;
bytes[4] = 'a';
realmObject.binary = bytes;
realm.commitTransaction();
原因是 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()
并非所有条件都适用于所有数据类型,具体请参考 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 ascending
result = result.sort("age", Sort.DESCENDING);
因为查询结果并不会被复制,且在查询提交时并不会被执行,您可以链式串起查询并逐步进行分类筛选:
RealmResults<User> teenagers = realm.where(User.class).between("age", 13, 20).findAll();
User firstJohn = teenagers.where().equalTo("name", "John").findFirst();
您也可以在子对象上使用链式查询。假设以上 User
对象包含一个 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 之间的 User
并且他至少拥有一个 1 岁的 Dog
:
RealmResults<User> teensWithPups = realm.where(User.class).between("age", 13, 20).equalTo("dogs.age", 1).findAll();
请注意,查询链最终是建立在 RealmResults
上而非 RealmQuery
。如果你在某存在的 RealmQuery
上添加更多的查询条件,那么你在修改查询本身,而非查询链。请参考关联查询。
RealmResults
是对其所包含数据的自动更新视图,这意味着它永远不需要被重新查询获取。数据对象的改变会在下一次 Looper 事件中被反映到相应的查询结果。
RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => 0
realm.beginTransaction();
Dog dog = realm.createObject(Dog.class);
dog.setName("Fido");
dog.setAge(1);
realm.commitTransaction();
puppies.addChangeListener(new RealmChangeListener() {
@Override
public void onChange(RealmResults<Dog> results) {
// results and puppies point are both up to date
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 事件触发,但在事件到来之前,某些元素有可能不再满足查询条件或者其已被删除。
RealmResults<User> users = getUsers();
realm.beginTransaction();
users.get(0).deleteFromRealm(); // indirectly delete object
realm.commitTransaction();
for (User user : users) {
showUser(user); // Will crash for the deleted user
}
为避免该问题,可以使用 RealmResults
的 deleteFromRealm()
方法:
RealmResults<User> users = getUsers();
realm.beginTransaction();
users.deleteFromRealm(0); // Delete and remove object directly
realm.commitTransaction();
for (User user : users) {
showUser(user); // Deleted user will not be shown
}
你可以从查询结果中删除数据:
// obtain the results of a query
RealmResults<Dog> results = realm.where(Dog.class).findAll();
// All changes to data must happen in a transaction
realm.beginTransaction();
// remove single match
results.deleteFromRealm(0);
results.deleteLastFromRealm();
// remove a single object
Dog dog = results.get(5);
dog.deleteFromRealm();
// Delete all matches
results.deleteAllFromRealm();
realm.commitTransaction();
可以使用后台线程进行查询。
Realm 的大部分查询都非常快——快到可以使用在UI线程中而感觉不到延迟。但如果需要进行非常复杂的查询或者在大量数据中进行查询,那么使用后台线程进行查询将会是一个不错的主意。
示例:查找名字为 “John” 或者 “Peter” 的用户。
RealmResults<User> result = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAllAsync();
请注意,这里的调用并不会阻塞,而是立即返回一个 RealmResults
。这很类似于标准 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 线程中使用异步查询。异步查询需要使用 Handler 来传递查询结果。在没有 Looper 的线程中使用异步查询会导致 IllegalStateException
异常被抛出。
Realm(s) 是我们对数据库的称谓:它包含多个不同的对象,并对应磁盘中的一个文件。
您可能已经注意到,我们总是通过Realm.getInstance(this)
来访问我们已初始化的realm变量。该静态方法会为你的当前线程返回一个Realm实例,它对应了您Context.getFilesDir()
目录中的default.realm
文件。
该文件位于您应用的可写根目录中。默认情况下的Realm使用内部存储(internal storage),您的应用并不需要取得任何读写权限。一般来说,这个文件位于/data/data/
。
您可以通过realm.getPath()
来获得该Realm的绝对路径。
请务必注意到Realm的实例是线程单例化的,也就是说,在同一个线程内多次调用静态方法获得针对同路径的Realm,会返回同一个Realm实例。
RealmConfiguration
用来配置要被创建的 Realm 的各种特性。最简配置如下所示:
RealmConfiguration config = new RealmConfiguration.Builder(context).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(context)
.name("myrealm.realm")
.encryptionKey(getKey())
.schemaVersion(42)
.modules(new MySchemaModule())
.migration(new MyMigration())
.build();
// Use the config
Realm realm = Realm.getInstance(config);
您还可以有多个RealmConfiguration
。如此,您便可以控制Realm的版本、结构(schema)和路径。
RealmConfiguration myConfig = new RealmConfiguration.Builder(context)
.name("myrealm.realm")
.schemaVersion(2)
.modules(new MyCustomSchema())
.build();
RealmConfiguration otherConfig = new RealmConfiguration.Builder(context)
.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
可以保存为默认配置。通过在自定义的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(this).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();
}
}
定义一个非持久化的、存在于内存中的 Realm 实例:
RealmConfiguration myConfig = new RealmConfiguration.Builder(context)
.name("myrealm.realm")
.inMemory()
.build();
这样就可以创建一个存在于“内存中的” Realm。“内存中的”Realm 在内存紧张的情况下仍有可能使用到磁盘存储,但是这些磁盘空间都会在Realm实例完全关闭的时候被释放。
请注意使用同样的名称同时创建“内存中的”Realm 和常规的(持久化)Realm 是不允许的。
当某个“内存中的”Realm 的所有实例引用都被释放,该 Realm 下的数据也同时会被清除。建议在您的应用生命周期中保持对“内存中的” Realm 实例的引用以避免非期望的数据丢失。
对于普通的 Realm 来说,数据模型被定义成了 RealmObject
的子类。这样做保证了类型安全,但有时候某些数据模型在编译期是无法获得的。例如在处理数据迁移(migration)或CSV文件的时候。
DynamicRealm
是普通 Realm 的一个变种。它可以在没有 RealmObject
子类的情况下操作 Realm 数据。其对数据的访问是基于字符串而非 RealmObject
的定义。
创建 Dynamic Realm 使用与创建普通 Realm 相同的RealmConfiguration
,但是它的创建过程会忽略对 schema、migration以及 schema 版本的检查。
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);
// In a DynamicRealm all objects are DynamicRealmObjects
DynamicRealmObject person = realm.createObject("Person");
// All fields are accessed using strings
String 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 exception
person.getString("I don't exist");
// Queries still work normally
RealmResults<DynamicRealmObject> persons = realm.where("Person")
.equalTo("name", "John")
.findAll();
DynamicRealm
以类型安全和性能为代价,换来了更多的灵活性。请在你确实需要这些灵活性的情况下使用。
Realm
实现了 Closeable
接口以便与释放 native 内存和文件描述符,请务必在使用完毕后关闭 Realm 实例。
Realm
实例是基于引用计数的, 也就是说假设您在同一个线程中调用了 getInstance()
两次,您需要同样调用 close()
两次以关闭该实例。举例来说,如果您需要实现 Runnable
,简单地在函数开始的时候调用 getInstance()
,在函数结束的时候调用 close()
即可!
对于UI线程,您可以选择在 onDestroy()
方法内调用 realm.close()
。
对于 AsyncTask
,这里有个不错的例子可以参考:
protected Void doInBackground(Void... params) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ... Use the Realm instance ...
} finally {
if (realm != null) {
realm.close();
}
}
return null;
}
如果您需要创建一个包含 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();
}
}
}
}
如果您很幸运地工作在 minSdkVersion >= 19
之下,可以使用try-with-resources
:
try (Realm realm = Realm.getDefaultInstance()) {
// No need to close the Realm instance manually
}
如果 Realm 实例存在于一个带有 Looper 的线程,那么这个 Realm 实例即具有自动更新的功能。这意味这如果发生了 Realm 数据库的变化,那么该 Realm 实例会在下一个事件循环(event loop)中自动更新。这个便捷的功能使您不必花费太多的精力就能保证的UI与数据的实时同步。
如果 Realm 的实例所在线程没有绑定 Looper
,那么该实例不会被更新直到您手动调用 waitForChange()
方法。请注意,不更新 Realm 以保持对旧数据的引用会造成而外的磁盘和内存开销。这也是为什么要在线程结束时调用 close()
关闭 Realm 实例的一个重要原因。
如果您想确定当前 Realm 实例是否有自动更新功能,可以通过调用 isAutoRefresh()
方法查询。
如果您想知道您应用的 Realm 文件的具体路径,请参见这个 StackOverflow上的答案。
其实对于跨线程使用 Realm,您需要知道的事情并不多。关键点是得益于对象和查询的即时更新特性,您不需要担心数据在多线程时的一致性和效率问题。
您可以实时在不同线程中读取和写入 Realm 对象,不用担心其它线程会对同一对象进行操作。您需要在改变对象时使用事务,在另一线程中指向同一对象的数据会被即时更新(更新会在下一次事件循环时进行)。
唯一局限是您不能随意跨线程传递 Realm 对象。如果您在另一线程使用同一对象,请在哪个线程使用查询重新获得该对象。请谨记所有的 Realm 对象都会在不同线程中保持更新——Realm 会在数据改变时通知您。
参考如下实例。
假设我们的应用要展示一个用户列表。我们在一个后台线程中(一个安卓 IntentService)从远端获取新用户并将它们存储到 Realm 中。但后台线程存储新用户时,UI 线程中的数据会被自动更新。UI 线程会通过 RealmChangeListener
得到通知,这时 UI 线程应刷新相应的控件。因为 Realm 的自动更新特性,无需重新查询数据。
// in a Fragment or Activity, etc
@Override
public 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 thread
public 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、RealmObject 和RealmResults 实例都不可以跨线程使用。但是您可以使用异步查询和异步事务来将部分操作放入后台线程进行,待完成时调用线程被通知以获取结果。
当您需要跨线程访问同一部分数据时,只需简单地在该线程重新获取一个 Realm 实例(例如:Realm.getInstance(RealmConfiguration config)
或是其他类似方法),然后通过这个 Realm 实例来查询获得您需要的数据。查询获得的对象会映射到 Realm 中的相同数据,由此方法获得对象在其线程中任何地方都可读写!
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(context)
.modules(new MyModule())
.build();
// It is possible to combine multiple modules to one schema.
RealmConfiguration config = new RealmConfiguration.Builder(context)
.modules(new MyModule(), new MyOtherModule())
.build();
库(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(context)
.name("library.realm")
.modules(new MyLibraryModule())
.build();
// Apps can add the library RealmModule to their own schema.
RealmConfiguration config = new RealmConfiguration.Builder(context)
.name("app.realm")
.modules(Realm.getDefaultModule(), new MyLibraryModule())
.build();
目前您不可以在一个 Realm 文件里声明多个 RealmModule
。如果您有多个 RealmModule
,您需要将它们声明在多个 Realm 文件中以确保每个文件只有一个 RealmModule
声明。
这里有一个如何使用在库和 app 项目间使用 RealmModule
的完整例子。
您可以直接将 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 解析 JSON 时遵循如下规则: * 使用包含空值(null)的 JSON 创建对象: * 对于非必须(可为空值的属性),设置其值为 null
; * 对于必须(不可为空值的属性),抛出异常; * 使用包含空值(null)的 JSON 更新对象: * 对于非必须(可为空值的属性),设置其值为 null
; * 对于必须(不可为空值的属性),抛出异常; * 使用不包含对应属性的 JSON: * 该属性保持不变
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
实例上添加监听器以外,您还可以在 RealmObject
和 RealmResults
实例上添加监听器。你可以通过这样的方式来监视对象和查询结果的改变。另外,当监听回调函数被调用时,相应的数据已经被更新,您不需要去做刷新操作。
public class MyActivity extends Activity {
private Realm realm;
private RealmChangeListener puppiesListener;
private RealmChangeListener dogListener;
private RealmResults puppies;
private Dog dog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
puppiesListener = new RealmChangeListener>() {
@Override
public void onChange(RealmResults 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() {
@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 的数据模型用标准 Java 对象来定义,改变数据模型只需要改变数据对象定义即可。
如果没有旧 Realm 数据文件存在,那么代码的改变即会反应到相应的 Realm 数据文件改变。但如果已经有旧版本的 Realm 数据文件存在,Realm 会抛出异常提示数据库文件需要迁移。请在相应的 RealmConfiguration
设置 schema 版本和 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()
如上示例使得相应的 migration 代码在迁移需要的时候被自动执行。我们提供了相关 API 用来升级已保存的 schema 以及对应之前 schema 的数据。
// 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 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()
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(context)
.encryptionKey(key)
.build();
Realm realm = Realm.getInstance(config);
这保证了所有永久性存储在磁盘上的数据都是通过标准 AES-256 加密的。每次创建新的 Realm 实例的时候,都需要提供相同的密钥。
参考 examples/encryptionExample。这个例子演示了如何通过 Android KeyStore 来安全地存储密钥。
Realm 可以无缝地引入安卓开发。您需要谨记 RealmObject
的线程限制。当你需要跨 activity、service或者 broadcast receiver 传递 Realm 对象的时候,请牢记这一点。
Realm提供了一些抽象的工具类来方便地将 OrderedRealmCollection
(RealmResults
和 RealmList
都实现了这个借口)展示到UI控件上。
RealmBaseAdapter
可以与 ListView
配合使用。参见示例.RealmRecyclerViewAdapter
可以与 RecyclerView
配合使用。参见示例.你需要在 app 的 build.gradle
中添加额外的依赖以使用这些适配器。
dependencies {
compile 'io.realm:android-adapters:1.2.1'
}
因为你不可以直接通过 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
。
当您使用下列 API 时请小心:
AsyncTask
IntentService
AsyncTask
的 doInBackground()
方法会运行在一个后台线程。IntentService
的 onHandleIntent(Intent intent)
方法会运行在一个后台工作线程。
如果您需要在这些方法中使用 Realm,请在对 Realm 的调用结束后关闭 Realm 实例。见如下例子。
在 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.
}
}
在 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 是 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 : '[email protected]' }";
User user = gson.fromJson(json, User.class);
GridViewExample 展示了如何配合 GSON 使用 Realm。
您有时需要序列化与反序列化一个 Realm 对象以便与其它库(比如 Retrofit)相配合。因为 GSON使用成员变量值而非 getter 和 setter ,所以您无法通过 GSON 的一般方法来序列化 Realm 对象。
你需要为 Realm 模型对象自定义一个 JsonSerializer 并且将其注册为一个 TypeAdapter。
请参考这个 Gist。
某些 JSON API 会以数组的形式返回原始数据类型(例如 String 和 integer),Realm 暂时不支持对这种数组的处理。但您可以通过自定义 TypeAdapter来处理这种情况。
这个 Gist 展示了如何将 JSON 中的整型数组存入 Realm。类似地,您可以用这个方法来处理其它原始数据类型数组。
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 用来绑定 JSON 数据和其对应的 Java 类。
Jackson 需要使用发射进行工作。这与 Realm 的 RxJava 支持有冲突,因为作为可选功能,RxJava 有可能不再工程依赖库中。这会导致类似如下的异常抛出:
java.lang.NoClassDefFoundError: rx.Observable
at 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 项目。
Otto 是一个由 Square开发的事件总线(event bus)。Otto 与 Realm 搭配使用非常容易,但请注意时序和并发性的问题。
Otto 通常使用同一个线程来收发事件,这意味着您可以将 RealmObject
作为事件参数并在接收器方法中处理。但您如果使用这个方法让 Otto 将所有事件发送到 UI 线程执行,您就不可以再使用 RealmObject
做为事件参数了(RealmObject
不能跨线程使用)。
当 RealmObject
在一个线程中被改变,Realm 会在另一个线程中通过 Handler 更新 Realm 的数据。而 Otto.post(event)
会立刻调用事件处理。因此,当您发送 Realm 数据改变事件到其它线程,您需要手工调用 realm.refresh()
来得到最新的数据。
@subscribe
public void handleevent(ottoevent event) {
realm.refresh();
// continue working with realm data loaded in this thread
}
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 的时候有如下的限制:
RealmList
,那么您需要注册一个特殊 adapter 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 库可以让您在不使用真实设备或者模拟器的情况下直接在 Java 虚拟机上进行 JUnit 测试。但目前 Roboletrics 不支持带有原生库的测试。而 Realm 包含使用 C++ 的原生库,所以您目前不可以通过 Roboletrics 测试使用 Realm 的项目。
你可以关注这个 Robolectric 的新功能请求。
RxJava 是 Netflix 发布的一个 Reactive 的扩展 库以支持 观察者模式。
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
方法。通过判断 RealmResults
是否已经加载可以得知查询是否已经完成。
参考 RxJava sample project。
RxJava 是可选依赖,这意味着 Realm 不会自动包含它。这样做的好处是您可以选择需要的 RxJava 版本以及防止过多的无用方法被打包。如果您要使用相关功能,请手动添加 RxJava 到 build.gradle
文件。
dependencies {
compile 'io.realm:realm-android:0.90.1'
compile 'io.reactivex:rxjava:1.1.0'
}
您也可以通过继承 RxObservableFactory
来决定 Observable
的生成方式,然后通过 RealmConfiguration
进行配置。
RealmConfiguration config = new RealmConfiguration.Builder(context)
.rxFactory(new MyRxFactory())
.build()
如果没有 RxObservableFactory
被定义,RealmObservableFactory
会被默认使用,它支持 RxJava <= 1.1.*。
参考 unitTestExample
来获得与 JUnit3、JUnit4、Robolectric、Mockito 和 PowerMock 结合使用 Realm 的示例。
Happy hacking!欢迎在realm-java与我们真实存在的人类程序员交流!
当你使用 Android Studio 或者 IntelliJ 调试的时候请留神:调试视图中显示的变量值可能会造成误导。
举个例子,在 Android Studio 中查看一个RealmObject
的所有属性。您会发现这些属性与期望不符。这时因为 Realm 为每个 RealmObject 创建了代理类,通过使用代理类的 getter 和 setter 方法来存取数据,从而原始对象的属性不会被赋值。请参考(更多信息)。请查看访问器的返回以获得正确数据,入下图所示:
上图断点在113行。三个值被观察:person
变量,访问器person.getName()
和 person.getAge()
。代码107行到111行改变了 person
实例的 name 和 age,这些值在事务中被持久化。当运行暂停在113行时,可以观察到,调试器中显示的 person 属性是不正确的,但是访问器person.getName()
和 person.getAge()
的返回正确。请注意,.toString()
方法同样会输出正确的结果。
Realm 包含 native 代码。我们建议您使用崩溃报告工具如 Crashlytics 来记录 native 错误。更多信息意味着我们能帮您更快地解决问题。
默认的 stack trace 信息量较小,不利于解决 NDK 崩溃问题。 Crashlytics 允许您收集更多的崩溃信息。请参考 steps outlined in this guide。
添加如下代码到您工程根目录的 build.gradle
文件。请注意不需要添加 androidNdkOut
和 androidNdkLibsOut
。
crashlytics {
enableNdk true
}
Realm 还处于 beta 阶段,我们一直在不断地修改 bug 及添加功能。在 1.0 版本的发布之前,Realm 存在如下这些限制。
请参阅我们的 GitHub issues 获取更多关于已知问题的信息。
Realm 的目标是在可扩展性和运行效率之间取得一个平衡,因此在保存数据方面有一些客观存在的限制,例如:
class_
前缀,你可以通过 Realm browser 看到;String
和 byte []
大小不能超过 16MB;针对字符串的排序和忽略大小写匹配只支持字符集 ‘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
的顺序并不会被改变。
Realm数据文件本身支持多线程并发访问。但是Realm实例、RealmObject
、RealmQuery
以及RealmResults
不可以跨线程使用。请参考多线程示例获取更多在多线程环境中使用Realm的信息。更多信息。
尽管 Realm 文件支持多线程访问,但还不支持多进程访问。不同进程请使用不同的 Realm 文件拷贝。我们很快会提供对于多进程的支持。
RealmObject
是实时更新的对象,它们有可能因为其它线程对同对象的改动而更新。尽管两个 Realm 对象在调用 RealmObject.equals()
返回 true
时会有相同的哈希值,但它们的哈希值并不是不变的。因此,不应该将它们放入 HashMap
或 HashSet
等依赖于不变哈希值的容器中。
一般来说 Realm 的读写是足够快的,甚至在 UI 线程中读写也不是问题。但是,写事务是互相阻塞的,所以为了避免 ANR 的出现,我们建议你在后台线程中执行写操作。参考异步事务获得如何在后台线程进行写入相关的信息。
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();
}
}
在 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
}
// ...
}
我们特别制作了些特别的 Realm 配方,经常去这里找找看,没准有你需要的。如果你需要这里没有的配方,请在GitHub创建一个问题。
StackOverflow 上的这个答案介绍了怎么找到您的 Realm 文件。您可以通过 Realm Browser 来查看数据内容
大部分情况下,在您 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 从2012年起就已经开始应用在商业生产环境中了。
请注意:我们会针对社区的反馈来调整接口,当然我们也会针对反馈来添加新的功能以及问题修正。
是的!Realm 安卓版本是完全免费的,包括使用在商业项目中。
我们已经通过销售围绕我们的核心技术的产品和支持服务赚钱啦。如果您有额外的需求,欢迎通过邮件与我们取得联系。我们会一如既往地在 Apache 2.0 许可下开发并支持 realm-java 项目,它将永远作为一个开源项目存在。
core
相关的字样,它是什么?core
是我们对于 Realm 的 C++ 存储引擎的称谓。这部分代码目前并没有开源。但基于 Apache 2.0 许可开源这部分代码在我们的计划之中。在我们清理并完善 core
的核心功能之前,这部分的二进制版本发布会基于该许可。
主要区别在于普通 Java 对象本身会包含其数据,但 Realm 对象不会。Realm 对象通过 get 或者 set 方法来直接从 Realm 数据库中存取数据。 也就是说:Realm 对象总体上比普通 Java 对象更轻量;Realm 对象在数据改变的时候会自动更新,而普通 Java 对象不会。
原因是我们需要针对模型类添加一些易用的接口。另外它让我们比较容易在内部使用范型(generic)从而提高代码的易读性和易用性。如果您因为种种原因不想使用继承,您也可以通过实现 RealmModel
接口来定义对象。
RealmProxy
类是做什么用的?我们使用 RealmProxy 类来保证 Realm 对象本身不存储任何实际的数据,进而通过直接访问 Realm 数据来存取数据。
对于您项目中的模型类,Realm 的注解处理器(Annotation processor)会生成相应的 RealmProxy 类。该代理类继承于您的模型类,代理类是您在调用 Realm.createObject()
时实际返回的类型。
Transactions 是为了保证对数据的原子操作。它使得您可以保证多个数据被一次改变,或者保持不变。通过事务您可以精确控制您对每次需要改变多少数据(例如一次改变多个对象)。
基于 SQL 的数据库如 SQLite,插入多条数据通常是被自动隐性包含在事务中一次完成的。但在 Realm 中,写入事务永远是显性的。
Realm 基于我们的原生嵌入式存储引擎。该引擎会在原生内存堆(native
memory)上而不是Java虚拟机的内存堆分配内存。如果您的应用在内存管理上的存在问题导致 Realm 无法分配内存,io.realm.internal.OutOfMemoryError
异常会被抛出。请一定不要忽略这个异常!请不要使用空 catch
语句块来忽略这个异常!这么做有可能导致您的 Realm 数据库文件损坏!在该异常抛出时终止应用程序是不会损坏数据库的。如果您遇到了该异常,请检查应用是否有内存泄漏,或者其它内存使用异常的问题。
一般来说,Realm 数据文件比同等情况下的 SQLite 数据文件更小。
为了您数据的连续性,Realm 会同时保存数据的不同版本。如果您在一个线程中读取了 Realm 数据之后阻塞了该线程,同时又在其它线程中写入了 Realm,那么在第一个线程中的 Realm 不会被更新,并且因此一个非最新的 Realm 数据版本会被保存(这可能并不是您需要的数据)。以上会导致您的 Realm 文件体积增大(当然,Realm 会重用这些额外的磁盘空间占用,或者您可以通过调用 compactRealmFile
来释放这部分磁盘空间)。
Encryption is not supported on this device
异常Realm 的加解密功能曾经依赖于Unix信号(signal),但是部分型号的设备(例如HTC One X)的信号处理(signal handler)工作不正常。从v0.82.2
版本开始,Realm会在打开或创建加密Realm时尝试探测运行其的设备是否存在这个问题。如果探测结果表明该设备无法使用加密Realm,RealmEncryptionNotSupportedException
会被抛出。
从v0.85.0
开始,Realm 从新实现了底层的加解密机制,使其不再依赖 Unix 信号从而能够支持所有设备。因此 RealmEncryptionNotSupportedException
也从该版本起被移除。
Realm 在注解处理器运行的时候会收集匿名数据。我们保证这个过程是完全匿名的。它通过告诉我们你在使用哪个 Realm 版本、你的操作系统是什么帮助我们提高Realm的质量。它不会在你的生产环境中执行,也不会在你的用户设备上执行。它只会在注解处理器运行的时候执行。你可以在我们的源代码中看到它具体收集了哪些信息。
如果你的 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"
}
//...
}