【问题标题】:Android: Realm.getInstance(context) returns an already closed realm instanceAndroid:Realm.getInstance(context) 返回一个已经关闭的领域实例
【发布时间】:2015-06-03 19:50:07
【问题描述】:

Realm.getInstance(context) 很少会返回已经关闭的领域实例。这怎么可能?

根据https://realm.io/news/using-realm-with-rxjava/,我将 Realm 与 RxJava 一起使用

特别是,这个方法会抛出一个IllegalStateException: This Realm instance has already been closed, making it unusable.

@Override
    public void call(final Subscriber<? super RealmList<T>> subscriber) {
        final Realm realm = Realm.getInstance(context);
        subscriber.add(Subscriptions.create(new Action0() {
            @Override
            public void call() {
                try {
                    realm.close();
                } catch (RealmException ex) {
                    subscriber.onError(ex);
                }
            }
        }));

        RealmList<T> object;
        realm.beginTransaction(); //THROWS EXCEPTION

        //...
}

如果我注释掉 realm.close(); 问题,没问题。虽然我认为这会导致本机内存泄漏。

对于为什么会发生这种情况,我最好的猜测是对该方法进行了多次调用,如果这些方法调用完美排列,那么可以检索已经关闭的领域实例?

编辑:通过使用Schedulers.io(),我收到很多Calling close() on a Realm that is already closed 警告。我的猜测是,在我完成使用.io() 线程之后,领域实例会自动关闭。不知道为什么会发生这种情况。

EDIT2:通过切换到使用 Schedulers.newThread() 而不是 Schedulers.io() 作为我的 observables,问题不再出现。但我看到很多Remember to call close() on all Realm instances 警告。我很确定我正在关闭它们,所以我对此感到非常困惑。

EDIT3:通过切换到使用AndroidSchedulers.mainThread(),没有错误。除了我的 Realm 调用在主线程上运行,这很糟糕。我猜为什么这不会导致警告是因为realm 现在位于主线程上,这也是调用realm.close() 的地方(通过rx.subscriber)。

EDIT4:这是我的领域可观察调用的逻辑。

@Override
public Observable<List<ImageArticleCategoryEntity>> getArticleBuckets() {

    return RealmObservable.list(context, GET_ARTICLE_BUCKETS)
            .filter(FILTER_OUT_NULL_OR_EMPTY_LIST)
            .switchIfEmpty(refreshAndSaveAndLoadNewDataFromDb)
            .map(CONVERT_FROM_REALMLIST_TO_IMAGE_ARTICLE_ENTITYLIST);

}

public void loadArticleImages() {
    articleRepo.getArticleBuckets()
            .subscribeOn(RealmThread.get())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<ImageArticleCategoryEntity>>() {
                @Override
                public void onCompleted() {
                    Timber.v("Loading article images complete!");
                    if (view != null)
                        view.hideLoadingAnimation();
                }

                @Override
                public void onError(Throwable e) {
                    Timber.e("Error loading article images", e);
                    Log.e("tag", "Error loading article images", e);
                    if (view != null) {
                        view.hideLoadingAnimation();
                        view.showLoadingErrorToast();
                    }
                }

                @Override
                public void onNext(List<ImageArticleCategoryEntity> integerImageArticleCategoryEntityHashMap) {
                    if (view != null)
                        view.loadArticleImages(integerImageArticleCategoryEntityHashMap);
                }
            });

【问题讨论】:

  • 您是否 100% 确定 Realm.getInstance(context) 实际上返回了一个关闭的实例?我认为您可能应该将 Realm 绑定到服务中的活动计数、活动的生命周期或保留的无头片段的生命周期。
  • 我不在主线程上执行 IO 操作,这似乎是您的建议,因为领域实例不能跨线程传递。是的,我 100% 确定这正在发生。我尝试了一个捕获IllegalStateException 的try/catch 块。在捕获中,我通过realm.getInstance(context) 得到了一个新的realm,并在之后立即调用了realm.beginTransaction()。再次抛出异常。
  • 哦,好的。对,你不能跨线程共享 Realm。
  • @EpicPandaForce 是的。我没有那样做。我的领域实例只在同一个线程上使用。
  • 你什么时候打电话给unsubscribe来订阅领域?

