阿里P7Android高級架構進階視頻免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將繼續從以下兩個內容來介紹通信方案:
- [ViewModel 與 View 的通信]
- [ EventBus源碼分析]
一、ViewModel 與 View 的通信
以一個用戶信息修改界面為例,在請求服務器之前,必須先校驗用戶數據,而 Presenter 或 ViewModel 的職責就是顯示和取消 Loading,以及將校驗或服務器的返回結果展示到界面上。此外,如果一個 Dialog 正在顯示,當配置變更后也應該恢復 Dialog。
Presenter 和 ViewModel 不應持有 View 的引用。
在 MVP 架構中,我們經常需要定義一些契約類接口(Contract
),View 實現 Contract.View
接口,Presenter 實現 Contract.Presenter
接口,在 Presenter 中不持有 Activity/Fragment 的引用,只持有 View 實例,這樣可以方便地調用 View 接口暴露的方法。
例如 EditProfileContract.kt
:
但是,在 MVVM 架構中,ViewModel 不再持有 View 的引用,而是通過 LiveData 或 RxJava 向 View 層暴露數據。一旦 View 訂閱了 ViewModel,它就開始接收數據更新。這看似很完美,但當 ViewModel 想要更新 View 狀態,比如顯示和取消 Loading,將數據校驗或服務器結果反饋到 UI 界面上,會變得非常困難。
解決方案
ViewModel 中的 LiveData 或 Observable 越少越好。因此我們最好找到一種方法,可以封裝需要傳遞給 View 層的數據和信息。在多數情況下,ViewModel 需要向 View 層暴露以下三種數據:
- Data
- Status
- State
下面將依次介紹。
Data
Data -- 就是需要在 View 上展示的內容,比如用戶信息的 User 實體類,或社交 Feed 流中的列表項。
Status
Status -- 可以是任何僅需傳遞一次的信息,如校驗錯誤,網絡異常,或者服務器錯誤。
Status.Kt:
LiveData
沒有提供任何開箱即用的方法,但在 Google 的官方示例中,有一個 SingleLiveEvent 的實現,可以解決這個問題。
一個生命周期感知的被觀察者,僅在訂閱后發送新的更新,常用於導航和 Snackbar 消息等事件。
這可以避免一些常見問題:在配置變更(如屏幕旋轉)期間,如果觀察者處於活動動態,SingleLiveEvent
將會發送更新事件。
它繼承於MutableLiveData
,是一個被觀察者,即使對外暴露了SingleLiveEvent#setValue()
或SingleLiveEvent#call()
方法,
注意:只有一個觀察者會受到更新通知。
新建一個 SingleLiveEvent
用來向 View 層暴露 Status 數據。
EditProfileViewModel.Kt:
View 只關心 Status 數據,並根據不同的狀態或錯誤執行對應的邏輯。如下實例,我們能很方便地根據每個錯誤顯示不同的 Toast 或 Snackbar。
EditProfileFragment.Kt:
State
State -- 即 UI 狀態,比如加載進度條和 Dialog 等,每次開始訂閱 ViewModel 的數據時,ViewModel 應該把這些 UI 狀態通知給 View 層。一種簡單的做法是,我們可以創建一個數據類來保存這些狀態。
EditProfileState.Kt:
然后在 ViewModel 中創建一個 MutableLiveData
,用來包裝這個 EditProfileState
。由於 ViewModel 只會暴露 LiveData 給 View 層,因此我們應該提供 setter
方法,便於 View 更新此狀態。
EditProfileViewModel.kt:
最后,根據上面的 State 狀態數據,決定 Dialog 的顯示和取消。
EditProfileFragment.Kt:
二、EventBus源碼分析
2.1EventBus簡介
2.1.1EventBus
EventBus 是一個 Android 事件發布/訂閱框架,通過解耦發布者和訂閱者簡化Android 事件傳遞,這里的事件可以理解為消息,本文中統一稱為事件。事件傳遞既可用於 Android 四大組件間通訊,也可以用戶異步線程和主線程間通訊等等。
傳統的事件傳遞方式包括:Intent、Handler、BroadCastReceiver、Interface 回調,相比之下 EventBus 的優點是代碼簡潔,使用簡單,並將事件發布和訂閱充分解耦。可簡化 Activities, Fragments, Threads, Services 等組件間的消息傳遞,可替代Intent、Handler、BroadCast、接口等傳統方案,更快,代碼更小,50K 左右的 jar 包,代碼更優雅,徹底解耦。
2.1.2概念
事件(Event):又可稱為消息,本文中統一用事件表示。其實就是一個對象,可以是網絡請求返回的字符串,也可以是某個開關狀態等等。事件類型(EventType)指事件所屬的 Class。
事件分為一般事件和 Sticky 事件,相對於一般事件,Sticky 事件不同之處在於,當事件發布后,再有訂閱者開始訂閱該類型事件,依然能收到該類型事件最近一個Sticky事件。
訂閱者(Subscriber):訂閱某種事件類型的對象。當有發布者發布這類事件后,EventBus 會執行訂閱者的onEvent 函數,這個函數叫事件響應函數。訂閱者通過register 接口訂閱某個事件類型,unregister 接口退訂。訂閱者存在優先級,優先級高的訂閱者可以取消事件繼續向優先級低的訂閱者分發,默認所有訂閱者優先級都為0。
發布者(Publisher):發布某事件的對象,通過 post 接口發布事件。
2.1.3、訂閱者、發布者、EventBus 關系圖
EventBus 負責存儲訂閱者、事件相關信息,訂閱者和發布者都只和 EventBus 關聯。
事件響應流程
訂閱者首先調用 EventBus的 register 接口訂閱某種類型的事件,當發布者通過 post 接口發布該類型的事件時,EventBus 執行調用者的事件響應函數。
2.2、EventBus優勢
2.2.1、對比Java監聽器接口(Listener Interfaces)
在Java中,特別是Android,一個常用的模式就是使用“監聽器(Listeners)”接口。在此模式中,一個實現了監聽器接口的類必須將自身注冊到它想要監聽的類中去。這就意味着監聽與被監聽之間屬於強關聯關系。這種關系就使得單元測試很難進行開展。
2.2.2、對比本地廣播管理器(LocalBroadcastManager)
另一項技術就是在組件間通過本地廣播管理器(LocalBroadcastManager)進行消息的發送與監聽。雖然這對於解耦有很好的幫助,但它的API不如EventBus那樣簡潔。此外,如果你不注意保持Intent extras類型的一致,它還可能引發潛在的運行時/類型檢測錯誤。
使用EventBus不僅使代碼變得清晰,而且增強了類型安全(type-safe)。當用Intent傳遞數據時,在編譯時並不能檢查出所設的extra類型與收到時的類型一致。所以一個很常見的錯誤便是你或者你團隊中的其他人改變了Intent所傳遞的數據,但忘記了對全部的接收器(receiver)進行更新。這種錯誤在編譯時是無法被發現的,只有在運行時才會發現問題。
而使用EventBus所傳遞的消息則是通過你所定義的Event類。由於接收者方法是直接與這些類實例打交道,所以所有的數據均可以進行類型檢查,這樣任何由於類型不一致所導致的錯誤都可以在編譯時刻被發現。
另外就是你的Event類可以定義成任何類型。通常會為了表示事件而顯式地創建明確命名的類,你也通過EventBus發送/接收任何類。通過這種方法,你就不必受限於那些只能添加到Intent extras中的簡單數據類型了。例如,你可以發送一個和ORM模型類實例,並且在接收端直接處理與ORM操作相關的類實例。
2.3 EventBus使用
2.3.1基本使用
2.3.2具體案例
2.3.3 onEvent函數使用解析
前一篇給大家裝簡單演示了EventBus的onEventMainThread()函數的接收,其實EventBus還有另外有個不同的函數,他們分別是:
1、onEvent
2、onEventMainThread
3、onEventBackgroundThread
4、onEventAsync
這四種訂閱函數都是使用onEvent開頭的,它們的功能稍有不同,在介紹不同之前先介紹兩個概念:告知觀察者事件發生時通過EventBus.post函數實現,這個過程叫做事件的發布,觀察者被告知事件發生叫做事件的接收,是通過下面的訂閱函數實現的。
onEvent:如果使用onEvent作為訂閱函數,那么該事件在哪個線程發布出來的,onEvent就會在這個線程中運行,也就是說發布事件和接收事件線程在同一個線程。使用這個方法時,在onEvent方法中不能執行耗時操作,如果執行耗時操作容易導致事件分發延遲。
onEventMainThread****:如果使用onEventMainThread作為訂閱函數,那么不論事件是在哪個線程中發布出來的,onEventMainThread都會在UI線程中執行,接收事件就會在UI線程中運行,這個在Android中是非常有用的,因為在Android中只能在UI線程中更新UI,所以在onEvnetMainThread方法中是不能執行耗時操作的。
onEventBackground:如果使用onEventBackgrond作為訂閱函數,那么如果事件是在UI線程中發布出來的,那么onEventBackground就會在子線程中運行,如果事件本來就是子線程中發布出來的,那么onEventBackground函數直接在該子線程中執行。
onEventAsync****:使用這個函數作為訂閱函數,那么無論事件在哪個線程發布,都會創建新的子線程在執行onEventAsync.
2.3.4 EvetntBus3.0使用解析
注冊一般是在 onCreate
和 onStart
里注冊,盡量不要在 onResume
,可能出現多次注冊的情況,比如下面這個異常:
取消注冊 要寫到 onDestroy
方法里,不要寫到 onStop
里,有時會出現異常的哦
EventBus 3 和之前版本的 EventBus 不兼容,這里采用注解的方法來接收事件,四種注解 @Subscrible、@Subscrible(threadMode = ThreadMode.ASYNC)、@Subscribe(threadMode = ThreadMode.BACKGROUND)、@Subscribe(threadMode = ThreadMode.MAIN)分別對應之前的 onEvent()、onEventAsync()、onEventBackground()、onEventMainThread()。
EventBus 3 采用注解后,方法名沒有限制了,參數只有一個,和發送者 post 的參數對應配對,在未聲明 threadMode 時,默認的線程模式為 ThreadMode.POSTING,只有在該模式下才可以取消線程。
由於可在任何地方都可以 post 一個事件,那么在不同線程之間傳遞事件,比如在工作線程傳遞一個事件更新UI線程中的一個控件,則需要注意 threadMode 的切換。
如果遇到訂閱事件無法執行的情況,分析后發現是訂閱事件的 Activity 還未執行的原因。找到原因就好辦了,這時候就需要用到 postSticky。
2.4EventBus源碼解析
2.4.1 register
EventBus.getDefault().register(this); EventBus.getDefault()其實就是個單例,和我們傳統的getInstance一個意思:
使用了雙重判斷的方式,防止並發的問題,還能極大的提高效率。
register公布給我們使用的有4個:
調用內部類SubscriberMethodFinder的findSubscriberMethods方法,傳入了subscriber 的class,以及methodName,返回一個List<SubscriberMethod>。
那么不用說,肯定是去遍歷該類內部所有方法,然后根據methodName去匹配,匹配成功的封裝成SubscriberMethod,最后返回一個List。
繼續回到register:
到此,我們register就介紹完了。
你只要記得一件事:掃描了所有的方法,把匹配的方法最終保存在subscriptionsByEventType(Map,key:eventType ;value:CopyOnWriteArrayList<Subscription> )中;
eventType是我們方法參數的Class,Subscription中則保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了執行改方法所需的一切。
2.4.2 post
到此,我們完整的源碼分析就結束了,總結一下:register會把當前類中匹配的方法,存入一個map,而post會根據實參去map查找進行反射調用。分析這么久,一句話就說完了~~
其實不用發布者,訂閱者,事件,總線這幾個詞或許更好理解,以后大家問了EventBus,可以說,就是在一個單例內部維持着一個map對象存儲了一堆的方法;post無非就是根據參數去查找方法,進行反射調用。
阿里P7Android高級架構進階視頻免費學習請點擊:https://space.bilibili.com/474380680
參考https://www.cnblogs.com/fuyaozhishang/p/9944653.html
https://www.jianshu.com/p/36cb80026dc9