RxLife 史上最優(yōu)雅的管理RxJava生命周期

一、簡介

熟悉RxJava的同學,當我們開啟一個異步任務時,通常需要在Activity/Fragment銷毀時,及時關閉異步任務,否則就會有內存泄漏的微信。

一般的做法是訂閱成功后,拿到Disposable對象,在Activity/Fragment銷毀時,調用Disposable對象的dispose()方法,將異步任務中斷,也就是中斷RxJava的管道,代碼如下:

Disposable disposable = Observable              
    .interval(0, 1, TimeUnit.SECONDS)  //開啟一個定時器
    .subscribe(aLong -> {                       
    
    });                                         

//Activity/Fragment銷毀時,中斷RxJava管道     
if (disposable != null && !disposable.isDisposed()) {
    disposable.dispose();                            
}                                                                          

這種做法在代碼的執(zhí)行效率上是最高效、性能最優(yōu)的,然而這種做法在開發(fā)效率上卻是最低的

試想,如果我們開啟了n個異步任務,就需要在Activity/Fragment銷毀時中斷n個異步任務。對于這種寫法,身患強迫癥的我,實在不能接受。也許你們會說,可以使用CompositeDisposable類,就可以避免手寫關閉n個異步任務的代碼,只需要關閉一次即可。沒毛病,確實可以,然而這種做法也僅僅是避免了我們手寫關閉異步任務的代碼而已。追求極致的我,也不能接受這種寫法,此時我就想,能不能就用一行代碼解決這個問題呢?于是乎,就開啟了我的探索之路,于是乎,就有了RxLife。

先來介紹下RxLife,相較于trello/RxLifecycleuber/AutoDispose,具有如下優(yōu)勢:

  • 直接支持在主線程回調
  • 支持在子線程訂閱觀察者
  • 簡單易用,學習成本低
  • 性能更優(yōu),在實現(xiàn)上更加簡單

gradle依賴

dependencies {
   implementation 'com.rxjava.rxlife:rxlife:1.0.8'
   //if you use AndroidX
   implementation 'com.rxjava.rxlife:rxlife-x:1.0.8'
}

二、RxLife使用

1、Activity/Fragment

首先,我們來看看在Activity/Fragment上如何使用,如下:

//在Activity/Fragment上
Observable.interval(1, 1, TimeUnit.SECONDS)
    .as(RxLife.as(this)) //這里this 為LifecycleOwner接口對象
    .subscribe(aLong -> {
        Log.e("LJX", "onNext aLong=" + aLong);
    });

沒錯,就是這么簡單粗暴,在這,我們只需要將RxLife.as(this)傳入RxJava的as操作符即可。此時當Activity/Fragment銷毀,就會自動關閉RxJava管道,避免內存泄漏。

2、View

接著來看看在View上如何使用,如下:

//在View上
Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒發(fā)送一條消息
    .as(RxLife.as(this)) //這里this 為View對象
    .subscribe(aLong -> {
        Log.e("LJX", "onNext aLong=" + aLong);
    });

疑?這跟上面的代碼不是一模一樣的嗎?是的,代碼一模一樣,但是在這我們傳入的this是一個View對象。此時當View從窗口中移除時(執(zhí)行了onDetachedFromWindow方法),就會自動關閉RxJava管道,避免內存泄漏。

3、ViewModel

ViewModel是Google Jetpack里面的組件之一,由于它能自動感知Activity/Fragmeng的銷毀,所以RxLife單獨為它做了適配。在ViewModel中使用RxLife,需要繼承RxLife的 ScopeViewModel 類,然后就可以跟上面一樣,優(yōu)雅的使用RxLife.as(this),如下:

public class MyViewModel extends ScopeViewModel {

    public MyViewModel() {
        Observable.interval(1, 1, TimeUnit.SECONDS)
            .as(RxLife.as(this)) //這里的this 為Scope接口對象
            .subscribe(aLong -> {
                Log.e("LJX", "onNext aLong=" + aLong);
            });
    }
}

此時當Activity/Fragmeng銷毀時,就會自動關閉RxJava管道,避免內存泄漏。

注意:要想ViewModel對象感知Activity/Fragment銷毀事件,就不能使用new 關鍵字創(chuàng)建對象,必須要通過ViewModelProviders類獲取ViewModel對象,如下:

//在Activity/Fragment上
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)

4、任意類

