Android Realm + RxJava - 从不正确的线程访问Realm。 Realm对象只能在创建它们的线程上访问

时间:2016-09-06 20:29:09

标签: android multithreading realm rx-java

我正在尝试实现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实例在主线程中创建。所以我得到了这个例外。

是否有任何实施优惠?

感谢。

2 个答案:

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

这是示例源代码。 https://github.com/savepopulation/wikilight

祝你好运。