一、架構指南
在日常的開發中,我們經常會講到 MVC、MVP、MVVM 等多種開發模式,這其實都是應用架構的不同呈現方式,你目前又是使用的什么應用架構呢?
一個好的架構,其至少應該遵循兩個原則
- 關注點分離。關注點分離指的是架構中的每一層應只專注于實現某一特定目的。一種常見的錯誤就是在
Activity
或Fragment
中編寫所有代碼(例如,直接在界面層完成網絡請求),這種基于界面的類應僅包含與系統和用戶交互的邏輯,你應該使這些類盡可能保持精簡,這樣可以避免許多與生命周期相關的問題 - 通過模型驅動界面。模型是指負責處理應用數據的組件,模型應獨立于應用的界面層和其它應用組件,這樣才能不受應用的生命周期以及相關的關注點的影響
關于第一點。對于一個移動設備來說,其擁有的資源是固定且極其有限的,系統可能會隨時終止某些應用進程以便為前臺進程騰出內存空間。而且,即使是對于前臺進程來說,我們也并非擁有Activity
和Fragment
的完全所有權,系統也可能會隨時因為內存空間不足或者系統配置更改等意外情況而銷毀它們。做到關注點分離,就是將各個層次的職責劃分開,避免將用戶數據和界面的生命周期強綁定,這樣當意外情況發生時用戶數據才不會隨之一起被銷毀
關于第二點。通過模型驅動界面,即界面對模型來說應該是相當于一個觀察者而獨立存在,界面不應該直接持有數據。界面通過觀察數據的變化來驅動自身,模型也可以通過改變自身來驅動界面更改。這里指的模型最好是持久性模型,即模型的生命周期需要比界面甚至應用進程更加長,典型代表即 Jetpack 中的 ViewModel 和 Room。這樣當有意外情況發生時,用戶也不會丟失數據,我們可以在隨后就為用戶恢復數據
Google 推薦的應用架構圖如下所示
當中,每個組件僅依賴于其下一級的組件。ViewModel 就是關注點分離原則的一個具體實現,是作為用戶數據的承載體和處理者而存在的,Activity/Fragment 僅依賴于 ViewModel,ViewModel 就用于響應界面層的輸入和驅動界面層變化,Repository 用于為 ViewModel 提供一個單一的數據來源及數據存儲域,Repository 可以同時依賴于持久性數據模型和遠程服務器數據源
二、LiveData 的優勢
本文想要討論的就是 ViewModel 所包含的 LiveData
從 Google 官方推薦的應用架構圖可以看到,LiveData 是包含在 ViewModel 中的。LiveData 是一種可觀察的數據存儲器,Activity/Fragment 是觀察者,LiveData 是被觀察者。LiveData 具有生命周期感知能力,當我們向 LiveData 注冊了一個和 LifecycleOwner 相綁定的 Observer 時,如果 LifecycleOwner 的生命周期處于STARTED
或 RESUMED
狀態,則認為該觀察者當前處于活躍狀態,此時 LiveData 才會向觀察者發送事件通知,非活躍狀態的觀察者不會收到任何事件通知。且當 LifecycleOwner 的狀態變為DESTROYED
時,LiveData 會自動解除和觀察者之間的綁定關系,以防止內存泄漏和過多的內存消耗。所以說,LiveData 具有生命周期感知能力,Activity/Fragment 無需和 LiveData 創建明確且嚴格的依賴路徑
ViewModel 和 LiveData 可以看做是對關注點分離和通過模型驅動界面原則的一個共同實現,ViewModel 提供了讓用戶數據獨立于界面而存在的能力,LiveData 提供了安全地通知并驅動界面變化的能力
三、LiveData 的缺陷
LiveData 的設計初衷就決定了其具有以下三點缺陷(或者說特性):
- 只在 Observer 至少處于 STARTED 狀態時才能收到事件通知。Activity 只有在 onStart 后和 onStop 前才能收到事件通知
- LiveData 是黏性的。假設存在一個靜態的 LiveData 變量,且已經包含了數據,對其進行監聽的 Activity 都會收到其當前值的回調通知,即收到了黏性消息。這個概念就類似于 EventBus 中的 StickyEvent
- 中間值可以被新值直接掩蓋。當 Activity 處于后臺時,如果 LiveData 先后接收到了多個值,那么當 Activity 回到前臺時也只會收到最新值的一次回調,中間值直接被掩蓋了,Activity 完全不會感受到中間值的存在
以上三點特性都是由于界面層的特點來決定的:
- 當界面處于后臺時,此時就完全沒有必要更新界面,因為此時界面對用戶來說完全不可見,且界面有可能再也沒有機會回到前臺了,所以只有當界面回到前臺時更新操作才是有意義的
- 當界面被意外銷毀后,我們需要根據已有的數據來進行界面重建,所以 LiveData 被設計為黏性的
- 對于 LiveData 所代表的界面狀態值來說,我們往往需要的只是其最新狀態,不需要處理中間值,所以 LiveData 的中間值可以被新值直接掩蓋
四、LiveData 作為消息通知組件
如果將 LiveData 單純地作為界面層狀態更新的載體來看待的話,那么以上三點特性就挺合情合理的了。但如果我們是將 LiveData 作為應用全局的消息通知組件的話,這三個特性就會給我們帶來困擾了
相信很多開發者都嘗試過將一個 LiveData 實例聲明為靜態變量,然后多個 Activity 通過同時監聽該 LiveData 來實現數據通信。這種方式的優點是:能夠以非常簡單的方式來實現跨頁面通信,同時也保障了生命周期安全。缺點是:在 Activity 處于 onStop 狀態時無法收到通知,且會收到黏性消息這種臟數據
在 Activity 處于 onStop 狀態時收到通知有什么意義呢?收到了黏性消息又會導致什么問題呢?這可以通過假設一個需求來說明
假設當前你的 App 包含一個圈子列表頁面,每個圈子 item 包含了一個按鈕用于改變對此圈子的關注狀態,當點擊關注后就會向用戶展示一個幾百毫秒的動畫效果。點擊 item 可以跳轉到圈子詳情頁,在詳情頁也包含一個按鈕用于改變圈子的關注狀態
現在,產品要求兩個頁面間的關注狀態是要能夠實時統一的,即在圈子詳情頁改變關注狀態后,圈子列表頁面也要跟著一起改變關注狀態。為了實現這個效果,可以聲明一個全局的靜態變量來實現跨頁面通知圈子關注狀態的變化
object FocusRepository {
//String 表示圈子ID,Boolean 表示對該圈子的關注狀態
val focusLiveData = MutableLiveData<Pair<String, Boolean>>()
}
class CircleListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_circle_list)
//建立監聽
FocusRepository.focusLiveData.observe(this, Observer {
})
}
}
class CircleDetailsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_circle_details)
onCircleFocusStateChanged("100", true)
}
private fun onCircleFocusStateChanged(circleId: String, focused: Boolean) {
FocusRepository.focusLiveData.value = Pair(circleId, focused)
}
}
這種方式就會導致三個問題:
- 當用戶在 CircleDetailsActivity 改變了圈子的關注狀態后返回,CircleListActivity 從后臺回到前臺后才會收到 focusLiveData 的事件通知,此時才會觸發執行動畫。而在這種情況下我們并不希望用戶看到動畫效果,而是希望能夠在 CircleListActivity 改變關注狀態的同時就實時在觸發動畫了。此時使用 LiveData 就無法滿足我們的需求了,LiveData 不支持在 Activity 處于 onStop 狀態時下發通知
- 如果在 CircleDetailsActivity 先后改變了多個圈子的關注狀態的話,那么就會導致另一個問題:中間值被最新值直接掩蓋了。這也是由于LiveData 不支持在 Activity 處于 onStop 狀態時下發通知導致的
- 在 focusLiveData 已經有值的情況下,當用戶第一次打開 CircleListActivity 時,就會收到 focusLiveData 的回調通知。而此時 CircleListActivity 的數據會從服務器獲取,可以保證是最新的,并不需要本地值的回調通知,此時 focusLiveData 就相當于臟數據了。這種情況下,LiveData 也會給我們帶來困擾,其黏性消息其實就相當于臟數據了
五、EventLiveData
考慮到 LiveData 不那么適合用做應用全局的消息通知組件,所以我就基于其源碼實現了一個改良版的 EventLiveData,以此來解決 LiveData 的缺陷。EventLiveData 在使用上基本 LiveData 一樣,我只是對其進行了功能擴展
發送消息:
val eventLiveData = EventLiveData<String>()
//主線程調用
eventLiveData.setValue("leavesC")
//子線程調用
eventLiveData.postValue("leavesC")
//任意線程都可以調用,內部會自動判斷線程
eventLiveData.submitValue("leavesC")
不接收黏性消息:
//不接收黏性消息
//在 onResume 之后和 onDestroy 之前均能收到 Observer 回調
eventLiveData.observe(LifecycleOwner, Observer {
})
//不接收黏性消息
//在 onCreate 之后和 onDestroy 之前均能收到 Observer 回調
eventLiveData.observe(LifecycleOwner, Observer {
}, false)
接收黏性消息:
//接收黏性消息
//在 onResume 之后和 onDestroy 之前均能收到 Observer 回調
eventLiveData.observeSticky(LifecycleOwner, Observer {
})
//接收黏性消息
//在 onCreate 之后和 onDestroy 之前均能收到 Observer 回調
eventLiveData.observeSticky(LifecycleOwner, Observer {
}, false)
不和生命周期綁定:
//不接收黏性消息
eventLiveData.observeForever(Observer {
})
//接收黏性消息
eventLiveData.observeForeverSticky(Observer {
})
六、實現原理
EventLiveData 是基于 LiveData 的源碼來改造實現的,在理解了 LiveData 的設計理念和實現原理后來進行自定義其實就非常簡單了,這里就簡單說下我的實現思路
LiveData 內部包含一個 mVersion
變量用來標記當前值的版本,即值的新舊程度,當外部傳遞了新值時(不管是 setValue 還是 postValue),mVersion 均會遞增 +1
@MainThread
private fun setValue(value: T) {
assertMainThread(
"setValue"
)
mVersion++
mData = value
dispatchingValue(null)
}
同時 ObserverWrapper 內部包含一個 mLastVersion
用于標記 Observer 內最后一個被回調的 value 的新舊程度
private abstract class ObserverWrapper {
//外部傳進來的對 LiveData 進行數據監聽的 Observer
final Observer<? super T> mObserver;
//用于標記 mObserver 是否處于活躍狀態
boolean mActive;
//用于標記 Observer 內最后一個被回調的 value 的新舊程度
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<? super T> observer) {
mObserver = observer;
}
}
而 considerNotify
方法會根據 mLastVersion 的大小來決定是否需要向 Observer 回調值,那么我們只要控制 Observer 的 mLastVersion 的初始值大小不就可以避免舊值的通知了嗎?
private void considerNotify(ObserverWrapper observer) {
···
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
再然后,LifecycleBoundObserver 的 shouldBeActive()
方法就限制了只有當 Lifecycle 的當前狀態是 STARTED 或者 RESUMED 時才進行數據回調,那么我們只要改變此限制條件,就可以增大 Observer 的有效生命周期范圍了
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
}
七、引入依賴
EventLiveData 已托管到 jitpack,可以直接遠程依賴。GitHub 地址:https://github.com/leavesCZY/EventLiveData
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.leavesCZY:EventLiveData:1.0.2'
}