相信大家對MVP都非常的熟悉了,在P層,我們一般都有發(fā)送Http請求的需求,
此時,我們也希望,在Activity/Fragment銷毀時,能自動將Http關閉,所以RxLife對任意類做了點適配工作。在任意類中,我們需要繼承RxLife的BaseScope類,然后就優(yōu)雅的使用RxLife.as(this)了,如下:

public class Presenter extends BaseScope {

    public Presenter(LifecycleOwner owner) {
        super(owner); 
        Observable.interval(1, 1, TimeUnit.SECONDS)
            .as(RxLife.as(this)) //這里的this 為Scope接口對象
            .subscribe(aLong -> {
                Log.e("LJX", "onNext aLong=" + aLong);
            });
    }
}

5、kotlin

在上面的代碼中,我們使用了as操作符,然后在kotlin中,as是一個關鍵字,使用起來就不是很方便,所以RxLife對kotlin做了適配工作,在kotlin中,我們可以使用life替代as操作符,并且更加的簡潔,如下:

Observable.intervalRange(1, 100, 0, 200, TimeUnit.MILLISECONDS)     
    .life(this)                                                 
    .subscribe { aLong ->                                                    
        Log.e("LJX", "onNext aLong=" + aLong)
    }                                                            

三、原理

說起原理,其實trello/RxLifecycle、uber/AutoDispose、RxLife三者的原理都是一樣的,都是拿到最低層觀察者的Disposable對象,然后在某個時機,調用該對象的Disposable.dispose()方法中斷管道,以達到目的。
原理都一樣,然而實現(xiàn)卻大不相同,

  • trello/RxLifecycle (3.0.0版本) 內部只有一個管道,但卻有兩個事件源,一個發(fā)送生命周期狀態(tài)變化,一個發(fā)送正常業(yè)務邏輯,最終通過takeUntil操作符對事件進行過濾,當監(jiān)聽到符合條件的事件時,就會將管道中斷,從而到達目的

  • uber/AutoDispose(1.2.0版本) 內部維護了兩個管道,一個是發(fā)送生命周期狀態(tài)變化的管道,我們稱之為A管道,另一個是業(yè)務邏輯的管道,我們稱至為B管道,B管道持有A管道的觀察者引用,故能監(jiān)聽A管道的事件,當監(jiān)聽到符合條件的事件時,就會將A、B管道同時中斷,從而到達目的

  • RxHttp 內部只有一個業(yè)務邏輯的管道,通過自定義觀察者,拿到Disposable對象,暴露給Scope接口,Scope的實現(xiàn)者就可以在合適的時機調用Disposable.dispose()方法中斷管道,從而到達目的

RxLife具體實現(xiàn)

光從文字層面上所原理,好像有點抽象,接下來,我們看看RxLife在代碼層面上是如何實現(xiàn)的。在上面的代碼案例中,我們皆能看到RxLife.as(this)這行代碼的身影,那這個as方法接收的是什么類型的參數(shù)呢?我們看看源碼:

image

上面一共有10個as系列方法,其中有8個是對外提供的。而且前面9個方法最終都會調用第10個as(Scope scope, boolean onMain)方法。

我們先粗略來看幾個方法,

  • as(LifecycleOwnerowner owner) 方法,接收的是一個LifecycleOwner接口對象,簡單介紹下這個接口,這個接口對象能使我們自定義的類感知Activity/Fragment的生命周期回調。我們常見的Activity/Fragment就實現(xiàn)了這個接口,所以我們就能夠在Activity/Fragment中調用此as方法

  • as(View view) 這個方法就很直觀了,直接接收一個View對象,我們在View上調用的就是這個方法

  • as(Scope scope) 方法接收一個Scope接口對象,后面會對這個接口介紹,這里可以告訴大家的是,在上面的ViewModel及任意類中繼承的ScopeViewModelBaseScope類都實現(xiàn)了Scope接口,所以我們在ViewModel及任意類中調用的就是這個as方法

Scope接口

Scope,翻譯過來就是作用域的意思。那么什么是作用域,簡單來說,就是一個對象從創(chuàng)建到死亡,這就是它的作用域,比如:Activity/Fragment的作用域就是從onCreateonDestroy;View的作用域就是從onAttachedToWindowonDetachedFromWindow;ViewModel的作用域就是從構造方法onCleared方法;其它任意類的作用域就是從創(chuàng)建到銷毀,當然,你也可以自己指定一些類的作用域。到這,我們來看看Scope接口里面都有啥:

public interface Scope {

    //訂閱事件時,回調本方法,即在onSubscribe(Disposable d)方法執(zhí)行時回調本方法
    void onScopeStart(Disposable d);

