Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

青蛙要fly發表於2018-05-29

前言:

Android技能樹系列:

Android基礎知識

Android技能樹 — 動畫小結

Android技能樹 — View小結

Android技能樹 — Activity小結

Android技能樹 — View事件體系小結

Android技能樹 — Android儲存路徑及IO操作小結

Android技能樹 — 多程式相關小結

Android技能樹 — Drawable小結

資料結構基礎知識

Android技能樹 — 陣列,連結串列,雜湊表基礎小結

Android技能樹 — 樹基礎知識小結(一)

演算法基礎知識

Android技能樹 — 排序演算法基礎小結

Rx系列相關

Android技能樹 — RxPermission分析

Android技能樹 — Rxjava取消訂閱小結(1):自帶方式

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

現在很多專案都在使用Rxjava了,對於RxJava的使用,估計都很熟悉了,但是很多人在使用RxJava的時候容易產生記憶體洩漏問題,比如我們在用RxJava配合Retrofit的時候,發出請求出去,拿到資料後我們可能會去重新整理介面,但是如果這時候網路比較差,返回比較慢,而我們的Activity這時候關閉了,那RxJava當拿到返回的資料的時候去重新整理介面就會報空指標異常了。所以我們當Activity關閉的時候,我們這時候如果RxJava還沒執行完,我們應該取消訂閱。

常用的主要三種方式:(按照⭐️推薦從低到高來介紹)

  1. 自帶取消訂閱方式(⭐️)
  2. RxLifeCycle(⭐️⭐️)
  3. AutoDispose(⭐️⭐️⭐️)

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

本文主要講解RxLifeCycle方式。

RxLifeCycle簡述

這裡肯定不會簡單的介紹如何使用RxLifeCycle,github上面已經寫得很清楚了,RxLifecycle github連結,我們主要是看具體的實現原理。

簡單使用:

假設我們的Activity是繼承RxActivity (PS: 不是一定要繼承的,只是說明一種使用情況,具體可以看GitHub)


//手動設定解除訂閱的時間:(ps:這裡設為onPause的時候解除訂閱)
myObservable
      .compose(this.bindUntilEvent(ActivityEvent.PAUSE))
      .subscrbe();

/**
自動設定解除訂閱的時間:
(ps:比如你是在onStart時候訂閱,則自動會在onPause時候解除,
如果在onCreate時候訂閱,則會自動在onDestory時候解除)
*/
 myObservable
            .compose(this.bindToLifecycle())
            .subscribe();
複製程式碼

在介紹RxLifeCycle之前,先介紹一些基礎知識,加深大家的理解。

1 基礎知識:

1.1 Subject

我們知道在RxBus中我們使用的是Subject ,因為它既可以是觀察者又是被觀察者。而Subject有很多種類:子類有PublishSubject、BehaviorSubject、ReplaySubject、AsyncSubject、SerializedSubject。

具體每種的區別可以看:RxJava中常見的幾種Subject

這裡我們主要講解BehaviorSubject

1.1.1 BehaviorSubject

Subject that emits the most recent item it has observed and all subsequent observed items to each subscribed Observer.

大意是BehaviorSubject會傳送離訂閱最近的上一個值,沒有上一個值的時候會傳送預設值(如果有的話)。

image

1.2 Hot Observable 和 Cold Observable

正好上面講到了Subject,順帶提一下冷熱Observable。和RxLifeCycle關係不大,但是可以當了解,不想看的可以跳過 1. 2 基礎知識。

所謂的冷熱和我們單例模式中的餓漢式和飽漢式有一點點像,冷Observable需要有訂閱者的時候才開始發射資料(有點像飽漢式),熱Observable並不是一定需要訂閱者才開始發射資料(有點像餓漢式)。

PS: 大家也可以具體參考文章擁抱 RxJava(三):關於 Observable 的冷熱,常見的封裝方式以及誤區,一些圖片及說明我這邊也直接引用該文章。

1.2.1 Cold Observable :

