用 LiveData 實現新的事件匯流排
作者乘風(企業代號名),目前負責貝殼裝修專案Android研發工作。
1 背景
在Android系統中,我們開發的時候不可避免的會用到訊息傳遞,頁面和元件之間都在進行訊息傳遞,訊息傳遞既可以用於Android四大元件之間的通訊,也可用於主執行緒和子執行緒之間的通訊。從一開始Android書本中學習的Handler、BroadcastReceiver、介面回撥等方式,到我們現在廣為使用到的greenrobot家的EventBus,Square家的Otto,還有依託響應式程式設計代表RxJava實現的RxBus,最近在瀏覽美團技術部落格時發現@liaohailiang基於Android Architecture Components實現了一個名為LiveEventBus的新的事件匯流排,依靠LiveData這個類可以實現一個無須手動解除註冊,無記憶體洩漏問題的事件匯流排,下面一起來了解一下。
2 從其他的時間匯流排說起
2.1 EventBus
EventBus是Android和Java的釋出/訂閱事件匯流排。在它出現之前,我們往往使用Handler、Intent、BroadcastReceiver等方式進行資料傳遞,但這些都不能滿足我們高效的開發。自從EventBus出現後,簡化了元件之間都通訊,將事件傳送者和接收者隔離,在Activity和Fragment還有後臺執行緒中表現良好,避免了複雜而且容易出錯的依賴關係和生命週期問題,立馬受到開發者的歡迎,它的思想就是訊息的釋出和訂閱,這種思想在很多其他框架中都有體現。

