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

作者: 一哥的烦恼 | 来源:发表于2017-01-21 23:00 被阅读1430次

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

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

    正文开始

    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();
                }
            });
    

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

    相关文章

      网友评论

      • RamboMing:查看资料说查询出来的对象或者对象集合,不能删除使用,只能拷贝,新版还是这样吗,上手快,就是不知道坑多少,好不好填
        一哥的烦恼:恩 如果realm close掉的话 realm对象就不能使用了 所以需要将realm对象转换为普通对象,realm.copyFromRealm(object) 再使用。
      • RamboMing:好文,你好,请问支持Rxjava2.0吗
        一哥的烦恼: @好奇无术 目前还没有用过2.0
        ghjjjhghh:现在支持rxjava2.0不
        一哥的烦恼:谢谢 目前我还没有用过Rxjava 2.0 不过2.0是向下兼容的 所以应该是支持的

      本文标题:使用Realm和RxJava打出组合拳的正确姿势

      本文链接:https://www.haomeiwen.com/subject/tovgbttx.html