【问题标题】:Android, RxJava, MVP and memory leaksAndroid、RxJava、MVP 和内存泄漏
【发布时间】:2017-01-11 19:10:54
【问题描述】:

我有一个创建 Presenter 实例的 Activity。在 Presenter 层中,我从存储库中获取了一个 Observable 的实例。然后我使用订阅者的子类订阅 Observable,然后将生成的订阅对象添加到 CompositeSubscription。因为我需要在调用订阅者的 onNext() 后修改活动,所以我还将 Presenter 的引用传递给订阅者。

现在我想知道引用是如何工作的,什么时候可以进行垃圾回收。

示例 1: Observable 使用订阅者订阅,订阅被添加到 CompositeSubscription。在调用订阅者的 onNext() 之前,父活动会触发其 onPause() 生命周期事件。它告诉 Presenter 点击 onPause() 并且 Presenter 在 CompositeSubscription 上调用 clear()。

此时 CompositeSubscription、Subscriber 和 Observable 是否符合 GC 条件?或者在 Presenter 的 onPause() 方法中,我是否需要显式地清空对 Observable、Subscriber 和 CompositeSubscription 的引用?

示例 2:

与示例 1 类似,Presenter 订阅了一个 Observable,并且在调用 Subscriber 的 onNext() 方法之前,Activity 会执行 onPause(),但这次它也会执行 onResume()。

就像在示例 1 中一样,Presenter 在 onPause() 中对 CompositeSubscription 调用 clear()。

然后在 onResume 中会发生以下情况: Presenter 之前将 Observable 缓存在一个单例类中,因此在 onResume 中它可以看到缓存中有一个 Observable,这意味着 Observable 从未完成运行。因此,Presenter 现在创建了一个新的 Subscriber 实例和一个新的 CompositeSubscription 实例,并使用新的 Subscriber 和 CompositeSubscription 实例订阅缓存的 Observable。

但现在我的问题是,我是否引入了内存泄漏? Subscriber 的第一个实例引用了 Presenter。当 onResume() 被调用时,我创建了 Subscriber 的第二个实例,Presenter 引用了这个新实例。那么订阅者的第一个实例会发生什么?它是否符合 GC 条件,还是因为它引用了 Presenter 但不再有指向它的引用而造成内存泄漏?

class Presenter {
    private MyActivity mActivity;
    private Repository mRepository;
    private GlobalCache mGlobalCache;
    private CompositeSubscription mCompSub;

    public Presenter(MyActivity activity, Repository repository, GlobalCache globalCache) {
        mActivity = activity;
        mRepository = repository;
        mGlobalCache = globalCache;
    }

    public void doLongRunningThing() {
        Observable<Object> obs = mRepository.getObs();
             mGlobalCache.retain(obs);
             mCompSub = new CompositeSubscription();
             MySubscriber subscriber = new Subscriber(this);
             compSub.add(obs.subscribe(subscriber));
    }


    public void onResume() {
        if (mGlobalCache.getObs() != null) {
            Observable<Object> obs = mGlobalCache.getObs();
            mCompSub = new CompositeSubscription();
            MySubscriber subscriber = new Subscriber(this);
            compSub.add(obs.subscribe(subscriber));
        }
    }

    public void onPause() {
        if(mCompSub != null && mCompSub.hasSubscriptions()) {
            mCompSub.clear();
        }
    }

    public void onDestroy() {
        mActivity = null;
        mRepository = null;
        mGlobalCache = null;
    }

    public void handleResponse(Object object) {
        activity.setUiToSomeState();
    }

}

class MySubscriber extends Subscriber<Object> {
     private Presenter mPresenter;
     private GlobalCached mGlobalCache;

     public MySubscriber(Presenter presenter, GlobalCache globalCache) {
         mPresenter = presenter;
         mGlobalCache = globalCache;
     }

     onCompleted() {

     }

     onError() {

     }

     onNext(Object object) {
          mGlobalCache.clearObs();
          mPresenter.handleResponse(object);
     }
}

【问题讨论】:

    标签: android memory-leaks rx-java


    【解决方案1】:

    示例 1:
    假设您的意思是这里没有保存引用的“缓存”:
    此示例中没有泄漏。关于 GC,订阅者对象可以被 GC(释放),因为没有对象再引用它,但是,CompositeSubscription 引用由演示者持有,我假设由活动持有(可能与 Observable 相同,不清楚从示例中),因此活动持有对这个对象的引用,它们的 GC 取决于它们的父活动。直到活动本身不再被任何人持有。
    (旁注:已完成的活动与暂停/停止的活动之间存在差异,在前一种情况下,系统将尽快尝试 GC,因为不再需要此活动,而在后一种情况下,系统将只要它认为有必要,就会举行活动)

    示例 2:
    虽然你有一个(假设是静态的)缓存,当你在 onPause 取消订阅 observable 时,Observable 对象没有对演示者/活动的引用,因此不会发生活动泄漏。 在现实生活场景中,它仍然取决于这个 observable 或应用在他身上的任何操作符确实持有什么,这意味着如果在链中的某个地方您引用了活动/演示者对象,则可能存在泄漏。

    除此之外,我建议您始终进行测试以确保您没有遗漏任何内容,您可以使用 adb dumpsys meminfo 并观察活动的数量,简单地多次打开和关闭(完成)活动可能表明存在泄漏,Square 的优秀人员还提供了 LeakCanary 库,可以在调试时自动报告活动泄漏。

    【讨论】:

    • 我的主要问题是参考树,并且不了解垃圾收集器在确定某物是否符合 GC 条件时如何抓取树。你的答案是正确的。