我們常見的工廠方法提供的都是Cold Observable,包括just(),fromXX,create(),interval(),defer()。 他們有訂閱者的時候才會發射資料,並且他們的共同點是當你有多個Subscriber的時候,他們的事件是獨立的。

Observable interval = Observable.interval(1,TimeUnit.SECONDS);
複製程式碼

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

1.2.2 Hot Observable

不同於Cold Observable, Hot Observable是共享資料的。對於Hot Observable的所有subscriber,他們會在同一時刻收到相同的資料。我們通常使用publish()操作符來將Cold Observable變為Hot。或者我們在RxBus中常常用到的Subjects 也是Hot Observable。

而Hot Observable不需要有訂閱者,只需要呼叫connect()方法就會開始傳送資料,這時候當其他訂閱這個Observable的時候,並不會從頭開始接受資料。

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

而常用的Hot Observable 是 ConnectableObservable。

1.3 takeUntil操作符

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

我們可以看到takeUtil操作符的功能: 在第二個Observable發射一個內容或終止後放棄第一個Observable發射的內容。

所以我們馬上就可以想到假設第一個是我們的網路請求介面的Observable , 然後通過takeUntil繫結了一個其他的Observable , 比如我們是要在onDestory時候取消訂閱,那隻需要在onDestory方法裡面使第二個Observable傳送一個內容即可。

1.4 Filter操作符

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

就如同字面意思,起到過濾作用,你寫一個條件,只有符合條件的傳送資訊才會被接收到。

observable.filter(new Predicate<R>() {
      @Override
      public boolean test(R r) throws Exception {
           //根據過濾條件,來決定返回是false/true
           return false/true;
      }
});
複製程式碼

1.5 hide()方法

這個方法在Rxjava 1 裡面叫做asObservable() 。可能很多人沒用過,主要還是用在Subject。

比如你寫了一個Subject,你想暴露出去一個介面讓別人使用,你可能會這麼寫:

public class Test {
      //假設是BehaviorSubject
      private BehaviorSubject subject = BehaviorSubject.create();
      //把Observable這塊方面通過方法分享出去,但是又不想整個Subject都分享出去。
      public Observable getObservable(){
            return  ((Observable) subject);
      }
      //比如你呼叫play方法,按照要求只能傳送1,2,3
      public void play(){
            subject.onNext(1);
            subject.onNext(2);
            subject.onNext(3);
      }
}
複製程式碼

但是這麼寫沒啥卵用,只要獲取後強制轉換就可以:

//又可以傳送相關資料
((BehaviorSubject) getObservable()).onNext(99999);
複製程式碼

所以這時候需要使用asObservable方法了:這實際上只是將您的主題封裝在一個可觀察的物件中,這使得消費程式碼無法將其轉換回主題,asObservable是隱藏實現細節的一種防禦機制。

//改變暴露的方法:
public Observable getObservable(){
            return  subject.asObservable();
}

//這時候就算你強轉也沒用,會報錯,因為這時候通過asObservable獲取到的物件已經不是Subject物件了。
((BehaviorSubject) getObservable()).onNext(99999);
複製程式碼

而在Rxjava 2 中只是把這個asObservable 方法改成了 hide方法而已。用法相同。

1.6 Transformer

Tramsformer有很多種:ObservableTransformer,FlowableTransformer,SingleTransformer,MaybeTransformer,CompletableTransformer。

我們這裡已ObservableTransformer為例:

ObservableTransformer其實可以理解為Observable 轉換器:可以通過它將一種型別的Observable轉換成另一種型別的Observable。

比如平常時候每個observable我們都需要寫上這段程式碼::


observable.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(XXXXX);
複製程式碼

明明知道大部分的observable要使用的是

.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
複製程式碼

所以有些人就會想到我寫一個方法:

public Observable defaultSet(Observable ob){
       return ob.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread());
}

複製程式碼

當然這麼寫是沒問題,但是你每個請求都要求用defaultSet方法包住了:

defaultSet(observable)
         .subScribe(XXXXXX);
複製程式碼

