neonDion
neonDion

Reputation: 2358

Android, RxJava, MVP and memory leaks

I have an Activity which creates an instance of Presenter. In the Presenter layer I am getting an instance of an Observable from a Repository. Then I subscribe to the Observable using a subclass of Subscriber and then I add the resulting Subscription object to a CompositeSubscription. Because I need to modify the Activity once the Subscriber's onNext() has been called I also pass a reference of Presenter to the Subscriber.

Now I'm wondering how the references work and what becomes eligible for garbage collection and when.

Example 1: The Observable is subscribed using the Subscriber and the Subscription is added to CompositeSubscription. Before the Subscriber's onNext() can be called the parent activity hits its onPause() lifecycle event. It tells the Presenter about hitting onPause() and the Presenter calls clear() on the CompositeSubscription.

At this point are CompositeSubscription, Subscriber and Observable eligible for GC? Or in Presenter's onPause() method do I need to explicitly null the references to Observable, Subscriber and CompositeSubscription?

Example 2:

Similar to example 1 the Presenter subscribes to an Observable and before the onNext() method of Subscriber is called the Activity goes through onPause() but this time it also goes through onResume().

So just like in example 1 the Presenter calls clear() on the CompositeSubscription in onPause().

Then in onResume the following happens: The Presenter previously cached the Observable in a singleton class so in onResume it can see that there is an Observable in the cache meaning the Observable never finished running. So, the Presenter now creates a new instance of Subscriber and a new instance of CompositeSubscription and subscribes to the cached Observable using the new instances of Subscriber and CompositeSubscription.

But now my question is, have I introduced a memory leak? The first instance of Subscriber has a reference to Presenter. When onResume() gets called I create a second instance of Subscriber and the Presenter references this new instance. So what happens to the first instance of Subscriber? Is it eligible for GC or does it create a memory leak because it references presenter but has no references pointing to it anymore?

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);
     }
}

Upvotes: 1

Views: 1152

Answers (1)

yosriz
yosriz

Reputation: 10267

Example 1:
assuming you mean that here there is no 'cache' that hold reference:
There is no leak in this example. regarding the GC, the Subscriber object can be GC'd (freed) as no object has reference to it anymore, but, CompositeSubscription reference is held by the presenter that I assume held by the activity (might be the same for Observable, not clear from the example) so as the activity hold reference to this objects, their GC depends on their parent activity. until the activity itself will be no longer hold by anyone.
(side note: there is a difference between activity that is finished to activity that is a paused/stopped, in the former case the system will try to GC soon enough as there is no need with this activity anymore while in the later case the system will hold the activity as long as it sees its necessary)

Example 2:
although you have a (assuming statically) cache, as you are unsubscribing from the observable at onPause, the Observable object has no reference to the presenter/activity and thus no activity leak happen. on real life scenario, it is still depends on what this observable or any operator apply at him does hold, meaning there can be leak if somewhere in the chain you have reference to activity/presenter object.

besides that, I would recommended always to test to make sure you didn't miss something, you can use adb dumpsys meminfo and observer the count of activities, simple open and close (finish) the activity multiple times can indicate a leak, there also the LeakCanary library by the awesome guys at Square that can report automatically about activity leaks at debug.

Upvotes: 2

Related Questions