RxLife 史上最優雅的管理RxJava生命週期

本文已受權「郭霖」微信公衆號獨家原創發佈java

1、簡介

熟悉RxJava的同窗,當咱們開啓一個異步任務時,一般須要在Activity/Fragment銷燬時,及時關閉異步任務,不然就會有內存泄漏的微信。git

通常的作法是訂閱成功後,拿到Disposable對象,在Activity/Fragment銷燬時,調用Disposable對象的dispose()方法,將異步任務中斷,也就是中斷RxJava的管道,代碼以下:github

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

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

這種作法在代碼的執行效率上是最高效、性能最優的,然而這種作法在開發效率上倒是最低的安全

試想,若是咱們開啓了n個異步任務,就須要在Activity/Fragment銷燬時中斷n個異步任務。對於這種寫法,身患強迫症的我,實在不能接受。也許大家會說,可使用CompositeDisposable類,就能夠避免手寫關閉n個異步任務的代碼,只須要關閉一次便可。沒毛病,確實能夠,然而這種作法也僅僅是避免了咱們手寫關閉異步任務的代碼而已。追求極致的我,也不能接受這種寫法,此時我就想,能不能就用一行代碼解決這個問題呢?因而乎,就開啓了個人探索之路,因而乎,就有了RxLife微信

先來介紹下RxLife,相較於trello/RxLifecycleuber/AutoDispose,具備以下優點:異步

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

gradle依賴ide

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

2、RxLife使用

一、Activity/Fragment

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

//在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管道,避免內存泄漏。性能

二、View

接着來看看在View上如何使用,以下:學習

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

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

三、ViewModel

ViewModel是Google Jetpack裏面的組件之一,因爲它能自動感知Activity/Fragmeng的銷燬,因此RxLife單獨爲它作了適配。在ViewModel中使用RxLife,須要繼承RxLife的 ScopeViewModel 類,而後就能夠跟上面同樣,優雅的使用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 關鍵字建立對象,必需要經過ViewModelProviders類獲取ViewModel對象,以下:

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

四、任意類

相信你們對MVP都很是的熟悉了,在P層,咱們通常都有發送Http請求的需求, 此時,咱們也但願,在Activity/Fragment銷燬時,能自動將Http關閉,因此RxLife對任意類作了點適配工做。在任意類中,咱們須要繼承RxLife的BaseScope類,而後就優雅的使用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);
            });
    }
}
複製代碼

五、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)
    }                                                            
複製代碼

3、原理

提及原理,其實trello/RxLifecycleuber/AutoDisposeRxLife三者的原理都是同樣的,都是拿到最低層觀察者的Disposable對象,而後在某個時機,調用該對象的Disposable.dispose()方法中斷管道,以達到目的。 原理都同樣,然而實現卻大不相同,

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

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

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

RxLife具體實現

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

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

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

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

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

  • as(Scope scope) 方法接收一個Scope接口對象,後面會對這個接口介紹,這裏能夠告訴你們的是,在上面的ViewModel及任意類中繼承的ScopeViewModelBaseScope類都實現了Scope接口,因此咱們在ViewModel及任意類中調用的就是這個as方法

Scope接口

Scope,翻譯過來就是做用域的意思。那麼什麼是做用域,簡單來講,就是一個對象從建立到死亡,這就是它的做用域,好比:Activity/Fragment的做用域就是從onCreateonDestroy;View的做用域就是從onAttachedToWindowonDetachedFromWindow;ViewModel的做用域就是從構造方法onCleared方法;其它任意類的做用域就是從建立到銷燬,固然,你也能夠本身指定一些類的做用域。到這,咱們來看看Scope接口裏面都有啥:

public interface Scope {

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

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

此接口描述的就是RxJava的做用域,即從事件訂閱到事件結束。到這,也許有人已經知道了,只要咱們實現了這個接口,就能拿到Disposable對象,而後就能夠在某個時刻,中斷RxJava短道,提早結束RxJava做用域。從而使得RxJava的做用域小於等於調用者的做用域,避免了內存泄漏。

咱們簡單看一下BaseScope類的具體實現

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實現很是簡單,在onScopeStart方法中拿到Disposable對象添加進CompositeDisposable對象,而後在Activity/Fragment銷燬使,調用CompositeDisposable對象的dispose方法,統一中斷RxJava管道,從而達到目的。

Scope一共有4個實現類,分別是:LifecycleScopeViewScopeScopeViewModelBaseScope,BaseScope上面已經介紹,其它3個原理都同樣,只是在實現上會有一點點不一樣,這就不在一一介紹了。

4、問題暴露

咱們知道,任意類想要監聽Activity/Fragment生命週期回調,都必需要實現LifecycleObserver接口,而後經過如下代碼添加進觀察者隊列

owner.getLifecycle().addObserver(this);
複製代碼

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

在這裏插入圖片描述
咱們來看看 trello/RxLifecycleuber/AutoDisposeRxLife這三者是如何處理這個問題的。

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是在對象建立時添加觀察者,且它沒有作任何處理,若是你在子線程使用,就須要額外注意了,並且它只有在頁面銷燬時,纔會移除觀察者,試想,咱們在首頁通常都會有很是多的請求,而這每個請求都會有一個AndroidLifecycle對象,咱們想請求結束就要回收這個對象,然而,這個對象仍是觀察者隊列裏,就致使了沒辦法回收,若是咱們不停下拉刷新、上拉加載更多,對內存就是一個挑戰。

RxLifecycle還有一個弊端時,當Activity/Fragment銷燬時,始終會往下游發送一個onComplete事件,這對於在onComplete事件中有業務邏輯的同窗來講,無疑是致命的打擊。

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會在事件結束或者頁面銷燬時移除觀察者,這一點要優於RxLifecycle。

RxLife

RxLife庫咱們看AbstractLifecycle類,以下:

public abstract class AbstractLifecycle<T> extends AtomicReference<T> implements Disposable {
        
    //省略部分代碼
    protected final void addObserver() throws Exception {
        //Lifecycle添加監聽器須要在主線程執行
        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監聽器是線程安全的,以下:

在這裏插入圖片描述
那爲什麼AutoDispose庫中的DetachEventCompletable依然會線程作判斷?代碼以下
在這裏插入圖片描述
請大神爲我解答。

5、小彩蛋

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

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

此時若是你還想在主線程回調觀察者,使用asOnMain方法便可,以下:

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

6、小結

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

注:必定要使用ViewModelProviders獲取ViewModel對象,以下

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

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

歡迎你們加羣討論RxHttp&RxLife 交流羣: 378530627

7、強烈推薦

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

Android OkHttp 史上最優雅的設置baseUrl

Android 史上最優雅的實現文件上傳、下載及進度的監聽

相關文章
相關標籤/搜索