继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

从ActiveAndroid到Realm的爬坑之路!

星箜下
关注TA
已关注
手记 7
粉丝 8
获赞 739

公司的项目一直都是使用的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的特点:

  1. 文档完善算是最吸引我的一点了,终于不用使用Google翻译然后边猜边读了,英语差的孩子伤不起。
  2. JSON支持, 能直接读取json并且写入数据库,怎么说呢,ActiveAndroid配合Gson也能实现这一点,但程序员的事,不优雅点怎么行? 一句createObjectFromJson 就搞定一切的感觉还是很美好的。
  3. 其余的还有加密,多线程支持,以及官方说的多进程支持特性,虽然听着很带感,不过暂时还没试过。

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

打开App,阅读手记
4人推荐
发表评论
随时随地看视频慕课网APP