從圖中可以看出訂閱釋出模式是一種一對多的關係,同一個事件可以被多個訂閱者接收,當釋出者的狀態發生變化時,訂閱者都能收到通知進行資料更新。
2.2 RxBus
RxBus 名字看起來像一個庫,但它並不是一個庫,而是一種模式,它的思想是使用RxJava來實現了EventBus,而讓你不再需要使用Otto或者GreenRobot的EventBus。RxBus就是基於RxJava封裝實現的類。隨著RxJava在更多Android專案中的使用, 我們完全可以使用RxBus代替EventBus,減少庫的引入,增加系統的穩定性。
EventBus雖然使用方便,但是在事件的生命週期的處理上需要我們利用訂閱者的生命週期去註冊和取消註冊,這個部分還是略有麻煩之處,如果忘記了在生命週期結束回撥中取消訂閱,就會導致記憶體洩漏的問題,而我們可以結合使用RxLifecycle來配置,簡化這一步驟。
RxBus的實現
在RxJava中有個Subject類,它繼承Observable類,同時實現了Observer介面,因此Subject既可以作為被觀察者傳送事件,也可以作為觀察者接收事件,而RxJava內部的響應式的支援實現了事件匯流排的功能。可以使用PublishSubject.create().toSerialized();生成一個Subject物件。
剩下的動作和EventBus一樣了,在需要發訊息的地方傳送事件,那麼訂閱了該Subject物件的訂閱者就會收到事件進行處理。
最後當頁面finish的時候,如果沒有取消訂閱,就會和EventBus一樣,導致Activity或Fragment無法回收引發記憶體洩漏,EventBus要求我們在頁面結束時取消註冊,所以RxBus也需要如此。在RxJava中,訂閱操作會返回一個Subscription物件,以便在合適的時機取消訂閱,防止記憶體洩漏,如果一個類產生多個Subscription物件,我們可以用一個CompositeSubscription儲存起來,以進行批量的取消訂閱。
網上RxBus有很多實現,例如AndroidKnife/RxBus,大家可以點選檢視。
2.3 otto
otto是square推出的一款應用在android上的輕量級事件匯流排框架,目的是為了解決訊息通訊的問題,通過反射幫助你在不持有對方引用的情況下通知到對方,緩解了移動開發中會遇到的耦合問題。
那麼它有什麼不足呢?
1)在註冊的時候,因為otto要用反射遍歷Object的所有方法,所以時間會拉長,對效能會有一定的影響,如果你的專案對效能要求並不那麼高,那完全可以使用otto來減少程式碼量。
2)從原始碼裡看,在基類中註冊事件是一件比較麻煩的事情。
3)訂閱事件的引數問題,otto只允許接收一個引數,不然會丟擲RuntimeException。
4)專案通訊場景較多而且複雜的時候,otto框架的拓展性就顯得不夠友好了。
目前,Square推薦使用RxJava實現RxBus方式來代替Otto,也就是說otto被square公司拋棄了。
2.4 小結
3 LiveData基礎
3.1 什麼是LiveData?
LiveData是Android Architecture Components提出的框架,LiveData是一個可以在給定生命週期內被觀察的資料持有者類,它可以感知並遵循Activity、Fragment或Service等元件的生命週期。正是由於LiveData對元件生命週期可感知特點,因此可以做到僅在元件處於生命週期的啟用狀態時才更新UI資料。如果觀察者對應的生命週期變成了destroyed狀態時,它會被自動的移除,這對Activity和Fragment來說是非常有用的,它們可以很安全的觀察LiveData而不用擔心洩漏問題,它們被銷燬後將立即取消訂閱。
3.2 兩種註冊監聽方式
3.2.1 observe模式
擁有生命週期,在started和resumed時觸發回撥,更新資料,無需手動解除註冊,無記憶體洩漏問題。
3.2.2 observeForever模式
一直監聽,沒有生命週期,跟EventBus和RxBus一樣,需要手動解除註冊,否則有記憶體洩漏問題。
3.3 LiveData的優點
3.3.1 保證資料和UI的統一
因為LiveData採用了觀察者模式,LiveData是被觀察者,當資料改變時通知觀察者(UI)及時更新。當元件從後臺到前臺來時,LiveData也能將最新資料通知元件進行更新。
3.3.2 避免記憶體洩漏
因為LiveData能夠監聽生命週期的變化,當生命週期變化為DESTROYED時,就會清除觀察者物件,不僅可以編寫更少的程式碼,而且可以避免其他事件匯流排忘記呼叫反註冊帶來的記憶體洩漏風險。
3.3.3 頁面不可見時不會崩潰
因為只有當生命週期是STARTED或RESUMED時,LiveData才會通知資料變化,所以不用擔心頁面不可見時收到通知重新整理資料導致的崩潰。
3.3.4 不需要額外處理響應生命週期變化
這一點還是因為LiveData能感知元件的生命週期,所以不需要我們告訴它生命週期狀態。
3.3.5 解決configuration changed問題
當元件被recreate時,資料還是存在LiveData中,所以在螢幕發生旋轉或者被回收再次啟動,立刻就能收到最新的資料。
3.4 為什麼用LiveData實現的事件匯流排能替代EventBus、RxBus和otto
3.4.1 減少apk包大小
因為只依賴Android官方的Android Architecture Components元件的LiveData,沒有其他依賴,實現只有一個類。而EventBus jar包大小為57kb,RxBus依賴RxJava和RxAndroid,其中RxJava2包大小2.2MB,RxJava1包大小1.1MB,RxAndroid包大小9kb,otto就不說了,因為Square推薦使用RxJava實現RxBus方式來代替Otto[捂臉]。
3.4.2 依賴方支援更好
LiveEventBus只依賴Android官方Android Architecture Components元件的LiveData,相比RxBus依賴的RxJava和RxAndroid,依賴方支援更好。
4 LiveData程式碼實現
4.1 MutableLiveData
MutableLiveData是LiveData的子類,其中只重寫了兩個方法,程式碼如下:

一個是postValue(T value),一個是setValue(T value),具體區別就是後臺執行緒/主執行緒的呼叫,後面看程式碼繼續瞭解。
4.2 LiveData
先看看LiveData的宣告:

它是一個抽象類,所以才有了MutableLiveData這個實現類。
接下來我們先從setValue入手看看LiveData的原始碼。
4.2.1 setValue(T value)

從註釋中知道,如果有處於活躍狀態的觀察者,value將會通知到它們,setValue方法必須在UI執行緒中呼叫,並且指出,如果需要從後臺執行緒使用的話,可以使用postValue方法。
其中assertMainThread方法實際上是判斷是否是主執行緒,不是則會丟擲異常:

mVersion初始值為-1,這裡就會變成0;
主要看看dispatchingValue(null);方法:
引數initiator為null,會走到紅框中,接著看:
mLastVersion是在ObserverWrapper這個類中定義的,初始值為-1,在setValue的時候mVersion已經變成了0,所以紅框中的邏輯不會進入,接著往下走,observer就會通過onChanged回撥方法將我們設定的值通知給訂閱者。
4.2.2 postValue(T value)

