使用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();
}
});
文章都来自个人工作总结和个人感悟,如有不正确的地方,还请不吝赐教,先在此谢过。