领域、RxJava、asObservable() 和 doOnUnsubscribe()

Realm, RxJava, asObservable() and doOnUnsubscribe()

在我的 Android 项目中,我使用 realm 作为我的数据存储引擎。我喜欢它!
我还使用 RxJava 因为它使“线程化”变得容易得多,而且我真的很喜欢整个“反应式思维”。我喜欢它!

我使用 MVP 模式 + 一些“清洁架构”的想法来构建我的应用程序。

我的 Interactors 是唯一知道 Realm 的人。我借助 Observable 公开数据,如下所示:

@Override
public Observable<City> getHomeTown() {
    final Realm realm = Realm.getDefaultInstance();
    return realm.where(City.class).equalTo("name", "Cluj-Napoca").findAllAsync().asObservable()
            .doOnUnsubscribe(new Action0() {
                @Override
                public void call() {
                    realm.close();
                }
            })
            .compose(new NullIfNoRealmObject<City>());
}

问题是我的 doOnUnsubscribe 副作用在 Realm 可以处理暴露的可观察对象之前被调用:

Caused by: java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:344)
at io.realm.RealmResults.removeChangeListener(RealmResults.java:818)
at io.realm.rx.RealmObservableFactory.call(RealmObservableFactory.java:137)
at rx.subscriptions.BooleanSubscription.unsubscribe(BooleanSubscription.java:71)
at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
at rx.Subscriber.unsubscribe(Subscriber.java:98)
at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
at rx.Subscriber.unsubscribe(Subscriber.java:98)
at rx.subscriptions.CompositeSubscription.unsubscribeFromAll(CompositeSubscription.java:150)
at rx.subscriptions.CompositeSubscription.unsubscribe(CompositeSubscription.java:139)
at ro.tudorluca.realm.sandbox.city.CityPresenter.onDestroy(CityPresenter.java:62)
at ro.tudorluca.realm.sandbox.city.CityActivity.onDestroy(CityActivity.java:35)

我为此用例创建了一个 sandbox 项目。

我真的很喜欢使用 Realm+RxJava,但是当我 unsubscribe 时,我似乎找不到 close Realm 实例的干净解决方案(我通常在 activity 被摧毁)。有什么想法吗?

编辑 1https://github.com/realm/realm-java/issues/2357
编辑 2:感谢非常活跃的领域团队,已经有一个 pull request 来解决这个问题。

既然你说只有 Interactor "knows" 关于 Realm 框架,我会说甚至 return 一个托管的 Realm 对象,而不是 return 使用 copyFromRealm 的结果的非托管副本。这样你就不必关心在 Presenter.

中打开或关闭的 Realm 实例

与此同时,我会让 Presenter 选择调用是否应该异步进行,因为 RxJava 这样做很酷很容易,你不会'在另一个线程中调用 Interactor load 方法时没有问题(使用 Loopers 可以避免这种情况,但如果可以使情况更简单,为什么要使情况过于复杂 :P )。

所以我会选择:

Override
public Observable<City> getHomeTown() {
    final Realm realm = Realm.getDefaultInstance();
    City city = realm.where(City.class).equalTo("name", "Cluj-Napoca").findFirst();

    // make sure we don't send back Realm stuff, this is a deep copy that will copy all referenced objects (as the method doc says)
    City cityUnmanaged = realm.copyFromRealm(city);

    // safe to close the realm instance now
    realm.close();

    return Observable.just(cityUnmanaged);
}

我很想看到更多选项:)。

21 小时后,这就是我想出的:

@Override
public Observable<City> getHomeTown() {
    return getManagedRealm()
            .concatMap(new Func1<Realm, Observable<City>>() {
                @Override
                public Observable<City> call(Realm realm) {
                    return realm.where(City.class).equalTo("name", "Cluj-Napoca").findAllAsync().asObservable()
                            .compose(new NullIfNoRealmObject<City>());
                }
            });
}

private static Observable<Realm> getManagedRealm() {
    return Observable.create(new Observable.OnSubscribe<Realm>() {
        @Override
        public void call(final Subscriber<? super Realm> subscriber) {
            final Realm realm = Realm.getDefaultInstance();
            subscriber.add(Subscriptions.create(new Action0() {
                @Override
                public void call() {
                    realm.close();
                }
            }));
            subscriber.onNext(realm);
        }
    });
}

我在 Whosebug 上发布问题之前尝试过类似的操作,但我的错误是使用了 flatMap(),而不是 concatMap()

flatMap() 不同,concatMap() 将保持排放顺序,在我的例子中,这意味着我的 Action0 -> realm.close() 将是取消订阅流后调用的最后一个操作,在导致问题的 Realm Action0 -> results.removeChangeListener(listener) 之后。

可以在 github 上找到完整的示例。

编辑:感谢非常活跃的领域团队,已经有一个pull request来解决这个问题。

按照我的说法,在一个好的架构中要注意的主要事情之一就是模块化。所有主要模块(或库)都应与其余代码隔离。由于 Realm、RealmObject 或 RealmResult 不能跨线程传递,因此将 Realm 和 Realm 相关操作与其余代码隔离就显得尤为重要。

牢记这一理念,我想出了以下方法。

对于每个 jsonModel class,我们创建一个 realmModel class 和一个 DAO(数据访问对象)class。这里的想法是 class 的 DAO class none 必须知道或访问 realmModel 或 Realm 实体。 DAO class 获取 jsonModel,将其转换为 realmModel,执行 read/write/edit/remove 操作和读取操作 DAO 将结果 realmModel 转换为 jsonModel 和 returns 它们。

这种方式很容易维护Realm,避免所有与线程相关的问题,易于测试和调试。

这是一篇关于具有良好架构的 Realm 最佳实践的文章https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f

还有一个示例项目,演示了 Android 上的 Realm 与 MVP(模型视图展示器)、RxJava、Retrofit、Dagger、注释和测试的集成。 https://github.com/viraj49/Realm_android-injection-rx-test