一、簡介
熟悉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/RxLifecycle、uber/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ù)呢?我們看看源碼:
上面一共有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及任意類中繼承的ScopeViewModel、BaseScope類都實現(xiàn)了Scope接口,所以我們在ViewModel及任意類中調用的就是這個as方法
Scope接口
Scope,翻譯過來就是作用域的意思。那么什么是作用域,簡單來說,就是一個對象從創(chuàng)建到死亡,這就是它的作用域,比如:Activity/Fragment的作用域就是從onCreate到onDestroy;View的作用域就是從onAttachedToWindow到onDetachedFromWindow;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、ScopeViewModel及BaseScope,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及任意類,需要分別繼承ScopeViewModel及BaseScope類才可以使用RxLife.as(this),這多少都帶有點侵入性,但這也是沒有辦法的辦法,如果你覺得這樣不能接受,RxLife允許你自行去實現(xiàn)Scope接口。
注:一定要使用ViewModelProviders獲取ViewModel對象,如下
//在Activity/Fragment上
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)
本人水平有限,如文章中有見解不到之處,請廣大讀者指正,RxLife剛出來不久,使用過程中如有遇到問題,請在github上留言。
歡迎大家加群討論RxHttp&RxLife 交流群: 378530627