我正在尝试实现RxJava + Realm + Retrofit + Repository Pattern
这是我的本地实施:
@Override
public Observable<Page> search(@NonNull final String query) {
return Realm.getDefaultInstance().where(Page.class)
.equalTo("query", query)
.findAll()
.asObservable()
.cast(Page.class);
}
这是我的远程实现:
@Override
public Observable<Page> search(@NonNull String query) {
return mWikiServices.search(query).map(new Func1<Result, Page>() {
@Override
public Page call(Result result) {
final List<Page> pages = new ArrayList<>(result.getQuery().getPages().values());
return pages.get(0);
}
});
}
这是我的repo实现:
final Observable<Page> localResult = mSearchLocalDataSource.search(query);
final Observable<Page> remoteResult = mSearchRemoteDataSource.search(query)
.doOnNext(new Action1<Page>() {
@Override
public void call(Page page) {
//mSearchLocalDataSource.save(query, page);
//mResultCache.put(query, page);
}
});
return Observable.concat(localResult, remoteResult)
.first()
.doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
最后这是我在演示者中的订阅。
final Subscription subscription = mSearchRepository.search(this.mQuery)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Page>() {
@Override
public void onCompleted() {
// Completed
}
@Override
public void onError(Throwable e) {
mView.onDefaultMessage(e.getMessage());
}
@Override
public void onNext(Page page) {
mView.onDefaultMessage(page.getContent());
}
});
mCompositeSubscription.add(subscription);
当我运行代码时,我得到此异常:Realm访问来自错误的线程。 Realm对象只能在创建它们的线程上访问。
我在Realm Github repo中尝试过官方解决方案但没有一个能够工作。仍然有这个例外。
我想我得到了这个例外,因为我订阅了一个io线程。 Realm实例在主线程中创建。所以我得到了这个例外。
是否有任何实施优惠?
感谢。
答案 0 :(得分:1)
最后编辑:从技术上讲,两种解决方案都有效,这是Viraj Tank所说的问题 - "safe integration" vs "deep integration"。
然而,正确的深度集成方法是从服务中单独下载,以及监听底层Realm中的更改的订阅者。 (realmResults.asObservable().subscribe()
)。
老实说,我不禁觉得这在概念上存在缺陷。
首先,Realm查询在创建时在主线程上执行
@Override
public Observable<Page> search(@NonNull final String query) {
return Realm.getDefaultInstance().where(Page.class)
创建一个永远不会被关闭的Realm实例。
此外,它将asObservable()
与first()
结合使用,这让我想知道为什么要通过asObservable()
首先在结果中添加更改侦听器,而不是仅仅调用{{1} }}
然后,似乎远程数据源获取并向Realm添加元素并立即显示下载的项目,而不是由Realm通过更改侦听器直接提供的元素,从而提供自动更新。在这种情况下,我不确定Realm正在做什么。
无论如何,我最初的猜测是你可能能够使你的代码使用以下几行
Observable.just(results)
考虑到您似乎并不依赖于Realm的自动更新功能,您可以考虑使用final Observable<Page> localResult = mSearchLocalDataSource.search(query)
.subscribeOn(AndroidSchedulers.mainThread());
final Observable<Page> remoteResult = mSearchRemoteDataSource.search(query)
.subscribeOn(Schedulers.io())
.doOnNext(new Action1<Page>() {
@Override
public void call(Page page) {
//mSearchLocalDataSource.save(query, page);
//mResultCache.put(query, page);
}
});
创建一个可以在线程之间传递的非托管副本。
但实际上,对于正确使用Realm,您应该有两个订阅 - 从网络到Realm的单个数据流;以及realm.copyFromRealm(obj)
的订阅,当网页被网络可观察网页check out Christian Melchior's post写入网页时,会通知您。
Personally, I skipped the Realm observable because the RealmRecyclerViewAdapter
handled it。因此,如果您在RecyclerView中显示多个元素,则甚至不需要Realm observable,因为RealmResults<Page>.asObservable()
通过RealmRecylerViewAdapter
管理其自动更新,而不依赖RealmChangeListener
来执行此操作
修改强>
将the asker's repository分配为https://github.com/Zhuinden/wikilight后,显然我一直都是对的。简单的零拷贝解决方案就是为本地observable添加asObservable()
。
令人惊讶的是,没有太大变化。
subscribeOn(AndroidSchedulers.mainThread())
但公平地说,原始解决方案似乎从图片中删除了自动更新,因此解决方案中没有使用 final Observable<Page> localResult = mSearchLocalDataSource.search(query).filter(new Func1<Page, Boolean>() {
@Override
public Boolean call(Page page) {
return page != null;
}
}).subscribeOn(AndroidSchedulers.mainThread());
final Observable<Page> remoteResult = mSearchRemoteDataSource.search(query).subscribeOn(Schedulers.io())
.doOnNext(new Action1<Page>() {
@Override
public void call(Page page) {
if (page != null) {
mSearchLocalDataSource.save(query, page);
// mResultCache.put(query, page);
}
}
});
return Observable.concat(localResult, remoteResult)
.first()
.map(new Func1<Page, Page>() {
@Override
public Page call(Page page) {
if (page == null) {
throw new NoSuchElementException("No result found!");
}
return page;
}
});
,RealmChangeListener
也没有,所以{{1}确实更有意义。要进行自动更新,请
RealmObject.asObservable()
应该替换为:
copyFromRealm()
最后,一些额外的架构可以使这更好,但我想我会睡觉。
答案 1 :(得分:1)
经过长时间的研究,我找到了解决方案。
首先让我们记住这个问题:当我订阅一个Schedulars.io线程并尝试从领域或改造中获取数据时,我得到“Realm访问来自不正确的线程.Ellm对象只能在它们创建的线程上访问”
在这种情况下的主要问题是我在主线程中创建Realm实例,但尝试从工作线程访问它。
我们可以在主线程上订阅领域,但这不是一个好习惯。当我使用
realm.where("query",query).findFirstAsync().asObservable();
作为Github回购中的一个例子我被困在
Observable.concat(localResult, remoteResult).first();
我的解决方案是什么?
在我们的Repository实现中,我们还有两个远程和本地的可观察对象,如下所示:
final Observable<Page> localResult = mSearchLocalDataSource.search(query);
final Observable<Page> remoteResult = mSearchRemoteDataSource.search(query)
.doOnNext(new Action1<Page>() {
@Override
public void call(Page page) {
if (page != null) {
mSearchLocalDataSource.save(query, page);
mResultCache.put(query, page);
}
}
});
当我们从远程获取数据时,请注意我们保存到数据并缓存在内存中。
如果我们无法从缓存中获取数据,我们仍会连接两个observable。
return Observable.concat(localResult, remoteResult)
.first()
.map(new Func1<Page, Page>() {
@Override
public Page call(Page page) {
if (page == null) {
throw new NoSuchElementException("No result found!");
}
return page;
}
});
使用concat我们尝试从领域获取数据,如果我们不能尝试从远程获取数据。
这是远程可观察的实现:
@Override
public Observable<Page> search(@NonNull String query) {
return mWikiServices.search(query).flatMap(new Func1<Result, Observable<Page>>() {
@Override
public Observable<Page> call(Result result) {
final ArrayList<Page> pages = new ArrayList<>(result.getQuery().getPages().values());
Log.i("data from", "remote");
return Observable.from(pages).first();
}
});
}
这是本地源代码实现:
@Override
public Observable<Page> search(@NonNull final String query) {
return Observable.create(new Observable.OnSubscribe<Page>() {
@Override
public void call(Subscriber<? super Page> subscriber) {
final Realm realm = Realm.getInstance(mRealmConfiguration);
final Page page = realm.where(Page.class)
.equalTo("query", query)
.findFirst();
if (page != null && page.isLoaded() && page.isValid()) {
Log.i("data from", "realm");
subscriber.onNext(realm.copyFromRealm(page));
} else {
Observable.empty();
}
subscriber.onCompleted();
realm.close();
}
});
}
重点是我创建一个新的Observable并从那里获取来自领域的数据。所以我们创建了realm实例并在同一个线程中使用它。 (io线程)。我们创建对象的副本以摆脱非法状态异常。
当我们从realm获取数据时,如果为null,我们返回一个空的observable,不会陷入concat操作。
如果我们获取页面,它是有效的并加载我们发送给订阅者并完成操作。
这里我们如何保存从远程到领域的数据:
@Override
public void save(@NonNull String query, @NonNull Page page) {
final Realm realm = Realm.getInstance(mRealmConfiguration);
realm.beginTransaction();
final Page p = realm.createObject(Page.class);
p.setQuery(query);
p.setId(page.getId());
p.setTitle(page.getTitle());
p.setContent(page.getContent());
realm.copyToRealmOrUpdate(p);
realm.commitTransaction();
realm.close();
}
祝你好运。