Realm介绍与引入
其实Realm也已经作为移动端数据库被使用也有一段时间了,而如果作为新手想要把App中数据存储到本地,可能我们File、SP、SQLite,今天要讲的Realm就是代替的SQLite的方法之一,当然还有GreenDao等等,我知道Realm是七月底的时候,各个公众号开始介绍和使用它,而我开始使用它却是从上周才开始的。
我在开始学习Realm的时候就是一口气把官方的文档看完,而现在最新的版本是2.2.1,官方文档也会有中文的比如2.1.1,所以我觉得大家可以一边看英文的一边看中文的,还能锻炼自己的英文阅读能力,毕竟Android官方文档大部分都是英文的,所以在学习新技术的时候就可以看得出英文阅读能力到底有多重要。 下面就开始我们Realm的使用之旅吧!
不过所有这种第三方的开源库使用的第一步都是先添加依赖,然后开始使用。
1.在整个项目的build.gradle下的dependencies添加:
classpath "io.realm:realm-gradle-plugin:2.2.0"
2.在app目录下的build.gradle添加:
apply plugin: 'realm-android'
Realm基础
接下来在使用之前我们可以先来看一些基础概念:
1.比如我们如何定义一个数据模型,我们可以自定义一个类继承RealmObject或者实现RealmModel接口并在类上面添加@RealmClass这个注解,这时我们table就会在运行时自动创建了,代码如下:
public class TestModel extends RealmObject{
...
}
or
@RealmClass
public class OtherTestModel implements RealmModel{
...
}
当我们点进RealmObject时会发现其实它也是实现RealmModel接口的,如图:
2.我们创建了数据模型后还需要添加字段,这样才能真的组成一个table,Realm支持的类型如下:boolean、byte、int、long、float、double(以及他们的封装类)、String、Date、byte[]、RealmObject、RealmList extends RealmObject>,不过成员变量的修饰符并没有限制。
3.一些属性:
- 主键 @PrimaryKey,但是Realm不支持自增长的主键,我一般使用的时候都是定义一个String类型的主键,然后通过UUID保证主键的唯一性(如果封装类作为主键,主键可为null,除非同时被非空修饰)。
- 非空 @Required
- 忽略 @Ignore 如果该类中的某个字段被它修饰了就表明它不会被保存到Realm中。
- 索引 @Index 为字段增加搜索索引,这会导致插入速度变慢,同时数据文件体积有所增加,但能加速查询。
Realm使用
那接下来我们就可以开始使用Realm了,在代码中使用Realm前还需要对它进行初始化Realm.init(上下文对象);我是在全局的Application类中就行的,代码如下
public class MyApplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
mContext = getApplicationContext();
Realm.init(mContext);
}
public static Context getContext() {
return mContext;
}
}
然后再在清单文件中声明一下就可以了
...
1.准备
// 创建实体类 该类必须包含一个无参构造
public class TestModel extends RealmObject{
@PrimaryKey
private String _id;
private String testTitle;
private String updateTime;
...
}
// 如下配置的 Realm 会被存储在 Context.getFilesDir() 并且命名为 default.realm
// 获取Realm实例 获取默认配置的Realm
Realm mRealm = Realm.getDefaultInstance();
// 事务 无论是增删改查都需要开启事务
mRealm.beginTransaction();
// 具体逻辑
...
mRealm .commitTransaction();
// 如果要取消事务的话
mRealm .cancelTransaction();
// 为result设置数据改变侦听 赋值的话我是先调用查询 查询就算没有条目也不会返回null,所以无须担心
mResults.addChangeListener(new RealmChangeListener>() {
@Override
public void onChange(RealmResults element) {
// 这里无需再次赋值给RealmResults, 因为他会自动更新值(必须要当前前程有Looper, 而当前是主线程)
mResults = element;
mAdapter.notifyDataSetChanged();
}
});
2.写入
/**
* 插入假数据 这种方式会产生默认值的对象,然后手动设置值,且如果有主键时需要在createObject中设置 在后台线程进行
*/
private void createItemData() {
// executeTransaction系列方法都会自动管理事务 开启,关闭,取消
mRealm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
UUID uuid = UUID.randomUUID();
// 写入的时候指明了Id
TestModel testModel = realm.createObject(TestModel.class, uuid.toString());
testModel.setTestTitle("点击编辑标题");
testModel.setUpdateTime(getCurrTime());
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
mAdapter.notifyDataSetChanged();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
Log.e("Realm", "保存失败" + error.getMessage());
}
});
}
// 插入的话还有另一种方法copyToRealm();
// 这个Dog对象只是一个普通的对象
Dog dog = new Dog();
dog.setName("Rex");
dog.setAge(1);
mRealm.beginTransaction();
// 而这边返回的是managedDog 才是被持久化的对象
final managedDog = realm.copyToRealm(dog);
mRealm.commitTransaction();
3.查询
// 查询所有 TestModel是我自己写的类
RealmResults mResults = mRealm.where(TestModel.class).findAllAsync();
/**
* 根据标题查询数据 在UI线程进行
*/
private void queryResultByTitle(String title) {
// 根据Title进行模糊查询
mResults = mRealm.where(TestModel.class).contains("testTitle", title) .findAll();
mAdapter.notifyDataSetChanged();
}
// 支持的查询的条件 当然也支持聚合函数
- between()、greaterThan()、lessThan()、greaterThanOrEqualTo() 和 lessThanOrEqualTo()
- equalTo() 和 notEqualTo()
- contains()、beginsWith()和 endsWith() (类似模糊查寻)
- isNull() 和 isNotNull()
- isEmpty() 和 isNotEmpty()
// 每个查询条件都会被被隐式地被逻辑和(&)组合在一起,而逻辑或(or)需要显式地去执行 or() 如下:
RealmResults r = realm.where(User.class)
.greaterThan("age", 10) //implicit AND
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
// 排序
RealmResults result = realm.where(User.class).findAll();
result = result.sort("age"); // 默认正序
result = result.sort("age", Sort.DESCENDING);
4.删除
/**
* 删除数据 在后台线程进行
*/
private void deleteItemData(final int index) {
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
mResults.deleteFromRealm(index);
}
});
}
// 删除的方法还有很多就不一一列举了
5.更新
/**
* 更新数据方式一 通过再次查询 在后台线程进行
*/
private void updateItemDataById(final String title) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
TestModel testModel = realm.where(TestModel.class).equalTo("_id", mCurrEditId).findFirst();
testModel.setUpdateTime(getCurrTime()); testModel.setTestTitle(title);
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
mAdapter.notifyDataSetChanged();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
Log.e("Realm", "保存失败" + error.getMessage());
}
});
}
/**
* 更新数据方法二 通过Realm的特性(实时更新) 在创建RealmResults的线程进行
*/
private void updateItemData(final String title) {
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
mResults.get(mCurrItemIndex).setTestTitle(title);
mResults.get(mCurrItemIndex).setUpdateTime(getCurrTime());
}
});
mAdapter.notifyDataSetChanged();
}
6.数据库配置
// Realm的配置类
RealmConfiguration config = new RealmConfiguration.Builder()
.name("test.realm") // 命名
.schemaVersion(3) // 数据库版本
.migration(new MyMigration()) // 数据库内容发生变化
.build();
// 获取Realm对象并手动设置配置
mRealm = Realm.getInstance(config);
/**
* 当我们更新数据库字段时会使用这个类
*/
public class MyMigration implements RealmMigration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
// 比如当老的版本为3的时候
if (oldVersion == 3) {
schema.get("TestModel")
// 添加新的Realm支持的字段
.addField("testBassType", int.class)
// 添加新的自定义对象
.addRealmObjectField("testRealmObject", schema.get("OtherTestModel"))
// 添加新的List对象,用于一对多 多对多
.addRealmListField("testRealmList", schema.get("OtherTestModel"));
oldVersion++;
}
}
}
// 而我在相应的类中已经写了
// 用于测试数据库字段变更 版本变成4的时候放开下面的字段
// public int testBassType;
// public OtherTestModel testRealmObject;
// public RealmList testRealmList;
7.释放资源
@Override
protected void onDestroy() {
super.onDestroy();
// 移除侦听 并关闭Realm
mResults.removeChangeListeners();
mRealm.close();}
Realm知识点补充
1.异步查询可写回调的侦听,且其回调是通过Looper被执行的(如写入那条)。
2.Realm的实体类必须要包含一个无参构造函数,如果不写任何构造,Java自带一个无参构造,若是自己写了其他参数的构造后无参构造会不可用,此时需要手动头添加一个。
3.主键不支持自增长。
4.使用查询后的所返回的RealmResults<自定义类>对象列表,里面的对象并非是拷贝,而是查询后匹配对象的引用,所以删改都可以直接用它来操作。
5.每个查询条件都会被被隐式地被逻辑和(&)组合在一起,而逻辑或(or)需要显式地去执行 or()。
6.异步操作可以对RealmResults设置addChangeLintener(Callback),侦听结果集发生的变化,记得最后要移除掉侦听。
7.异步查询可用isLoaded()检查是否加载完毕,而同步时该方法永远返回true。
8.Realm、RealmObject 和RealmResults 实例都不可以跨线程使用,而获取后都是获取当前线程的实例,跨线程使用会报错。
9.Realm实例是基于引用计数的,所以你在同一个线程中获取了几次实例,也需要相应的close()几次。
10.如果Realm实例存在于一个带有Looper的线程,那么这个Realm实例就具有自动刷新的功能,这个时候就可以使用,且Listener只工作于 Looper线程。
11.如果Realm实例所在的线程没有Looper,则更新需要你手动调用waitForChange()。
我希望可以站在初学者&自学者的角度把Android中的知识点很清楚的介绍给大家,希望大家喜欢。 如果有错误希望指出来,有问题或者没看懂,都可以来问我的
项目代码的地址:https://github.com/GzwJaaaelu/RealmDemo