使用Realm和RxJava打出组合拳的正确姿势

题外话,这周一直在读一本计算机读物,《浪潮之巅》,还未读完。

这本书讲述的是计算机科学的发展史,其中,也掺杂了作者的很多个人观点。作者语言朴实,文笔恢弘,当读到那些顶级公司的崛起时,我也会感到莫名的振奋,催人向前,产生无数动力;而读到那些鼎鼎大名的科技公司的衰落甚至消亡时,也会感到丝丝的哀痛;科技世界,是一个变化的世界,“以不变应万变”的法则,在这里是绝对行不通的。身处在科技浪潮之中我们也应该明白,下一个科技革命在不远的将来就会来临,我们自当立志站在浪潮之巅,这样才不会被巨大的浪潮湮灭。

正文开始

realm_combine_rxjava.png

先简单介绍下:
Realm:一款用来代替sqlite和 core data的移动端数据库;有了它,移动应用中的数据存储变得轻而易举。
想了解Realm如何使用可以查看官网或者中文站(翻译的非常棒)
RxJava:一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。更多可以阅读扔物线的力作(真的学到了好多东西,应该是好多人的入门教程吧)

我自己在项目中是先用的RxJava,不得不说,实在太好用了,用了它之后可以避免回调噩梦还能够自由的切换工作线程。在项目中用到的地方很多,比如结合Retrofit进行网络接口调用,进行复杂业务逻辑的处理等等。

后来,由于客户端需要在本地存储业务数据,就考虑着要不要用Realm,同时考虑的还有GreenDao。GreenDao以前就用过,现在再次使用当然没有问题,但是作为技术人,有新的更好的技术而不去尝试,而墨守成规,是会落后的。况且,GreenDao确实也有些不方便的地方,比如需要使用Generator进行实体对象和Dao类的生成。

在真正使用Realm作为移动端数据库之前,还是做了很多考察的,第一步就是通读了Realm的官网文档,这样才能了解Realm的全貌,判断适合它使用的应用场景。

总结下来,
Realm有两个优点:

  • 对象就是表,属性就是字段,不需要额外的设置(相比GreenDao更简单)。
  • 相比其他移动端数据库框架,性能更加卓越,存取速度很快。

Realm也有两个缺点:

  • 不支持继承父类对象,所有Realm类都必须继承RealmObject(或者实现RealmModel接口)
  • realm对象只能在创建的线程使用,不允许跨线程访问realm对象。

关于第一点,无法解决,Realm设计之初,就要求所有Realm类都需要继承RealmObject,个人分析,毕竟Realm类还是一个数据表而不是纯粹的Java类,无法像类一样进行继承关系。目前只能每个表中都需要定义自己所有的字段,而无法把共有字段抽象到父类,暂时只能麻烦些。

关于第二点,也无法解决,Realm机制上就是如此,我们也没有办法。而我们又需要进行自由的切换线程,最终找到官方是有对应解决方案的,嗯,对,官网增加了对RxJava support,有了RxJava的强大助力,我们可以自由的玩耍了,但是先不要高兴的太早,这个方案其实有两个缺点。一个是这个方案只支持查询操作,如果我们需要存储操作也享受RxJava带来的种种便利呢?这个方案暂时没有解决方法。这个方案的另一个缺点时,realm的关闭问题,如果查询操作是异步的,我们如果写出这样的代码,

Realm realm = Realm.getDefaultInstance();
realm.where(Person.class)
    .findAllAsync()
    .asObservable()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<List<Person>>() {
        @Override
        public void call(List<Person> persons) {
        Logger.d("query complete");
        mPersons.clear();
        for (Person person : persons) {
            mPersons.add(person);
        }
        mPersonListAdapter.notifyDataSetChanged();
        }
    });
realm.close();

就会报错

Caused by: rx.exceptions.OnErrorNotImplementedException: This Realm instance has already been closed, making it unusable.
at rx.internal.util.InternalObservableUtils$ErrorNotImplementedAction.call(InternalObservableUtils.java:374)
at rx.internal.util.InternalObservableUtils$ErrorNotImplementedAction.call(InternalObservableUtils.java:371)

官方建议呢,将Realm的创建和关闭放在生命周期函数里面,这样做当然就无需考虑Realm的释放问题了,但是我们就需要在使用Realm的地方传入Realm的引用了,这也令人很不爽,毕竟Realm方法是数据库操作,按照架构层次来讲是不应该放View层的,如果可以随时使用随时关闭并且不会报错是最好的了。

为了这个问题,我和我们团队的小伙伴也是苦思冥想,各种尝试,经过不懈的努力,终于在GitHub((__) 嘻嘻……)上发现有位国外大哥fix了一个问题,连Realm官网都刊登了这篇文章,说明Realm官方也是认可这个解决方案的,放心了吧。不说废话了,上链接 Realm Post国外大哥的GitHub。

不过呢,上面的解决方案比较早,由于Realm也一直在更新,上面那个解决方案调用的API有些已经过时了,我自己稍微修改了下,使用起来非常完美,这次和RxJava结合毫无违和感。
代码如下:(也可以点击这里可以通过我的GitHub查看源码。)

