#Kotlin# 小心 Rx 的生命周期

前言

最近在使用 RxJava 的時候,遇到了一些問題。我在某個頁面訂閱了 Retrofit 一次非同步網路請求返回的 Observable,在 Retrofit 尚未發射結果之前退出該頁面的話,程式會中止。

發生中止的原因是因為 Observable 的生命周期凌駕於 Activity(/Fragment) 之上,當頁面銷毀後,並沒有中止 Observable 流,所以在 Retrofit 中的 Observable 依然保留了 Activity 的 Subscriber 的強引用,最終導致 Activity 無法被釋放,產生內存泄漏。這只是問題之一,當 Observable 開始發射數據時,如果 Subscriber 中要進行 UI 處理,會直接拋出異常並退出程式,因為 UI 對象已經被銷毀了。

RxLifecyle

trello/RxLifecycle 是一個不錯的解決方案,它監聽了 Activity/Fragment 的銷毀事件,利用 compose() 方法在流中加入自己的攔截器,在 subscribe() 操作之前攔截 Observable 並檢測 UI 是否銷毀,如果 UI 已經銷毀的話則中斷 Observable,並取消訂閱。

局限

RxLifecycle 不太適用於 MVP 框架,它提供的一些組件更適合直接在 Activity/Fragment 中處理 Observable。但是在 MVP 框架中,我們更應該於在 Presenter 層處理 Observable,而 View 層應該只負責提供視圖介面。所以我們的 Observable 應該和 Presenter 的生命周期掛鉤,而不應該和 View 層產生耦合。

解決方法

於是,我在新版本 Presenter.kt 中實現了類似的功能。

open class Presenter {n public enum class Event {n // Activity Eventsn CREATE,n START,n RESUME,n PAUSE,n STOP,n DESTROY,nn // Fragment Eventsn ATTACH,n CREATE_VIEW,n DESTROY_VIEW,n DETACHn }nn public val eventBehavior: BehaviorSubject<Event> = BehaviorSubject.create()nn final fun destory() {n eventBehavior.onNext(Event.DESTROY)n }nn // ...n}nnpublic class NormalCheckLifeCycleTransformer<T>(val eventBehavior: BehaviorSubject<Presenter.Event>):n Observable.Transformer<T, T> {n override fun call(observable: Observable<T>): Observable<T> {n return observable.takeUntil(n eventBehavior.skipWhile {n it != Presenter.Event.DESTROY && it != Presenter.Event.DETACHn }n )n }n}nnpublic fun <T> rx.Observable<T>.on(presenter: Presenter):n Observable<T> {n return observeOn(rx.android.schedulers.AndroidSchedulers.mainThread())n .compose(NormalCheckLifeCycleTransformer<T>(presenter.eventBehavior))n}n

我自定義了一個轉換器,它對源 Observable 進行布爾操作 takeUntil(),當我們用於記錄 Activity 和 Fragment 生命周期事件的 eventBehavior 發射出一個 DESTROY 或者 DETACH 事件的時候則會中斷 Observable。

要注意的是 takeUntil() 不是等 Observable 發射出新的數據時才進行布爾判斷,它是在 takeUntil(O) 的 O 發射出任意數據時就進行中斷源 Observable。這很重要,這保障了我們 UI 進行銷毀的當時就能中斷訂閱,而不是等到 Observable 發射數據時再取消。

此外,我還實現了 Observable.on() 的拓展語法糖,這樣我們就能在 WeatherPresenter.kt 中使用 on() 語法糖將其綁定到我們的 Presenter 的生命周期內:

WeatherModel.getWeather("101010100").on(this).subscribe {n impl.setWeatherInfo(it)n}n

題外話

  • Kotlin 的 Lambda 寫起來真的是非常美妙,但是不要忘記了,JVM 上 Lambda 的實現和匿名(內部)類基本一致,當在 Lambda 中需要捕獲外部類的 this 指針時,一樣可能會引起內存泄漏,所以需要多加小心。具體例子可以查看該次 Commit 的更改:avoid rundealyeds memory leak · GitHub

  • 我在實現該版本 Lifecycler Checking 之前,曾經試過另一版本的實現。我使用了 CompositeSubscription 在 UI 銷毀時,對所有 Observable 進行統一取消訂閱。這樣做後確實沒有產生任何錯誤,但是當我使用 Leakcanary 進行測試時,顯示程式產生了內存泄漏。我很懷疑 Observable 並沒有清除 Subscriber 的引用,當然這個有待驗證。

推薦閱讀:

Kotlin 泛型中的 in 和 out
Kotlin 基礎:從 null safety 到 Standard.kt

TAG:Android开发 | Kotlin |