沒有了鏈式呼叫的清爽了,所以這時候ObservableTransformer 就出現了:

public class Transformer {
    
    public static <T> ObservableTransformer<T, T> switchSchedulers() {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
}

複製程式碼

這時候我們只需要呼叫:

observable.compose(Transformer.<Object>switchSchedulers()).subScribe(XXXX);
複製程式碼

所以我們知道了,我們想把一個Observable轉變成另外一個Observable可以使用ObservableTransformer。

1.7 combineLatest 操作符

兩個Observable發射,合併每個Observable發射的最新內容,然後發出去,看下面的圖片就很清楚。

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

1.7 take 與 skip

take操作符:

只發出Observable發出的前n個item。

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

skip操作符: 壓制Observable發出的前n個item。

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

1.8 map

通過對每個item應用函式來轉換Observable發出的item

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

1.9 catch

在Observable發射資料時,有時傳送onError通知,導致觀察者不能正常接收資料。可是,有時我們希望對Observable發射的onError通知做出響應或者從錯誤中恢復。

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

具體主要有三種不同操作符來實現:

  1. onErrorReturn:讓Observable遇到錯誤時發射一個特殊的項並且正常終止。
  2. onErrorResumeNext:讓Observable在遇到錯誤時開始發射第二個Observable的資料序列。
  3. onExceptionResumeNext:讓Observable在遇到錯誤時繼續發射後面的資料項。

具體描述可以參考:RxJava之錯誤處理

Android技能樹 — Rxjava取消訂閱小結(2):RxLifeCycle

2 RxLife原始碼解析

我們已Activity中取消訂閱為例:

RxActivity.java(程式碼說明具體檢視原始碼裡面的備註):

public abstract class RxActivity extends Activity implements LifecycleProvider<ActivityEvent> {
    
    //建立一個BehaviorSubject,用來做takeUntil中的第二個Observable,讓其在核實的生命週期傳送資訊。
    private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();

    @Override
    @NonNull
    @CheckResult
    public final Observable<ActivityEvent> lifecycle() {

        //使用hide()方法把這個subject暴露出去
        return lifecycleSubject.hide();
    }

    @Override
    @NonNull
    @CheckResult
    public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ActivityEvent event) {
        return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
    }

    @Override
    @NonNull
    @CheckResult
    public final <T> LifecycleTransformer<T> bindToLifecycle() {
        return RxLifecycleAndroid.bindActivity(lifecycleSubject);
    }

    @Override
    @CallSuper
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        lifecycleSubject.onNext(ActivityEvent.CREATE);
    }

    @Override
    @CallSuper
    protected void onStart() {
        super.onStart();

        //在onStart時候傳送資訊
        lifecycleSubject.onNext(ActivityEvent.START);
    }

    @Override
    @CallSuper
    protected void onResume() {
        super.onResume();

        //在onResume時候傳送資訊
        lifecycleSubject.onNext(ActivityEvent.RESUME);
    }

    @Override
    @CallSuper
    protected void onPause() {

        //在onPause時候傳送資訊
        lifecycleSubject.onNext(ActivityEvent.PAUSE);
        super.onPause();
    }

    @Override
    @CallSuper
    protected void onStop() {

        //在onStop時候傳送資訊
        lifecycleSubject.onNext(ActivityEvent.STOP);
        super.onStop();
    }

    @Override
    @CallSuper
    protected void onDestroy() {

        //在onDestroy時候傳送資訊
        lifecycleSubject.onNext(ActivityEvent.DESTROY);
        super.onDestroy();
    }
}
複製程式碼

同時我們也注意到一個小細節: 在onCreate , onStart , onResume的時候,都是先呼叫super.XXX, 然後再用subject 傳送相關Event;但是在 onPause , onStop , onDestory 裡面卻是先用subject 傳送相關Event,然後再呼叫super.XXXX。為啥會有這個區別。因為一般取消訂閱都是在onPause,onStop,onDestory情形下,所以優先先取消訂閱,再去執行系統自己的操作。比如onDestory,先去取消訂閱,再去執行super.onDestory方法。