package com.example.testingexample.view.rxjava;

/**
 * Created by ZhangYige on 2016/12/8.
 * 解决Realm不能使用RxJava 切换线程的问题
 * from(https://github.com/kboyarshinov/realm-rxjava-example)
 */

import android.support.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.exceptions.RealmException;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;

public abstract class OnSubscribeRealm<T> implements Observable.OnSubscribe<T> {
    private final String fileName;

    private final List<Subscriber<? super T>> subscribers = new ArrayList<>();
    private final AtomicBoolean canceled = new AtomicBoolean();
    private final Object lock = new Object();

    public OnSubscribeRealm() {
        this(null);
    }

    public OnSubscribeRealm(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void call(final Subscriber<? super T> subscriber) {
        synchronized (lock) {
            boolean canceled = this.canceled.get();
            if (!canceled && !subscribers.isEmpty()) {
                subscriber.add(newUnsubscribeAction(subscriber));
                subscribers.add(subscriber);
                return;
            } else if (canceled) {
                return;
            }
        }
        subscriber.add(newUnsubscribeAction(subscriber));
        subscribers.add(subscriber);

        RealmConfiguration.Builder builder = new RealmConfiguration.Builder();
        builder.deleteRealmIfMigrationNeeded();
        if (fileName != null) {
            builder.name(fileName);
        }
        Realm realm = Realm.getInstance(builder.build());
        boolean withError = false;

        T object = null;
        try {
            if (!this.canceled.get()) {
                realm.beginTransaction();
                object = get(realm);
                if (!this.canceled.get()) {
                    realm.commitTransaction();
                } else {
                    realm.cancelTransaction();
                }
            }
        } catch (RuntimeException e) {
            realm.cancelTransaction();
            sendOnError(new RealmException("Error during transaction.", e));
            withError = true;
        } catch (Error e) {
            realm.cancelTransaction();
            sendOnError(e);
            withError = true;
        }
        if (!this.canceled.get() && !withError) {
            sendOnNext(object);
        }

        try {
            realm.close();
        } catch (RealmException ex) {
            sendOnError(ex);
            withError = true;
        }
        if (!withError) {
            sendOnCompleted();
        }
        this.canceled.set(false);
    }

    private void sendOnNext(T object) {
        for (int i = 0; i < subscribers.size(); i++) {
            Subscriber<? super T> subscriber = subscribers.get(i);
            subscriber.onNext(object);
        }
    }

    private void sendOnError(Throwable e) {
        for (int i = 0; i < subscribers.size(); i++) {
            Subscriber<? super T> subscriber = subscribers.get(i);
            subscriber.onError(e);
        }
    }

    private void sendOnCompleted() {
        for (int i = 0; i < subscribers.size(); i++) {
            Subscriber<? super T> subscriber = subscribers.get(i);
            subscriber.onCompleted();
        }
    }

    @NonNull
    private Subscription newUnsubscribeAction(final Subscriber<? super T> subscriber) {
        return Subscriptions.create(new Action0() {
            @Override
            public void call() {
                synchronized (lock) {
                    subscribers.remove(subscriber);
                    if (subscribers.isEmpty()) {
                        canceled.set(true);
                    }
                }
            }
        });
    }

    public abstract T get(Realm realm);
}

package com.example.testingexample.view.rxjava;

/**
 * Created by ZhangYige on 2016/12/8.
 * 解决Realm不能使用RxJava 切换线程的问题
 * from(https://github.com/kboyarshinov/realm-rxjava-example)
 */

import java.util.List;

import io.realm.Realm;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.RealmResults;
import rx.Observable;
import rx.functions.Func1;

public final class RealmObservable {
    private RealmObservable() {
    }

    public static <T extends Object> Observable<T> createObservable(final Func1<Realm, T> function) {
        return Observable.create(new OnSubscribeRealm<T>() {
            @Override
            public T get(Realm realm) {
                T t = function.call(realm);
                if(t!=null){
                    if (t instanceof RealmObject) {
                        return (T) realm.copyFromRealm((RealmObject)t);
                    } else if (t instanceof RealmList) {
                        return (T) realm.copyFromRealm((List<RealmObject>) t);
                    } else if(t instanceof RealmResults){
                        return (T) realm.copyFromRealm((List<RealmObject>) t);
                    }
                    return t;
                }
                return t;
            }
        });
    }
}

使用示例

RealmObservable
        .createObservable(new Func1<Realm, Void>() {
            @Override
            public Void call(Realm realm) {
                Logger.d("realm add start");
                Person person = realm.createObject(Person.class);
                person.setName("zhangsan" + System.currentTimeMillis());
                person.setSex("男");
                person.setAge(24);
                return null;
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                Logger.d("realm add finish");
                //查询人员信息
                queryPersons();
            }
        });

文章都来自个人工作总结和个人感悟,如有不正确的地方,还请不吝赐教,先在此谢过。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容