Realm 使用说明
Realm
一个跨平台移动数据库引擎
资料
导入
- 配置项目的 build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:2.1.1"
}
}
- 配置模组的 build.gradle
apply plugin: 'realm-android'
混淆
不需要
基本用法
模型
继承RealmObject
,或者实现RealmModel
接口并添加注解@RealmClass
。
public class User extends RealmObject {
// 主键,可为空,默认已索引,String、byte、short、int、long、Byte、Short、Integer、Long
@PrimaryKey
private long id;
// 非空,Boolean、ByteShort、Integer、Long、Float、Double、String、byte[]、Date
@Required
private String name;
// 索引,String、byte、short、int、long、boolean、Date
@Index
private int age;
@Ignore // 忽略
private int tempReference;
private Dog dog; // 对单
private RealmList<Cat> cats; // 对多
// 省略 get/set 方法
}
public class Dog extends RealmObject {
public String name;
}
// 接口+注解,创建的托管对象缺少生成的部分方法,使用 RealmObject 的静态方法替代
@RealmClass
public class Cat implements RealmModel {
public String name;
}
初始化
// Application 中初始化
Realm.init(context);
Realm实例
Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例。
// Context.getFilesDir() 目录下的 default.realm 文件
Realm realm = Realm.getDefaultInstance();
RealmConfiguration config = new RealmConfiguration.Builder().build(); // 默认的 RealmConfiguration
Realm.setDefaultConfiguration(configuration); // 设置默认 RealmConfiguration
// 配合 Configuration 使用
Realm.deleteRealm(configuration); // 清除数据
Realm realm = Realm.getInstance(configuration); // 获取自定义的 Realm
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealm.realm") // 库文件名
.encryptionKey(getKey()) // 加密
.schemaVersion(42) // 版本号
.modules(new MySchemaModule()) // 结构
.migration(new MyMigration()) // 迁移
.build();
// 非持久化的、存在于内存中的 Realm 实例
RealmConfiguration myConfig = new RealmConfiguration.Builder()
.name("myrealm.realm")
.inMemory()
.build();
事务
所有的写操作(添加、修改和删除对象),必须包含在写入事务中,确保线程安全。如果一个写入事务正在进行,那么其他的线程的写入事务就会阻塞它们所在的线程,使用异步事务以避免阻塞
读取事务是隐式的,读操作可在任何时候进行。当写入事务被提交到 Realm 时,该 Realm 的所有其他实例都将被通知,读入隐式事务将自动刷新你每个 Realm 对象。
realm.beginTransaction(); // 开始事务
realm.commitTransaction(); // 提交事务
realm.cancelTransaction(); // 取消事务
// 自动处理写入事务的开始和提交,并在错误发生时取消写入事务
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// ...
}
});
// 异步事务,4种重载,onSuccess 和 onError 不是必须,非 Looper 线程中只有空(null)回调函数被允许使用
RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
// 异步不能使用外部的 Realm
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// 事务成功,Looper 传回前台执行
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// 事务失败,自动取消,Looper 传回前台执行
}
});
// 退出注意取消事务
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
添加
User realmUser = realm.createObject(User.class);
// 有主键需要添加主键,主键无自增
User realmUser = realm.createObject(User.class, primaryKeyValue);
// 普通对象转化为托管对象,建议有主键的bean使用
User user = new User();
User realmUser = realm.copyToRealm(user); // 主键冲突时报异常
User realmUser = realm.copyToRealmOrUpdate(user); // 主键冲突时更新,无主键报异常
删除
final RealmResults<User> results = realm.where(User.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// 删除一个托管对象
results.get(0).deleteFromRealm();
// 使用以下方法,可避免自动更新集合前,某些元素有可能不在集合内,引起的崩溃
results.deleteFromRealm(0);
// 删除集合的首末对象
results.deleteFirstFromRealm();
results.deleteLastFromRealm();
// 删除所有集合内对象
results.deleteAllFromRealm();
// 删除所有
realm.delete(User.class);
realm.deleteAll();
}
});
修改
直接修改托管对象,即修改了数据库。
bean 有主键时,可使用copyToRealmOrUpdate()
转化相同主键的对象为托管来修改数据库。
查询
-
查询条件
between()、greaterThan()、lessThan()、greaterThanOrEqualTo()、lessThanOrEqualTo() equalTo()、notEqualTo() contains()、beginsWith()、endsWith() isNull()、isNotNull() isEmpty()、isNotEmpty()
RealmResults<User> result = realm.where(User.class) .between("age", 0, 99) .findAll(); // 执行查询 User user = realm.where(User.class) .equalTo("name", "John", Case.INSENSITIVE) // 忽略大小写 .findFirst(); // 执行查询
-
关联查询
realmresults<user> users = realm.where(user.class) .equalto("dogs.name", "fluffy") // 关联查询,以“.”分隔 .equalto("dogs.color", "brown") // 条件与 .findall(); realmresults<user> users = realm.where(user.class) .equalto("dogs.name", "fluffy") .findall() .where() // 在结果中继续查询 .equalto("dogs.color", "brown") .findall();
-
逻辑运算符
or()、beginGroup()、endGroup()
RealmResults<User> results = realm.where(User.class) .greaterThan("age", 10) // 大于等于 .beginGroup() // 左括号 .equalTo("name", "Peter") .or() // 或,如果不加此操作符,默认为于 .contains("name", "Jo") .endGroup() // 左右括号 .findAll();
-
排序
RealmResults<User> results = realm.where(User.class).findAll(); results = result.sort("age"); // 升序 results = result.sort("age", Sort.DESCENDING); // 降序 RealmResults<User> results = realm.where(User.class) .findAllSorted("age", Sort.DESCENDING); // 降序
-
聚合
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();
-
异步
步查询需要使用Handler来传递查询结果。在没有 Looper 的线程中使用异步查询会导致 IllegalStateException
异常被抛出。
Listener 只工作于 Looper 线程。对于非 Looper 线程请使用Realm.waitForChange()
private RealmResults<User> results;
public void onStart() {
realm = Realm.getDefaultInstance();
// 立刻返回一个 RealmResults<User>,当其完成时,RealmResults 实例会被更新
results = realm.where(User.class).findAllAsync();
realm.addChangeListener(listener); // Realm 注册监听
results.addChangeListener(listener); // 结果注册监听
if (results.isLoaded()) {
// 完成加载执行
}
results.load(); // 阻塞线程指导异步完成
}
public void onStop () {
realm.removeChangeListener(listener); // Realm移除监听
realm.removeAllChangeListeners(); // Realm移除所有监听
results.removeChangeListener(listener); // 结果移除监听
results.removeChangeListeners(); // 结果移除所有监听
}
private RealmChangeListener listener = new RealmChangeListener<RealmResults<User>>() {
@Override
public void onChange(RealmResults<User> results) {
// 在 Looper 线程,每次更新后执行
// 非 Looper 线程,使用 Realm.waitForChange()
}
};
关闭
Realm 实例是基于引用计数的, 调用getInstance()
获取了几次实例,就需要调用close()
关闭几次
UI 线程外的 Looper 线程
public class MyThread extends Thread {
private Realm realm;
public void run() {
Looper.prepare();
try {
realm = Realm.getDefaultInstance();
//...
Lopper.loop();
} finally {
if (realm != null) {
realm.close();
}
}
}
}
// AsyncTask
protected Void doInBackground(Void... params) {
Realm realm = Realm.getDefaultInstance();
try {
// ...
} finally {
realm.close();
}
return null;
}
new Thread(new Runnable() {
@Override
public void run() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ...
} finally {
if (realm != null) {
realm.close();
}
}
}
}).start();
注意
- 基本数据类型永远不能为空,
RealmObject
数据类型永远可以为空 - 目前不支持
final
、transient
和volatile
修饰的成员变量 - 支持使用递归关系,但要注意死循环的问题, Realm 不会检查
RealmList
的循环嵌套 - 设置一个类型为
RealmList
的属性为空值(null)会清空该列表,即列表长度变为0。但并不会删除列表中的任何RealmObject
- 在没有 Looper 的线程中使用异步查询会导致
IllegalStateException
异常被抛出。
进阶用法
JSON
JSON 包含空值(null)属性,创建更新对象,对象属性不可为空时抛出异常
Json 和对象的属性不同的,对象属性不变
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Dog dog = realm.createObjectFromJson(Dog.class, "{\"name\": \"dog\"}");
}
});
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
try {
InputStream is = getAssets().open("user.json");
realm.createAllFromJson(User.class, is);
} catch (IOException e) {
e.printStackTrace();
}
}
});
DynamicRealm
某些数据模型在编译期是无法获得的。例如在处理数据迁移(migration)或CSV文件的时候,此时使用 DynamicRealm 可以在没有 RealmObject 子类的情况下操作 Realm 数据
RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);
// 创建 DynamicRealmObject 实例
DynamicRealmObject user = realm.createObject("User");
// 通过字符串访问数据,而不是 RealmObject 的定义
String name = person.getString("name");
int age = person.getInt("age");
// DynamicRealm 会忽略 schema、migration 以及 schema 版本的检查,但结构依然存在。获取不存在的属性会报异常
person.getString("I don't exist");
// 查询工作相同
RealmResults<DynamicRealmObject> users = realm.where("User")
.equalTo("name", "John")
.findAll();
schema 结构
- Realm 使用所有项目中的 Realm 模型类来创建 schema。但这个行为是可以改变的,例如,你可以通过使用 RealmModule 让 Realm 只包含所有模型类的一个子集。
@RealmModule(classes = { User.class, Dog.class })
public class MyModule {
}
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule()) // 设置使用的 schema
.build();
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule(), new MyOtherModule()) // 可以设置多个 schema
.build();
- 在库中使用到的 Realm 必须通过 RealmModule 来暴露和使用其 schema。
// 库必须使用 library = true,以阻止默认创建。
// allClasses = true,即为使用所有
@RealmModule(library = true, allClasses = true)
public class MyLibraryModule {
}
// 库需要确切的设置 RealmModule
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
.name("library.realm")
.modules(new MyLibraryModule())
.build();
// Apps 中添加库的 RealmModule
RealmConfiguration config = new RealmConfiguration.Builder()
.name("app.realm")
.modules(Realm.getDefaultModule(), new MyLibraryModule())
.build();
数据库升级
-
不保存旧数据
RealmConfiguration config = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build()
-
数据迁移
RealmConfiguration config = new RealmConfiguration.Builder() .schemaVersion(2) // 结构改变时增加,默认初始值为0 .migration(migration) // 数据迁移方案 .build() RealmMigration migration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { // 动态 Realm 获取数据库结构 RealmSchema schema = realm.getSchema(); // 迁移版本 1: 增加一个类 // Example: // public User extends RealmObject { // private String name; // private int age; // // getters and setters left out for brevity // } if (oldVersion == 0) { schema.create("User") .addField("name", String.class) .addField("age", int.class); oldVersion++; } // 迁移版本 2: 增加一个主键和对象引用 // Example: // public Person extends RealmObject { // @PrimaryKey // private long id; // private String name; // private int age; // private Dog favoriteDog; // private RealmList<Dog> dogs; // // getters and setters left out for brevity // } if (oldVersion == 1) { schema.get("User") .addField("id", long.class, FieldAttribute.PRIMARY_KEY) // 增加主键熟悉“id” .addRealmObjectField("favoriteDog", schema.get("Dog")) // 增加对象 .addRealmListField("dogs", schema.get("Dog")); // 增加对象列表 oldVersion++; } } }
加密
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);
高阶用法
Android相关
-
Adapter
ListView
使用RealmBaseAdapter
,RecyclerViews
使用RealmRecyclerViewAdapter
dependencies { compile 'io.realm:android-adapters:1.4.0' }
-
Intent
RealmObject 不能通过 Intent 传递,可以通过传递属性然后再查询
-
AsyncTask
private class DownloadOrders extends AsyncTask<Void, Void, Long> { @Override protected Long doInBackground(Void... voids) { // 后台子线程,获取使用并关闭 Realm Realm realm = Realm.getDefaultInstance(); try { realm.createAllFromJson(Order.class, api.getNewOrders()); Order firstOrder = realm.where(Order.class).findFirst(); long orderId = firstOrder.getId(); return orderId; } finally { realm.close(); } } @Override protected void onPostExecute(Long orderId) { // 返回主线程,通过id查询对象,进行操作 } }
-
IntentService
ChangeListener 在 IntentService 中不会工作。尽管 IntentService 本身是一个 Looper 线程,但每次 onHandleIntent 的调用是独立的事件。你可以注册监听器的调用不会返回失败,但他们永远不会被触发。
public class OrdersIntentService extends IntentService { public OrdersIntentService(String name) { super("OrdersIntentService"); } @Override protected void onHandleIntent(Intent intent) { // 后台子线程,获取使用并关闭 Realm Realm realm = Realm.getDefaultInstance(); realm.createAllFromJson(Order.class, api.getNewOrders()); Order firstOrder = realm.where(Order.class).findFirst(); long orderId = firstOrder.getId(); realm.close(); } }
Retrofit
GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
// Retrofit 获取的对象转换成 Realm 对象
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();
RxJava
Realm
、RealmResults
、RealmObject
、DynamicRealm
、DynamicRealmObject
可以转化为Observable
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
// 组合 Realm, Retrofit 和 RxJava (使用 Retrolambda),
realm.where(Person.class)
.isNotNull("username")
.findAllAsync()
.asObservable() // 转化为 Observable
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));
Parceler
compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"
// 项目编译完成,RealmObject 转化成 RealmProxy
@Parcel(implementations = { UserRealmProxy.class },
value = Parcel.Serialization.BEAN,
analyze = { User.class })
public class User extends RealmObject {
// ...
}
- 如果你的模型包含 RealmList,那么你需要注册一个特殊 adapter
- 一旦对象被打包(parcelled),它将变为一个有当前数据快照,不再被 Realm 管理的一个 unmanaged 对象。之后该对象的数据变化不会被 Realm 写入。