    //onError/onComplete 時調回調此方法,即事件正常結束時回調
    void onScopeEnd();
}

此接口描述的就是RxJava的作用域,即從事件訂閱到事件結束。到這,也許有人已經(jīng)知道了,只要我們實現(xiàn)了這個接口,就能拿到Disposable對象,然后就可以在某個時刻,中斷RxJava短道,提前結束RxJava作用域。從而使得RxJava的作用域小于等于調用者的作用域,避免了內存泄漏。

我們簡單看一下BaseScope類的具體實現(xiàn)

public class BaseScope implements Scope, GenericLifecycleObserver {

    private CompositeDisposable mDisposables;

    public BaseScope(LifecycleOwner owner) {
        owner.getLifecycle().addObserver(this);
    }

    @Override
    public void onScopeStart(Disposable d) {
        addDisposable(d);
    }

    @Override
    public void onScopeEnd() {}

    private void addDisposable(Disposable disposable) {
        CompositeDisposable disposables = mDisposables;
        if (disposables == null) {
            disposables = mDisposables = new CompositeDisposable();
        }
        disposables.add(disposable);
    }

    private void dispose() {
        final CompositeDisposable disposables = mDisposables;
        if (disposables == null) return;
        disposables.dispose();
    }

    @Override
    public void onStateChanged(LifecycleOwner source, Event event) {
        //Activity/Fragment 生命周期回調
        if (event == Event.ON_DESTROY) {  //Activity/Fragment 銷毀
            source.getLifecycle().removeObserver(this);
            dispose(); //中斷RxJava管道
        }
    }
}

可以看到,BaseScope實現(xiàn)非常簡單,在onScopeStart方法中拿到Disposable對象添加進CompositeDisposable對象,然后在Activity/Fragment銷毀使,調用CompositeDisposable對象的dispose方法,統(tǒng)一中斷RxJava管道,從而達到目的。

Scope一共有4個實現(xiàn)類,分別是:LifecycleScope、ViewScope、ScopeViewModelBaseScope,BaseScope上面已經(jīng)介紹,其它3個原理都一樣,只是在實現(xiàn)上會有一點點不同,這就不在一一介紹了。

四、問題暴露

我們知道,任意類想要監(jiān)聽Activity/Fragment生命周期回調,都必須要實現(xiàn)LifecycleObserver接口,然后通過以下代碼添加進觀察者隊列

owner.getLifecycle().addObserver(this);

這行代碼的內部是通過FastSafeIterableMap類來管理觀察者的,而這個類是非線程安全的,如下:

在這里插入圖片描述

我們來看看trello/RxLifecycle、uber/AutoDispose、RxLife這三者是如何處理這個問題的。

trello/RxLifecycle
RxLifecycle庫是AndroidLifecycle類感知生命周期,簡單看看源碼:

public final class AndroidLifecycle implements LifecycleProvider<Lifecycle.Event>, LifecycleObserver {

    public static LifecycleProvider<Lifecycle.Event> createLifecycleProvider(LifecycleOwner owner) {
        return new AndroidLifecycle(owner);
    }

    private final BehaviorSubject<Lifecycle.Event> lifecycleSubject = BehaviorSubject.create();

    private AndroidLifecycle(LifecycleOwner owner) {
        owner.getLifecycle().addObserver(this);
    }
    //中間省略部分代碼
    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
    void onEvent(LifecycleOwner owner, Lifecycle.Event event) {
        lifecycleSubject.onNext(event);
        if (event == Lifecycle.Event.ON_DESTROY) {
            owner.getLifecycle().removeObserver(this);
        }
    }
}

可以看到,RxLifecycle是在對象創(chuàng)建時添加觀察者,且它沒有做任何處理,如果你在子線程使用,就需要額外注意了,而且它只有在頁面銷毀時,才會移除觀察者,試想,我們在首頁一般都會有非常多的請求,而這每一個請求都會有一個AndroidLifecycle對象,我們想請求結束就要回收這個對象,然而,這個對象還是觀察者隊列里,就導致了沒辦法回收,如果我們不停下拉刷新、上拉加載更多,對內存就是一個挑戰(zhàn)。

RxLifecycle還有一個弊端時,當Activity/Fragment銷毀時,始終會往下游發(fā)送一個onComplete事件,這對于在onComplete事件中有業(yè)務邏輯的同學來說,無疑是致命的打擊。

uber/AutoDispose
AutoDispose庫我們看LifecycleEventsObservable類,如下

class LifecycleEventsObservable extends Observable<Event> {