2.1 手動設定取消訂閱時間

我們先來講解手動設定某個生命週期作為取消訂閱,我們知道主要是使用:

@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ActivityEvent event) {
      return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
複製程式碼

我們通過上面的基礎知識,應該知道我們的目的是把我們自己的Obsevable和RxActivity裡面的BehaviorSubject通過takeUntil繫結在一起,因為RxActivity裡面所有的生命週期都傳送了相應的ActivityEvent事件,所以我們需要使用filter來過濾掉不是我們關心的生命週期事件 ,最後通過ObservableTransformer來把我們的Observable進行轉換成這個合成好的《Observable & BehaviorSubject》。

所以我們整體思路知道了,我們來具體看bindUntilEvent原始碼:

@Nonnull
@CheckReturnValue
public static <T, R> LifecycleTransformer<T> bindUntilEvent(@Nonnull final Observable<R> lifecycle, @Nonnull final R event) {
   checkNotNull(lifecycle, "lifecycle == null");
   checkNotNull(event, "event == null");
   return bind(takeUntilEvent(lifecycle, event));
}
複製程式碼

可以看到主要是bind和 takeUntilEvent二個方法,我們先看takeUntilEvent方法:

private static <R> Observable<R> takeUntilEvent(final Observable<R> lifecycle, final R event) {
   return lifecycle.filter(new Predicate<R>() {
       @Override
        public boolean test(R lifecycleEvent) throws Exception {
        return lifecycleEvent.equals(event);
      }
  });
}
複製程式碼

因為我們前面提過,我們在生命週期中都會讓subject傳送相應的ActivityEvent事件,所以我們這裡只是把這個subject通過filter過濾,然後只傳送我們指定的生命週期。

我們再來看bind方法,這時候就知道bind方法的目的是為了幫我們的Observable和這個已經使用過filter的subject進行繫結並返回:

@Nonnull
@CheckReturnValue
public static <T, R> LifecycleTransformer<T> bind(@Nonnull final Observable<R> lifecycle) {
   return new LifecycleTransformer<>(lifecycle);
}
複製程式碼

返回了LifecycleTransformer:

public final class LifecycleTransformer<T> implements 
ObservableTransformer<T, T>,
FlowableTransformer<T, T>,
SingleTransformer<T, T>,
MaybeTransformer<T, T>,
CompletableTransformer
複製程式碼

我們可以看到LifecycleTransformer實現了很多Transformer,因為這樣我們使用Observable或者Single等都可以來進行轉換。比如我們是Observable,那我們就會呼叫LifecycleTransformer裡面實現的的ObservableTransformer對應的apply方法:

@Override
public ObservableSource<T> apply(Observable<T> upstream) {
    return upstream.takeUntil(observable);
}
複製程式碼

我們看到果然呼叫了takeUntil,把我們的Observable通過takeUntil與已經處理好指定ActivityEvent的subject進行繫結。

最終我們只需要:

myObservable.compose(bindUntilEvent(ActivityEvent.PAUSE));
複製程式碼

2.1 自動設定取消訂閱時間

自動取消訂閱程式碼:

@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
   return RxLifecycleAndroid.bindActivity(lifecycleSubject);
}
複製程式碼

我們可以看到,大致其實和手動指定生命週期的是一樣的,唯一的區別就是我們要根據我們設定訂閱事件的生命週期推算出相對於的取消訂閱生命週期。

我們來看bindActivity原始碼:

@NonNull
@CheckResult
public static <T> LifecycleTransformer<T> bindActivity(@NonNull final Observable<ActivityEvent> lifecycle) {
    return bind(lifecycle, ACTIVITY_LIFECYCLE);
}
複製程式碼

還是老樣子,bind最後肯定是返回一個LifecycleTransformer:

@Nonnull
@CheckReturnValue
public static <T, R> LifecycleTransformer<T> bind(@Nonnull Observable<R> lifecycle,
                                                           @Nonnull final Function<R, R> correspondingEvents) {
    checkNotNull(lifecycle, "lifecycle == null");
    checkNotNull(correspondingEvents, "correspondingEvents == null");
    return bind(takeUntilCorrespondingEvent(lifecycle.share(), correspondingEvents));
}
複製程式碼

先看takeUntilCorrespondingEvent方法:

private static <R> Observable<Boolean> takeUntilCorrespondingEvent(final Observable<R> lifecycle,
                                                          final Function<R, R> correspondingEvents) {
    return Observable.combineLatest(
            lifecycle.take(1).map(correspondingEvents),
            lifecycle.skip(1),
            new BiFunction<R, R, Boolean>() {
                @Override
                public Boolean apply(R bindUntilEvent, R lifecycleEvent) throws Exception {
                    return lifecycleEvent.equals(bindUntilEvent);
                }
            })
            .onErrorReturn(Functions.RESUME_FUNCTION)
            .filter(Functions.SHOULD_COMPLETE);
}
複製程式碼

我們先來看combineLatest裡面的二個Observable:

  1. lifecycle.take(1).map(correspondingEvents): 比如我們在oncreate裡面註冊了訂閱,我們這時候就要告訴系統我們要在onDestory裡面進行取消訂閱,所以我們要先take(1)獲取第一個(因為onstart,onresume等都會傳送相應的ActivityEvent),然後通過map操作符來轉換成相對的ActivityEvent:
private static final Function<ActivityEvent, ActivityEvent> ACTIVITY_LIFECYCLE =
        new Function<ActivityEvent, ActivityEvent>() {
            @Override
            public ActivityEvent apply(ActivityEvent lastEvent) throws Exception {
                switch (lastEvent) {
                    case CREATE:
                        return ActivityEvent.DESTROY;
                    case START:
                        return ActivityEvent.STOP;
                    case RESUME:
                        return ActivityEvent.PAUSE;
                    case PAUSE:
                        return ActivityEvent.STOP;
                    case STOP:
                        return ActivityEvent.DESTROY;
                    case DESTROY:
                        throw new OutsideLifecycleException("Cannot bind to Activity lifecycle when outside of it.");
                    default:
                        throw new UnsupportedOperationException("Binding to " + lastEvent + " not yet implemented");
                }
            }
};
複製程式碼

所以總結就是第一個Observable用來記錄我們等會要在那個生命週期去取消訂閱。

  1. lifecycle.skip(1): 既然我們一個Observable是記錄了要取消訂閱的事件,那我們第二個Observable就是在不同生命週期傳送不同事件,當二個事件相同時候就說明要取消訂閱了。但是我們第一次要跳過,因為第一個事件是在訂閱時候發生的 。

所以總結第二個Observable用來實時傳送生命週期的事件。

然後通過combineLatest把二個繫結一起,這時候就會在指定的生命週期時候就會傳送true,其餘時候傳送false,最後配合filter操作符,只有在true的時候才能傳送即可。這樣最終通過takeUntil再把我們的Observable繫結在一起,然後這時候這裡傳送true的時候,我們的Observable就會取消訂閱了。

事後談RxLifeCycle:

  1. 有些人會問,為什麼我使用了RxLifeCycle,就算到了相應生命週期了,還是會呼叫onComplete方法,因為有些人可能在這個方法裡面有相應邏輯處理程式碼。因為RxLifeCycle主要使用的是takeUntil,所以最後還是會執行onComplete,如果想取消訂閱的時候不呼叫這個,還是可以直接使用原生的Disposable來進行取消訂閱。

  2. Why Not RxLifecycle?。這文章的作者就是 RxLifeCycle 的作者 ,說了使用RxLifeCycle會遇到一些窘境 ,而是推薦了AutoDispose: Automatic binding+disposal of RxJava 2 streams.,這是Uber公司的開源Rxjava取消訂閱。而RxLifeCycle作者也參與其中,所以一些設計方式也很像,AutoDipose主要是配合了Android的LifeCycle元件。

總結:

emmmmmm.......請多多指教。

相關文章