阅读过Realm文档的童鞋们应该都知道Realm、RealmObject 和RealmResults 实例都是不可以跨线程使用的。
虽然Realm查询数据的速度非常快,但有些时候我们还是不得不用上异步查询。
在Realm中,从子线程查询到的数据到主线程中是不可以使用的,会报Realm accessed from incorrect thread.
所以我们如果希望通过我们熟悉的方式去创建异步任务,就需要在 子线程创建Realm实例。并且在子线程中将查询到的数据复制一份(普通RealmObject对象,非代理对象)出来。
最开始没有仔细查看Realm的api,自己写了一个CopyUtil,通过反射将代理对象复制成普通对象,效率比较低,后来优化之后好了一点,不过仍然较慢。最近发现realm API自带了copyFromRealm方法将代理对象复制成普通对象,而且非常快。
接下来说说Realm异步查询的三种方式。
第一种结合RxJava,不用自带的asObservable()创建。
//Dao代码:
public Observable<List<SqlCompany>> findAllRx() {
return Observable.create(new Observable.OnSubscribe<List<SqlCompany>>() {
@Override
public void call(Subscriber<? super List<SqlCompany>> subscriber) {
try {
Realm realm = Realm.getDefaultInstance();
RealmResults<SqlCompany> results = realm.where(SqlCompany.class).findAll();
List<SqlCompany> list = realm.copyFromRealm(results);
subscriber.onNext(list);
subscriber.onCompleted();
realm.close();
} catch (Exception e) {
e.printStackTrace();
subscriber.onError(e);
}
}
});
}
//使用
public void uiSearch() {
SqlCompanyDao dao = new SqlCompanyDao();
dao.findAllRx()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<SqlCompany>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List<SqlCompany> sqlCompanies) {
Log.d("SqlCompanyDao", "sqlCompanies:" + sqlCompanies);
}
});
}
这种代码比较多,看着比较累~这是最原始的结合Rxjava的方式。
第二种,对以上过程进行封装,不用每次创建新的realm对象,不用每次try catch 和 复制对象。
先贴封装好的工具类,国外牛人封装的,我只是稍微修改了一下。
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);
Realm realm = createRealm();
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);
// TODO: 2017/7/6 根据需求创建Realm实例
private Realm createRealm() {
return Realm.getDefaultInstance();
}
}
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;
}
});
}
}
接下来看看如何使用:
//Dao代码:
public void findAll(Subscriber<List<SqlCompany>> subscribe) {
RealmObservable
.createObservable(new Func1<Realm, List<SqlCompany>>() {
@Override
public List<SqlCompany> call(Realm realm) {
return realm.where(SqlCompany.class).findAll();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscribe);
}
//使用:
public void uiSearch2() {
SqlCompanyDao dao = new SqlCompanyDao();
dao.findAll(new Subscriber<List<SqlCompany>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List<SqlCompany> sqlCompanies) {
Log.d("SqlCompanyDao", "sqlCompanies:" + sqlCompanies);
}
});
}
第三种是使用Realm自己的异步查询:
//Dao代码
//异步获取数据,tip:一定要将结果集声明为全局变量,否则可能会被GC,导致listener不被调用。
RealmResults<SqlCompany> results;
public void findAll(@NonNull RealmChangeListener<RealmResults<SqlCompany>> listener) {
Realm realm = Realm.getDefaultInstance();
results = realm.where(SqlCompany.class)
.findAllAsync();
results.addChangeListener(listener);
}
//使用
public void uiSearch3() {
SqlCompanyDao dao = new SqlCompanyDao();
RealmChangeListener<RealmResults<SqlCompany>> changeListener = new RealmChangeListener<RealmResults<SqlCompany>>() {
@Override
public void onChange(RealmResults<SqlCompany> element) {
Log.d("SqlCompanyDao", "element:" + element);
element.removeChangeListener(this);
}
};
dao.findAll(changeListener);
}
这种异步查询方式最简单,且不需要复制对象,所以查出来的对象是代理对象,realm关闭后就不能使用了,且不能直接修改值。
这种方式有一点点小问题,有时候回调执行多次,官方文档显示,数据更新都会执行onChange,如果removeChangeListener,有时候会导致回调不执行。
总结,这三种异步查询,查询速度基本差不多。第二种相对来说最好。
realm也可以结合异步任务,或者其他异步框架来完成异步查询,不过一定要记住在使用线程中创建realm实例,并且要将代理对象复制成普通对象,因为代理对象是不能跨线程使用的。
Tip:
public void uiSearch4() {
Realm realm = Realm.getDefaultInstance();
realm.where(SqlCompany.class)
.findAllAsync()
.asObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DaoSubscribe<RealmResults<SqlCompany>>() {
@Override
public void onNext(RealmResults<SqlCompany> sqlCompanies) {
Log.d("SqlCompanyDao", "sqlCompanies:" + sqlCompanies);
}
});
}
直接使用以上代码进行异步查询是不行的,因为直接查出来的对象没办法跨线程使用,以上代码不存在复制对象的过程,所以会报错。
在实际使用中不要频繁通过Realm realm = Realm.getDefaultInstance();
来创建Realm对象,如果只需要同步查询,最好保持app中同一时刻仅有一个realm对象存在,让realm随着app的生命周期创建并释放,或者是使用完,并且复制对象之后立刻关闭realm。
文章内容如有错误欢迎指正,对本文内容有任何疑问欢迎加群讨论:283272067