  //省略部分代碼
  @Override protected void subscribeActual(Observer<? super Event> observer) {
    ArchLifecycleObserver archObserver = new ArchLifecycleObserver(lifecycle, observer, eventsObservable);
    observer.onSubscribe(archObserver);
    if (!isMainThread()) { //非主線程,直接拋出異常
      observer.onError(new IllegalStateException("Lifecycles can only be bound to on the main thread!"));
      return;
    }
    lifecycle.addObserver(archObserver); //添加觀察者
    if (archObserver.isDisposed()) {
      lifecycle.removeObserver(archObserver);
    }
  }
  //省略部分代碼

可以看到,AutoDispose是在事件訂閱時添加觀察者,并且當前非主線程時,直接拋出異常,也就說明使用AutoDispose不能在子線程訂閱事件。在移除觀察者方面,AutoDispose會在事件結束或者頁面銷毀時移除觀察者,這一點要優(yōu)于RxLifecycle。

RxLife

RxLife庫我們看AbstractLifecycle類,如下:

public abstract class AbstractLifecycle<T> extends AtomicReference<T> implements Disposable {
        
    //省略部分代碼
    protected final void addObserver() throws Exception {
        //Lifecycle添加監(jiān)聽器需要在主線程執(zhí)行
        if (isMainThread() || !(scope instanceof LifecycleScope)) {
            addObserverOnMain();
        } else {
            final Object object = mObject;
            AndroidSchedulers.mainThread().scheduleDirect(() -> {
                addObserverOnMain();
                synchronized (object) {
                    isAddObserver = true;
                    object.notifyAll(); //喚醒等待的線程
                }
            });
            synchronized (object) { //加鎖等待
                while (!isAddObserver) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
        //省略部分代碼
}

可以看到,RxLife對子線程做了額外的操作,在子線程通過同步鎖,添加完觀察者后再往下走,且RxLife同樣會在事件結束或者頁面銷毀時移除觀察者。

我的疑問

我們知道對View添加OnAttachStateChangeListener監(jiān)聽器是線程安全的,如下:


在這里插入圖片描述

那為何AutoDispose庫中的DetachEventCompletable依然會線程做判斷?代碼如下


在這里插入圖片描述

請大神為我解答。

五、小彩蛋

RxLife類里面的as系列方法,皆適用于Observable、Flowable、ParallelFlowable、Single、Maybe、Completable這6個被觀察者對象,道理都一樣,這里不在一一講解。
另外,在Activity/Fragment上,如果你想在某個生命周期方法中斷管道,可使用as操作符的重載方法,如下:

//在Activity/Fragment上
Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒發(fā)送一條消息
    .as(RxLife.as(this, Event.ON_STOP)) //在onStop方法中斷管道
    .subscribe(aLong -> {
        Log.e("LJX", "accept=" + aLong);
    });

此時如果你還想在主線程回調觀察者,使用asOnMain方法即可,如下:

//在Activity/Fragment上
Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒發(fā)送一條消息
    .as(RxLife.asOnMain(this, Event.ON_STOP)) //在onStop方法中斷管道,并在主線程回調觀察者
    .subscribe(aLong -> {
        Log.e("LJX", "accept=" + aLong);
    });
    
//等同于
Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒發(fā)送一條消息
    .observeOn(AndroidSchedulers.mainThread())
    .as(RxLife.as(this, Event.ON_STOP)) //在onStop方法中斷管道,并在主線程回調觀察者
    .subscribe(aLong -> {
        Log.e("LJX", "accept=" + aLong);
    });

六、小結

Activity/Fragment/View中,無需做任何準備工作就可以直接使用RxLife.as(this),
然而在ViewModel及任意類,需要分別繼承ScopeViewModelBaseScope類才可以使用RxLife.as(this),這多少都帶有點侵入性,但這也是沒有辦法的辦法,如果你覺得這樣不能接受,RxLife允許你自行去實現(xiàn)Scope接口。

注:一定要使用ViewModelProviders獲取ViewModel對象,如下

//在Activity/Fragment上
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)

本人水平有限,如文章中有見解不到之處,請廣大讀者指正,RxLife剛出來不久,使用過程中如有遇到問題,請在github上留言。

歡迎大家加群討論RxHttp&RxLife 交流群: 378530627

七、強烈推薦

30秒上手新一代Http請求神器RxHttp

Android OkHttp 史上最優(yōu)雅的設置baseUrl

Android 史上最優(yōu)雅的實現(xiàn)文件上傳、下載及進度的監(jiān)聽

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容