标签: android realm rx-java


【解决方案1】:

让我们简化Realm 实例的生命周期的管理方式。如果我们在ActivityFragment 的恢复/暂停周期的上下文中管理它,我们可以更轻松地控制和停止可能消耗Realm 实例的工作。 RxAndroid 中的工具在这方面有很大帮助。

因此,对于这个示例,我将假设这是从 Activity 发生的,但可以从 Fragment 使用非常相似的方法,或者提取到辅助类中,只需提示更多管道。

如果您使用 RxAndroid 将您的 Observable 绑定到 ActivityFragment 的生命周期,您似乎已经将其用于 mainThread(),您应该能够在其中轻松管理您的 Realm 实例一个生命周期。我还将RxAsyncUtil 用于Async.toAsync,这使得Realm 实例的原始创建更容易。

我使用 Rx 的次数比 Realm 多得多(尽管我也玩过它),所以如果我的一些 API 使用不完美,请原谅我。为了便于书写和阅读,我还将事物缩写为 java8 风格的 lambda。如果你不使用 retrolambda 之类的东西,它应该仍然很容易转换回来。

class MyActivity extends Activity {
  private final CompositeSubscriptions realmSubscriptions = new CompositeSubscription();

  private AsyncSubject<Realm> realm;

  @Override
  protected void onResume() {
    super.onResume();
    realm = AsyncSubject.create();
    realmSubscriptions.add(getRealmInstance());

    // This could actually happen anytime between onResume and onPause.
    realmSubscriptions.add(loadArticlesImages());
  }

  private <T> Observable<T> bindToRealm(final Observable<T> observable) {
    // Utility to bind to the activity lifecycle, observe on the main thread
    // (implicit in the bindActivity call), and do work on the Realm thread.
    return AppObservable.bindActivity(this, observable.subscribeOn(RealmThread.get()));
  }

  private Observable<List<ImageArticleCategoryEntity>> getArticleBuckets(
      final Realm realm) {
    /* As is, except the realm instance should be passed to RealmObservable.list instead
       of the context. */
  }

  private Subscription getRealmInstance() {
    // Grab the realm instance on the realm thread, while caching it in our AsyncSubject.
    return bindtoRealm(Async.toAsync(() -> Realm.getInstance(MyActivity.this)))
        .subscribe(realm);
  }

  private Subscription loadArticleImages() {
      // Using the flatMap lets us defer this execution until the
      // Realm instance comes back from being created on the Realm thread.
      // Since it is in an AsyncSubject, it will be available nearly
      // immediately once it has been created, and is cached for any future
      // subscribers.
      return bindToRealm(realm.flatMap((realm) ->
          articleRepo.getArticleBuckets(realm).subscribeOn(RealmThread.get())))
              .subscribe(
                  (next) -> {
                    if (view != null) {
                      view.loadArticleImages(next);
                    }
                  },
                  (error) -> {
                    Timber.e("Error loading article images", e);
                    Log.e("tag", "Error loading article images", e);
                    if (view != null) { 
                      view.hideLoadingAnimation(); 
                      view.showLoadingErrorToast(); 
                    } 
                  },
                  // onCompleted
                  () -> {
                    Timber.v("Loading article images complete!"); 
                    if (view != null) view.hideLoadingAnimation(); 
                  });
  }  

  @Override
  protected void onPause() {
    // Stop any work which we added that involves the Realm instance.
    realmSubscriptions.clear();

    // Clean up the AsyncObservable. If it has a Realm instance, close it.
    if (realm.getValue() != null) {
      realm.getValue().close();
    }
    realm.dispose();
    realm = null;

    super.onPause();
  }
}

您应该能够根据需要轻松地将其提取到 Activity 之外,并且只需为生命周期绑定传递一个 Activity/Fragment 实例,以及 Observable&lt;Realm&gt;,在本例中为 AsyncSubject。如果由于订阅工作仍然存在竞争条件,您可能需要尝试将 .unsubscribeOn(AndroidSchedulers.mainThread()) 甚至 .unsubscribeOn(Schedulers.immediate())(我实际上不确定在这种情况下哪个最好)添加到 bindToRealm 以确保取消订阅发生当您希望它在onPause 中时,在Realm 实例关闭之前。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多