Realm在使用之前需要调用初始化:
Realm.init(context);
建议把它放在Application的onCreate()
里.
配置
配置类: RealmConfiguration
定义了Realm的创建配置.
最基本的配置:
RealmConfiguration config = new RealmConfiguration.Builder().build();
它会创建一个叫default.realm
的文件, 放在Context.getFilesDir()
的目录下.
如果我们想自定义一个配置, 可以这样写:
// 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 config Realm realm = Realm.getInstance(config);
所以我们是可以有多个配置, 访问多个Realm实例的.
我们可以把配置设置为默认配置:
Realm.init(this); RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.setDefaultConfiguration(config);
之后用Realm.getDefaultInstance()
取到的就是这个默认配置对应的实例.
数据库迁移
迁移的策略是通过config指定的:
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()
其中MyMigration
实现了RealmMigration
接口, 在migrate()
方法中根据新旧版本号进行一步一步地升级.
具体例子见Migration.
开发的时候为了方便我用的是.deleteRealmIfMigrationNeeded()
, 这样在需要数据库迁移的时候直接就删了数据重新开始了.
关于Realm的close()
一个打开的Realm实例会持有一些资源, 有一些是Java不能自动管理的, 所以就需要打开实例的代码负责在不需要的时候将其关闭.
Realm的instance是引用计数的(reference counted cache), 在同一个线程中获取后续实例是免费的, 但是底层的资源只有当所有实例被释放了之后才能释放. 也即你调用了多少次getInstance()
, 就需要调用相应次数的close()
方法.
比较建议的方法是在Activity或Fragment的生命周期中处理Realm实例的开启和释放:
-
在Activity的
onCreate()
中getInstance()
,onDestroy()
中close()
. -
在Fragment的
onCreateView()
中getInstance()
,onDestroyView()
中close()
.
如果多个Fragment相关的都是同一个数据库实例, 那么在Activity中处理更好一些.
写
写操作一般的流程是这样:
// Obtain a Realm instance Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); //… add or update objects here … realm.commitTransaction();
这里创建对象可以用createObject()
方法或者copyToRealm()
方法.
前者是先创建再set值, 后者是先new对象再更新数据库.
如果不想自己处理beginTransaction()
, cancelTransaction()
和commitTransaction()
, 可以直接调用realm.executeTransaction()
方法:
realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { User user = realm.createObject(User.class); user.setName(“John”); user.setEmail(“john@corporation.com”); } });
异步
因为transactions之间是互相阻塞的.
异步执行可以用这个方法:
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. } });
这两个回调是Optional的, 它们只能在有Looper的线程调用.
注意: 这个方法的返回值对象可以用于在Activity/Fragment生命周期结束的时候取消未完的操作.
删除和更新
所有的写操作都要放在transaction中进行, 如上, 不同的操作只是其中具体方法不同.
删除操作:
final RealmResults users = getUsers(); // method 1: users.get(0).deleteFromRealm(); // method 2: users.deleteFromRealm(0); // delete all users.deleteAllFromRealm();
更新操作:
realm.copyToRealmOrUpdate(obj);
注意: 这个方法需要Model有主键, 会更新obj的主键对应的对象, 如果不存在则新建对象.
查询
–
查询可以流式地写:
// Or alternatively do the same all at once (the “Fluent interface”): RealmResults result2 = realm.where(User.class) .equalTo(“name”, “John”) .or() .equalTo(“name”, “Peter”) .findAll();
查询条件默认是and的关系, or则需要显式指定.
这个RealmResults
是继承Java的AbstractList
的, 是有序的集合, 可以通过索引访问.
RealmResults
永远不会为null, 当查不到结果时, 它的size()
返回0.
查询的线程
基本上所有的查询都是很快进行的, 足够在UI线程上同步进行.
所以绝大多数情况在UI线程上使用findAll()
是没有问题的.
如果你要进行非常复杂的查询, 或者你的查询是在非常大的数据集上进行的, 你可以选择异步查询, 使用findAllAsync()
.
查询条件是一个集合 -> in()
如果想要查询的某一个字段的值是在一个集合中, 比如我有一个id的集合, 我现在想把id在这个集合中的项目全都查出来, 这就可以使用in操作符:
RealmResults toDeleteLists = realm.where(TodoList.class).in(“id”, ids).findAll();
链式查询
查询的时候可以利用link或关系来查询, 比如一个Person类中含有一个RealmList<Dog> dogs
的字段.
查询的时候可以这样:
RealmResults persons = realm.where(Person.class) .equalTo(“dogs.color”, “Brown”) .findAll();
利用字段名dogs.
来查询一个dog的属性, 再查出拥有这种特定属性dog的人.
但是反向地, 我们能不能查询主人是满足特定属性的人的所有dogs呢? 目前(2017.2.17)这种查询仍是不支持的. 这里有讨论: realm-java-issue-607.
所以两种解决办法: 一是做两次查询; 二是在Dog类的model里加入对Person的引用.
Notifications
可以添加一个listener, 在数据改变的时候收到更新.
public class MyActivity extends Activity { private Realm realm; private RealmChangeListener realmListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); realm = Realm.getDefaultInstance(); realmListener = new RealmChangeListener() { @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(); } }
注意listener需要在不用的时候删除掉.
可以用这样删除所有的listeners:
realm.removeAllChangeListeners();
Listener不一定要和Realm绑定, 也可以和具体的RealmObject
或者RealmResults
绑定.
当Listener被调用的时候, 它绑定的对象是自动更新的, 不需要手动刷新.
查看数据库的工具
用Stetho不能直接查看Realm的数据库, 看不到.
需要用这个工具配置一下: stetho-realm.
之后就可以在浏览器中查看Realm的数据库了.
(但是感觉这个工具不是很好用, 有时候不显示数据, 有时候显示的是旧数据.)
也可以用官方提供的Realm Browser来查看, 但是只有Mac版.
如何查看看这里: StackOverflow answer.
实际使用的感想和遇到的问题
优点
-
建立Model之间的关系很方便也很直接, 查询的时候自动关联了其中的关系.
-
自动更新(Auto-Updating)的特性很有用, 不用再关心数据的刷新, 只用关心UI的刷新.
比如一旦给Adapter绑定了数据, 之后的数据更新只需要在onChange()里面通知Adapter调用notifyDataSetChanged()
即可.
当然我并没有用RealmBaseAdapter
和RealmRecyclerViewAdapter
, 估计这两个更好用, 官方有例子, 这里不再赘述.
缺点
这里有的也不能说是缺点, 只是使用起来觉得不方便的地方.
- 限制了创建对象和操作对象必须在同一个线程.
违反了这条会报错: java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
比如我们在UI线程查询出来的对象, 想要异步地删除或者更新, 我们必须在新的线程重新查询.
-
没有主键自增的功能, 见Issue #469, 需要自己控制主键自增.
-
从List中删除了一项之后, 最后的一项会移动过来补到被删除的那一项原来的位置. 这是因为人家就是这么设计的stackoverflow. 默认情况下是没有排序的, 数据按照添加的顺序返回, 但是这并不是一种保证, 所以当删除了中间的元素, 后面的会补上这个位置, 以保证底层的数据是放在一起的. 解决办法就是指定一个排序规则.
-
查询出来的对象不可以临时改变其数据, 否则会报错:
java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
-
不支持反向link的查询. (见前面链式查询部分的介绍).
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/b9ae4f0a959b3b7fe987ac48b57dd4f7.jpeg)
题外话
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-Rpa3aJUL-1712509857712)]
希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!