從註釋知道該方法是供後臺執行緒使用的,並且多次賦值的話以最後一次賦值為準,來看看紅框裡的程式碼:

程式碼裡最終還是呼叫了setValue方法,回到了setValue的邏輯,只是幫我們處理了後臺執行緒到主執行緒的切換工作。
賦值部分看完了,現在該看看註冊訂閱的程式碼了。
4.2.3 observe

從註釋中得知,訂閱事件在主執行緒上排程,如果LiveData已經有了資料集,它將被傳遞給觀察者。只有所有者處於Link Lifecycle.State Started或Resumed狀態時,觀察者才會接收事件,如果所有者移動到Link Lifecycle.State Destroyed狀態,則觀察者將被自動刪除。當資料在owner不處於活動狀態,它將不會接收任何更新,如果它再次處於活動狀態,它將自動接收最後一個可用資料。只要給定的生命週期所有者沒有destroy,LiveData將保持對觀察者和所有者的強引用,當它被銷燬時,LiveData將刪除對觀察者和所有者的引用。
LifecycleBoundObserver實現了GenericLifecycleObserver,在生命週期發生改變時會回撥onStateChanged方法,如果Activity/Fragment對生命週期時destroyed的時候,就會觸發removeObserver(mObserver)操作,否則就會執行activeStateChanged(shouldBeActive())方法。
activeStageChanged(shouldBeActive())是父類ObserverWrapper中定義的方法:

當生命週期處於STARTED或RESUMED時,就會執行紅框中的邏輯,其實咱們上面已經看過了dispatchingValue方法,再來看一次:


到這裡就出現了一個問題: 如果之前有呼叫過setValue方法的話,這裡mVersion++每次會自增,所以肯定是>-1的,而observer中定義的mLastVersion初始值是-1,所以紅框中的邏輯不會執行,程式碼往下執行走到observer.mObserver.onChanged((T) mData); 這樣就會導致LiveData每註冊一個新的訂閱者,這個訂閱者立刻會收到一個回撥得到之前釋出的訊息,即使這個設定的動作發生在訂閱之前。
由於是因為ObserverWrapper的mLastVersion和LiveData的mVersion不同步的問題導致的,所以可以通過繼承MutableLiveData然後重寫observe方法,LiveData提供getVersion()方法返回mVersion,我們只需把ObserverWrapper的mLastVersion設成mVersion就好了。
@liaohailiang在github上開源的LiveEventBus就是這麼幹的:

這樣就能保證新註冊的訂閱者在初始
if (observer.mLastVersion >= mVersion) { return; }
執行這個方法時就return了,只有當它setValue或postValue賦值後,才會執行通知邏輯。
4.2.4 public void observeForever(@NonNull Observer
接著看看註冊永久觀察者方法裡做了什麼操作:

首先用了一個AlwaysActiveObserver類:

這個類的shouldBeActive方法return true;這個狀態表明不受生命週期的影響了。
另外wrapper.activeStateChanged(true);也是傳入了一個true,所以在

considerNotify(ObserverWrapper observer)中,訂閱者永遠會接收到最新的釋出訊息。
4.2.5 removeObserver(@NonNull final Observer
在LifecycleBoundObserver中,由於實現了GenericLifecycleObserver,所以當訂閱者通過observe方式訂閱的時候,當生命週期發生變化時會回撥該方法:

當監聽到頁面當生命週期為DESTROYED會通知移除對應的觀察者:

從mObservers列表中移除生命週期所有者對應的觀察者,解除繫結觀察者,通知狀態變化。

Activity或Fragment則會移除觀察者物件。
需要注意的是這對observeForever方式註冊的觀察者並不生效。
5 總結
Android官方提供了Android Architecture Components元件的LiveData,具有很多其他事件匯流排不具備的優點,能方便的感知到頁面的生命週期變化從而進行資料通知,我們也能基於這個類自己實現出一套效能優異的事件匯流排,這裡推薦一個@liaohailiang在GitHub開源的專案程式碼:LiveEventBus,工程包含了其他的示例程式碼的使用本文沒有做演示,只是對LiveData的核心原理做一個剖析,感興趣的同學可以自行查閱程式碼。
作 者: 乘風 (企業代號名)
出品人:漠北鷹、劉伯溫(企業代號名)
---------- END ----------
推薦閱讀