从ActiveAndroid到Realm的爬坑之路(一)

来源:互联网 发布:正规淘宝客服兼职平台 编辑:程序博客网 时间:2024/06/05 00:59

公司的项目一直都是使用的ActiveAndroid,这是一个很传统的ORM框架,不过无论是与Gson的配合使用,还是连接查询或者分页查询,传统数据库的理念它都能支持的很好。无奈的是这个项目在GitHub上的最近更新时间还是在两年前,它对AndroidStudio的InstantRun特性支持的不是很好(编译时间就是程序员的生命啊,感谢Google推出的这一神器 )。强行使用的话可能会报如下错误:

第二点是它的版本升级策略做的不好,不能做到跨版本升级,目前的做法只能是升级APP的后如果检测到先前的版本就卸载然后重装,原先的数据只能丢失了。这样简单粗暴的做法自然不是长久之计。
第三点是不能查询个别列,将所有字段都查出来既浪费时间,又浪费空间。


ActiveAndroid的罪过数落完了,自然要把更换数据库的Task提上日程。leader给新的数据库提了五点要求:
1. 效率高,支持事务。
2. 支持个别列查询。
3. 升级方便,能跨版本升级。
4. 支持联表操作。
5. 逐步替换,替换过程中能随时切回原来的ORM。

最难的一点是更换只能在Service层进行(项目中所有的数据库操作都是通过一个一个Service完成的),不能对项目进行大量修改这一限制,注定了使用Realm会遇到一个又一个的坑。

Realm确实是一个非常前沿的数据库框架,跟其它ORM的千遍一律不同,它提供了强大的功能和可靠的速度,它的优点可以作如下总结:

  1. 效率高,因为是自己实现的C++数据库,与SQLite底层使用java不同,它天生就比其高效快速。维护Realm数据库还能带来平台无关性,iOS、Android等平台都可以共享数据库。
  2. 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的特点:
3. 文档完善算是最吸引我的一点了,终于不用使用Google翻译然后边猜边读了,英语差的孩子伤不起。
4. JSON支持, 能直接读取json并且写入数据库,怎么说呢,ActiveAndroid配合Gson也能实现这一点,但程序员的事,不优雅点怎么行? 一句createObjectFromJson 就搞定一切的感觉还是很美好的。
5. 其余的还有加密,多线程支持,以及官方说的多进程支持特性,虽然听着很带感,不过暂时还没试过。

总之,一个项目若是处于设计初期,我觉得使用Realm还是很方便的,毕竟,老技术虽然稳定,但总会有各种无法解决的缺陷,而新技术,正是为了解决这些缺陷而来的。

0 0
原创粉丝点击