公司的项目一直都是使用的ActiveAndroid,这是一个很传统的ORM框架,不过无论是与Gson的配合使用,还是连接查询或者分页查询,传统数据库的理念它都能支持的很好。无奈的是这个项目在GitHub上的最近更新时间还是在两年前,它对AndroidStudio的InstantRun特性支持的不是很好(编译时间就是程序员的生命啊,感谢Google推出的这一神器 )。强行使用的话可能会报如下错误:
第二点是它的版本升级策略做的不好,不能做到跨版本升级,目前的做法只能是升级APP的后如果检测到先前的版本就卸载然后重装,原先的数据只能丢失了。这样简单粗暴的做法自然不是长久之计。
第三点是不能查询个别列,将所有字段都查出来既浪费时间,又浪费空间。
ActiveAndroid的罪过数落完了,自然要把更换数据库的Task提上日程。leader给新的数据库提了五点要求:
- 效率高,支持事务。
- 支持个别列查询。
- 升级方便,能跨版本升级。
- 支持联表操作。
- 逐步替换,替换过程中能随时切回原来的ORM。
最难的一点是更换只能在Service层进行(项目中所有的数据库操作都是通过一个一个Service完成的),不能对项目进行大量修改这一限制,注定了使用Realm会遇到一个又一个的坑。
Realm确实是一个非常前沿的数据库框架,跟其它ORM的千遍一律不同,它提供了强大的功能和可靠的速度,它的优点可以作如下总结:
- 效率高,因为是自己实现的C++数据库,与SQLite底层使用java不同,它天生就比其高效快速。维护Realm数据库还能带来平台无关性,iOS、Android等平台都可以共享数据库。
- Auto-Refresh,Realm为每个实体创建了代理类,并且实现了其Getter/Setter,因此,每次得到或者修改实体的值,都是直接对底层数据库进行的操作,官方将其称为"懒加载"。但是很好奇它是怎么做到在下个Looper事件中自动更新的,因此进入源码查看了一番:
要想自动更新,那肯定需要在线程中创建Handler,首先找到Realm.getInstance(),是不是在Realm初始化的时候绑定Handler到当前线程:
public static Realm getDefaultInstance() {
if (defaultConfiguration == null) {
throw new NullPointerException("No default RealmConfiguration was found. Call setDefaultConfiguration() first");
}
return RealmCache.createRealmOrGetFromCache(defaultConfiguration, Realm.class);
}
这里可以看到,每个线程只能拥有一个Realm实例,再点进createRealmOrGetFromCache():
static synchronized <E extends BaseRealm> E createRealmOrGetFromCache(RealmConfiguration configuration,Class<E> realmClass) {
······
//这个类保存了Realm 和当前线程使用getInstance()的计数器,以及全局计数器。
RefAndCount refAndCount = cache.refAndCountMap.get(RealmCacheType.valueOf(realmClass));
if (refAndCount.localRealm.get() == null) {
// Create a new local Realm instance
BaseRealm realm;
if (realmClass == Realm.class) {
// RealmMigrationNeededException might be thrown here.
//在这里创建了新对象
realm = Realm.createInstance(configuration, cache.typedColumnIndices);
} else if (realmClass == DynamicRealm.class) {
realm = DynamicRealm.createInstance(configuration);
} else {
throw new IllegalArgumentException(WRONG_REALM_CLASS_MESSAGE);
}
// The cache is not in the map yet. Add it to the map after the Realm instance created successfully.
if (!isCacheInMap) {
cachesMap.put(configuration.getPath(), cache);
}
//创建一个Realm实例后,保存并将计数器置零。
refAndCount.localRealm.set(realm);
refAndCount.localCount.set(0);
}
Integer refCount = refAndCount.localCount.get();
if (refCount == 0) {
...
//全局计数器加1
refAndCount.globalCount++;
}
//线程计数器加1
refAndCount.localCount.set(refCount + 1);
@SuppressWarnings("unchecked")
E realm = (E) refAndCount.localRealm.get();
return realm;
所以在一个线程中可以多次使用getInstance(),并不会影响性能,但是从close() 方法的源码来看,调用一次close(),也只是将计数器减一,只有计数器归零,才会执行回收工作。也就是说,同一线程,用了几次getInstance 就要调几次 close ,否则就会内存泄漏,而且泄漏的是c++底层的那部分,这设计的就比较尴尬了。
接着走,刚才看到,对于第一次调用getInstance,会调用createInstance, 那跳进这个方法看看:
static Realm createInstance(RealmConfiguration configuration, ColumnIndices columnIndices) {
try {
return createAndValidate(configuration, columnIndices);
} catch (RealmMigrationNeededException e) {
if (configuration.shouldDeleteRealmIfMigrationNeeded()) {
deleteRealm(configuration);
} else {
try {
migrateRealm(configuration);
} catch (FileNotFoundException fileNotFoundException) {
// Should never happen
throw new RealmIOException(fileNotFoundException);
}
}
return createAndValidate(configuration, columnIndices);
}
}
并没有什么有用的信息,只是抛出版本不匹配异常时,会根据flag删除或更新以前的Realm,关键还是createAndValidate() :
static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices columnIndices) {
Realm realm = new Realm(configuration);
...
return realm;
}
只需要关注这两句,调用了Realm的构造函数,而它的构造函数什么都没做,只是调用了它的父类BaseRealm的构造:
protected BaseRealm(RealmConfiguration configuration) {
//终于发现了线程相关的东西和AutoRefresh
this.threadId = Thread.currentThread().getId();
...
//handlerController 就是Handler.Callback
this.handlerController = new HandlerController(this);
if (handlerController.isAutoRefreshAvailable()) {
setAutoRefresh(true);
}
}
public void setAutoRefresh(boolean autoRefresh) {
checkIfValid();
handlerController.checkCanBeAutoRefreshed();
if (autoRefresh && !handlerController.isAutoRefreshEnabled()) { // Switch it on
//这里创建了Handler,与当前线程绑定到了一起。而AutoRefresh就是在handlerController中实现的。
handler = new Handler(handlerController);
handlers.put(handler, configuration.getPath());
} else if (!autoRefresh && handlerController.isAutoRefreshEnabled() && handler != null) { // Switch it off
removeHandler();
}
handlerController.setAutoRefresh(autoRefresh);
}
至此,源码的探究应该告一段落了,接着列举一些Realm的特点:
- 文档完善算是最吸引我的一点了,终于不用使用Google翻译然后边猜边读了,英语差的孩子伤不起。
- JSON支持, 能直接读取json并且写入数据库,怎么说呢,ActiveAndroid配合Gson也能实现这一点,但程序员的事,不优雅点怎么行? 一句createObjectFromJson 就搞定一切的感觉还是很美好的。
- 其余的还有加密,多线程支持,以及官方说的多进程支持特性,虽然听着很带感,不过暂时还没试过。
总之,一个项目若是处于设计初期,我觉得使用Realm还是很方便的,毕竟,老技术虽然稳定,但总会有各种无法解决的缺陷,而新技术,正是为了解决这些缺陷而来的。