RxLifecycle Anti-Memory Leakage Analysis

Keywords: Fragment Retrofit network

Generation of call chain

For example, fragment inherits RxFragment from RxLifecycle and then initiates network requests in fragment

// observable generated by Retrofit
Retrofit.create(xxx).subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .compose(fragment.bindUntilEvent(FragmentEvent.DETACH))
                .subscribe(new Observer<Response<T>>() {
                    @Override
                    public void onError(Throwable e) {
                        onResponseError(e);
                    }

                    @Override
                    public void onComplete() {
                        // do something
                    }

                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(Response<T> t) {
                        onResponse(t);
                    }
                });

The generated call chain is as follows

this = {ObservableTakeUntil@14202} 
 other = {ObservableFilter@14199} 
  predicate = {RxLifecycle$1@14208} 
   event = {FragmentEvent@14227} "DETACH"
  source = {BehaviorSubject@14209} 
 source = {ObservableObserveOn@14193} 
  scheduler = {HandlerScheduler@14188} 
  source = {ObservableUnsubscribeOn@14187} 
   scheduler = {IoScheduler@14181} 
   source = {ObservableSubscribeOn@14180} 
    scheduler = {IoScheduler@14181} 
    source = {CallExecuteObservable@14166} 

Call of key methods

According to the generated call chain, the last subscribe called is the ObservableTakeUntil.subscribe() method, whose subscribeActual() method is as follows

@Override
public void subscribeActual(Observer<? super T> child) {
    final SerializedObserver<T> serial = new SerializedObserver<T>(child);

    final ArrayCompositeDisposable frc = new ArrayCompositeDisposable(2);

    final TakeUntilObserver<T> tus = new TakeUntilObserver<T>(serial, frc);

    child.onSubscribe(frc);

    other.subscribe(new TakeUntil(frc, serial));

    source.subscribe(tus);
}

Here the other is ObservableFilter, the source is ObservableObserveOn. In the call to other/source.subscribe(), onSubscribe() is called in BehaviorSubject/ObservableSubscribeOn.subscribeActual(), respectively, and eventually to TakeUntil/TakeUnObserver.onSubscribe(), where frc is assigned. After assignment, frc is as follows

this = {ArrayCompositeDisposable@6850}
 array = {Object[2]@6871} 
  0 = {ObservableObserveOn$ObserveOnObserver@6862} "0"
  1 = {ObservableFilter$FilterObserver@6852} 

The call of dispose

Two paths are called to dispose

ObservableTakeUntil.subscribeActual
 ObservableFilter.subscribeActual
  BehaviorSubject.subscribeActual
   BehaviorSubject$BehaviorDisposable.emitFirst
    // After that, it's the real dispose process.
    BehaviorSubject$BehaviorDisposable.test
     NotificationLite.accept
      ObservableFilter$FilterObserver.onNext
       ObservableTakeUntil$TakeUntil.onNext // Here, while calling dipose, onCompete is also called to notify observer.
        ArrayCompositeDisposable.dispose
         // Here we traverse frc and dispose
         ObservableObserveOn$ObserveOnObserver.dispose
          ObservableUnsubscribeOn$UnsubscribeObserver.dispose // Set the AtomicBoolean flag to true to indicate dispose
           ObservableSubscribeOn$SubscribeOnObserver.dispose
            DisposableHelper.dispose
             CallExecuteObservable.dispose // Call okHttpCall.cancel
            DisposableHelper.dispose
             scheduler.dispose
         ObservableFilter$FilterObserver
          BasicFuseableObserver.dispose
           BehaviorSubject$BehaviorDisposable.dispose // cancel is set to true

Fragment.onDetach
 BehaviorSubject.onNext(FragmentEvent.DETACH)
  BehaviorSubject$BehaviorDisposable.emitNext
   // This is the true dispose process after that, ibid.
   BehaviorSubject$BehaviorDisposable.test

The above different observer s have disposed, the specific dispose is different, such as terminating okHttpCall, terminating scheduler, setting identification bits and so on.

If:
1. dispose before the request is made, and go to the first branch, because okHttpCall is cancel ed, so it will not make the request again.
2. After the request is made, dispose will go to the second branch, because Observable Unsubscribe On $Unsubscribe Observer has dispose, get() == true, so onNext will not do anything here, as follows

@Override
public void onNext(T t) {
    if (!get()) {
        actual.onNext(t);
    }
}

Because the call is terminated and cleaned up in dispose and onComplete is also called, there will be no memory leak and no callback to Activity/Fragment, resulting in possible null pointer and other exceptions.

summary

Two key points are:
1. Observable TakeUntil is generated. In addition to source, other is generated. Other defines behavior according to predicate (condition). That is, when predicate.test is true, it executes the method of calling Observer Filter Observer through behavior 2. Observable TakeUntil $TakeUntil. onNext is actually called again by Observable Filter Observer, which is entered through frc. dispose

Bug:

A small problem with RxLifecycle.

When compose
1. If the until event is FragmentEvent.DESTROY_VIEW, the request for fragment s is sent after onDestoryView, which can cause problems. Reason: BehaviorSubject$BehaviorDisposable.emitNext will not be invoked at onDestoryView because the request has not yet been sent and the queue is empty, and then the DESTROY_VIEW event is washed out by the subsequent DESTROY and DETACH events in turn, so the request will not be cancelled when the request returns because the event is not DESTROY_VIEW, and the callback of the final request will still be invoked. use
2. If the until event is FragmentEvent.DETACH, then the problem also occurs when the request result is returned between onDestoryView and onDetach.

In general, it is better to set the until event to DESTROY_VIEW, and then remove onDestory in RxFragment and onNext called in onDetach.

Posted by Nothadoth on Mon, 03 Jun 2019 19:11:50 -0700