Android四大組件和線程間通訊方式有不少,好比Handler管道、廣播、接口回調、rxBus、EventBus等,可是這些方式都存在一些瑕疵,具體的優缺點以下:
git
那麼有沒有一種通訊方式能夠集以上全部框架的優勢於一身,而且避免以上缺點呢?答案就是做者今天要分享的livedatabus,livedatabus是基於原生的livedata實現的通訊框架,它擁有如下的優勢:
github
首先咱們來看一下LiveDataBus的總體架構,消息總線用來保存全部的消息通道,而後訂閱者訂閱其中任意的通道,發佈者向通道發佈消息
設計模式
LiveDataBus核心原理是什麼?
bash
LiveDataBus原理其實就是發佈-訂閱模式+liveData,接下來做者會一一道來。首先說說發佈-訂閱模式,這個模式和觀察者模式有些相似,甚至在有的設計模式書籍裏也認爲這2個模式是等同的。我我的以爲仔細分析的話仍是有一些不一樣的地方,最大的地方在於在觀察者模式中觀察者和被觀察者是互相知道對方的,可是發佈-訂閱模式中訂閱者並不知道發佈者是誰。因此在須要對兩者進行解耦時最好使用發佈-訂閱模式,發佈者不須要知道訂閱者的存在,兩者只是共用一個信息通道。通常是在單線程中使用觀察者模式,可是若是是在不一樣線程中通訊就用發佈-訂閱模式會更適合。網絡
觀察者模式和發佈-訂閱者模式對比架構
觀察者模式:
框架
發佈-訂閱者模式:異步
在客車裏乘客至關於觀察者,上車時乘務員經過買票知道了每位乘客到站信息,因此乘客只須要時刻觀察乘務員的指示,當到站點時乘務員會給出當面指示,到站點的乘客能夠根據乘務員的當面提示執行下車操做。可是在火車上乘務員不可能一個一我的去通知到站,只能經過發送廣播的方式,並且乘客並不知道是哪一個乘務員發送的廣播。
ide
接下來講說LiveData,首先看一下LiveData的定義:post
LiveData是一個數據持有類,持有數據而且這個數據能夠被觀察被監聽,和其餘Observable(被觀察者)不一樣的是,它是和Lifecycle綁定的,在生命週期內使用有效,減小內存泄漏和引用問題。
適合的使用場景這裏舉個例子,咱們經過網絡下載數據須要耗時通常放在子線程中,可是並不知道何時會下載完成,若是咱們不使用LiveData,那麼就有可能出現等數據回來時主線程的界面已經被銷燬的狀況,這樣就有可能出現問題了。這裏若是使用LiveData,就不須要管數據何時回來,回來後界面是否存在了,由於LiveData是自帶生命週期監測的。
接下來咱們來簡單使用一下LiveData
步驟一 獲取MutableLiveData對象
NameViewModel mModel = ViewModelProviders.of(this).get(NameViewModel.class);複製代碼
這個獲取方式比較奇怪,首先NameViewModel是咱們的一個自定義類,內容很是簡單,就是獲取到了一個系統的MutableLiveData對象而已,不過須要注意的是這個類必定要繼承ViewModel,否則是要報錯的,獲取不到MutableLiveData,具體代碼以下:
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
}複製代碼
步驟二 新建觀察者類
final Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(String s) {
nameText.setText(s);
}
};複製代碼
步驟三 將觀察者類傳入LiveData
mModel.getCurrentName().observe(this, nameObserver);複製代碼
注意,這裏的mModel.getCuttentName其實就是MutableLiveData對象,這個this就是當前activity的引用,也就是說將當前activity引用傳入了observe方法,這個其實就是LiveData能監測到當前Activity生命週期的緣由所在,具體怎麼監測下面會詳細講到。
步驟四 發送消息給觀察者
mModel.getCurrentName().postValue(anotherName);複製代碼
注意,這裏的anotherName是在NameViewModel中設置好的泛型,詳見第一步中MutableLiveData的對象獲取,指定了anotherName只能傳遞String過去。另外postValue方法是使用在異步線程中,setValue使用在主線程中,都是發送消息。
LiveData是如何作到監測頁面的生命週期的?
這個就必須從源碼着手了,咱們首先看一下將Activity傳進去的MutableLiveData中的observe方法
發如今這個方法中,將Activity對象和觀察者對象傳入了LifecycleBoundObserver中,因此咱們點進去看一下LifecycleBoundObserver是一個什麼樣的類,而後它接收到了這2個對象之後都作了一些什麼操做
咱們看到LifecycleBoundObserver實現了GenericLifecycleObserver,而後GenericLifecycleObserver又繼承了LifecycleObserver,而這個類正是系統檢測頁面生命週期改變相關的類。根據lifecycle的用法,實現了LifecycleObserver而且將觀察者傳入就能夠在生命週期改變時通知該觀察者。另外在其中咱們發如今onStateChanged中,若是當前頁面狀態是destroy的話,就移除咱們的觀察者,這樣觀察者就收不到回調了。
LiveData是否是就足夠解決業務中的問題了?
根據上面LiveData的基本使用,每更新一個控件就須要定義一個NameViewModel,由於須要不一樣的LiveData,緣由是觀察者的接口回調決定的,由於一個LiveData會執行一個onChange方法,可是一次只能帶來一個參數,因此不能讓全部的控件都獲取到想要的值,因此咱們必須想辦法進行優化,那就是LiveDataBus。
LiveDataBus應該如何構建?
LiveDataBus其實就是用map保存全部的LiveData,以惟一字符串做爲key,在使用的地方進行傳入key,獲取到map中保存的MutableLiveData
可是,咱們接下來作一個嘗試,在A頁面發送消息給B頁面,若此時B頁面還沒啓動。過一段時間後啓動B頁面還會收到消息,這是不合理的,由於發送消息的時候B頁面還沒啓動,因此那個時候發送的消息不該該被收到。固然這裏若是想作得更好可讓使用者進行設置,讓自定義的LiveDataBus支持粘性事件,這裏能夠參考一個第三方的LiveEventBus的實現。此次咱們主要講解一下如何經過hook技術取消這個粘性消息的接收,即在頁面未打開時,就算後面打開了也不接收消息。
想要解決這個問題就要從源碼入手了,咱們首先從調用的源頭MutableLiveData類中的setValue開始研究
咱們看到,這個setValue其實就是調用了父類LiveData中的setValue,因此咱們找到看下
能夠看到這裏面調用了dispatchingValue,因此咱們點進去看看
這裏面核心是condiderNotify方法,因此咱們固然要進去看看了
最後一行是否是很眼熟?沒錯,這就是觀察者的接口回調方法。你們要是不信能夠反過來看也能夠,首先到觀察者的接口回調方法,而後find useages同樣能夠看到是這個方法。那麼應該如何讓這個消息第一次訂閱Livedata的時候,這個onChange方法不執行呢?這個就必須用到修改系統代碼執行流程的hook技術了。
從上面代碼能夠看出,上面有3個判斷,只要其中有一個判斷執行了那麼都不會跑到最後的onChange方法,通過詳細分析這裏最好改的是第三個判斷。在第三個判斷中只要讓observer.mLastVersion >= mVersion就不會執行onChanged了,那麼應該如何讓這二者符合要求呢?
首先看一下這個mLastVersion和mVersion是在哪裏賦值的,先看mLastVersion吧,mLastVersion賦值總共有3個地方,前兩個是將mLastVersion賦值成和mVersion相等,這個不用考慮,由於這就是咱們想要的結果,最後那一次是賦值成一個變量,並且是在初始化的時候賦值的,這個地方是在private abstract修飾的一個內部類中,無法進行修改。因此咱們只能寄但願於mVersion身上了,咱們看到mVersion的賦值處第一個是賦值爲變量,這個是在LiveData的成員屬性中賦值的,在類加載的時候就會建立,這裏就算修改也會被mLastVersion複製過去,因此關鍵不在這裏。咱們把目標看向mVersion++,沒錯就是這裏打破了兩者的平衡,讓mVersion+1,最後的結果就是observer.mLastVersion<mVersion,致使那個判斷沒有進去,最後執行到了onChanged方法。那麼這個mVersion++是在哪裏執行的呢?這個是在setValue方法中執行的。因此通過分析,咱們利用好給爲初始化的頁面發送消息是先發送後註冊這個特色。只須要在判斷observer.mLastVersion>=mVersion以前將兩者賦值爲相等便可,換句話說,咱們須要在setValue後的某個地方將這兩者賦值爲相同便可。
mLastVersion是在observer對象中,而observer對象時considerNotify方法的參數傳進來的,而considerNotify方法是在dispatchingValue方法中調用的,進入dispatchingValue中能夠看到,實際上傳下去的值是mObservers這個map中的值,也就是說咱們只須要對當前頁面對應的Observer進行修改便可
修改的方式就是反射,首先拿到LiveData中的mObservers這個map,接下來獲取到當前頁面對應的Observer,而後調用其中的get方法獲取到Entry,而後調用set方法將其設置成mVersion的值,實際代碼以下
核心原理:當進入一個新頁面時,會執行對observers的初始化,其中調用hook方法對mLastVersion進行修改,致使系統流程走不到onChanged方法。當再次發消息時,因爲已經初始化過了,因此不會走到hook方法,就是正常流程,mLastVersion值爲-1,mVersion執行了++之後值變爲了0,這樣就會走入onChange方法了,因此能夠正常跑起來。
總結:本節咱們分析了不少跨線程、頁面通訊的方法,總結了它們的優缺點,而且介紹了發佈-訂閱模式和觀察者模式的區別。通過對比不少通訊方式咱們最終選擇了LiveDataBus,而且進行了模仿手寫,解決了其中發現的問題。總而言之,LiveDataBus是一個官方支持的高效率、無內存泄漏、簡單的優秀通訊框架。