Android Jetpack 架構元件之ViewModel 與 LiveData

語言: CN / TW / HK

前言

ViewModel 和 LiveData 作為JetPack 中架構元件中重要的元件,搭配好使用能大大提升開發效率。

ViewModel

ViewModel 具有生命週期意識,會自動儲存和管理 UI 相關的資料,即使裝置配置發生變化後資料還會存在,ViewModel的出現會讓讓Activity專注於檢視控制器的角色,業務邏輯交給ViewModel,很好地將檢視與邏輯分離開來。不止於此,ViewModel還能在Fragment之間通訊。

ViewModel 在Activity 之間通訊

ViewModel 能在Framgment 通訊,這很好理解,因為他們有共同的載體Activity,就能建立相同的ViewModel例項:

``` private val shareViewModel by lazy { ViewModelProvider(requireActivity()).get(ShareViewModel::class.java) }

``` 每點選一次HomeFragment我都會延遲更新資料。

``` class ShareViewModel :ViewModel() {

val data = MutableLiveData<String>()

var count = 0

fun getData(){

    Handler(Looper.getMainLooper()).postDelayed({
        count++
        data.value = "from the activity$count"

    },2000)
    data.value = "from the activity$count"
}

} ```

然後每個Fragment 監聽自己的資料來源就可以:

shareViewModel.data.observe(viewLifecycleOwner, Observer { textView.text = it })

5.gif

然而好像實際開發中,跨Activity共享資料的情況更多,這個時候又該怎麼處理呢?

ViewModelProvider接收的是ViewModelStoreOwner子類物件,Activity 和 Fragment都實現了ViewModelStoreOwner介面。想要跨Activity共享資料,我們讓Application實現ViewModelStoreOwner介面,通過Application來建立ViewModel不就能實現跨Activity通訊的問題,程式碼如下:

``` class MyApp : Application(), ViewModelStoreOwner {

private val TAG = "MyApp"


private val appViewModelStore: ViewModelStore by lazy {
    ViewModelStore()
}


override fun onCreate() {
    super.onCreate()
    AppScope.init(this)

}

override fun onTerminate() {
    super.onTerminate()
    appViewModelStore.clear()
}

override fun getViewModelStore(): ViewModelStore {

    return appViewModelStore
}

} ```

其中 AppScope:

``` object AppScope { private lateinit var myApp: MyApp fun init(application: MyApp){ myApp = application }

/**
 * 獲取程序共享的ViewModel
 */
fun <T : ViewModel?> getAppScopeViewModel(modelClass: Class<T>): T {
    return ViewModelProvider(myApp).get(modelClass)
}

} ``` 這裡我們做個小Demo,SecActivity 啟動編輯頁面ThirdActivity,編輯成功後,資料返回 SecActivity。 它們使用共同的EditViewModel,建立方式如下:

private val editViewModel: EditViewModel by lazy { AppScope.getAppScopeViewModel(EditViewModel::class.java) } 監聽資料變化 editViewModel.inputData.observe(this, Observer { it.let { tv?.text = it } })

執行一把:

1.gif

進入編輯頁面後,編輯5689,關閉頁面,資料確實傳遞到了SecActivity,共享資料成功,但是也帶來了新的問題,重新進入SecActivity,依然接收了原來了的資料,這是LiveData支援粘性事件導致的,接下來我們談談LiveData。

LiveData

LiveData 天生支援粘性事件,google 設計LiveData 並不是為了粘性而設計,但卻有粘性的效果。

LiveData 取消粘性事件

LiveData支援粘性事件的原因是 observer version 與 LiveData 的version沒有保持一致性,Observer 每次的初始值為-1,這樣因為

``` if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData);

```

只要我們重寫Observer,讓它的Version和LiveData的version保持一致,將不會把把歷史資料回撥新的註冊者,也就取消了粘性事件,重新定義WrapperObserver類: ``` /* * Observer 包裝類 * 通過改變mLastVersion的值就能做到非粘性事件 * / class WrapperObserver( var liveData: NoStickyLiveData, var observer: Observer, sticky: Boolean, observerForever: Boolean = false ) : Observer {

private val TAG = "WrapperObserver"

//標記該liveData已經發射幾次資料了,用以過濾老資料重複接收
private var mLastVersion = if (sticky) {
    -1
} else {
    liveData.getVersion()
}


override fun onChanged(t: T) {

    if (mLastVersion >= liveData.getVersion()) {
        return
    }
    mLastVersion = liveData.getVersion()
    observer?.onChanged(t)

}

} ```

自定義LiveData: ``` class NoStickyLiveData( private val eventName: String = "default", private val map: ConcurrentHashMap>? = null, private val sticky: Boolean = false ) : MutableLiveData() {

private val TAG = "StickyLiveData"

private var mVersion = 0

/**
 * 記錄 繫結的Observer
 */
private val mHashMap = ConcurrentHashMap<String, Observer<*>>()

fun getVersion(): Int {
    return mVersion
}


override fun setValue(value: T) {
    mVersion++
    super.setValue(value)

}

override fun postValue(value: T) {
    mVersion++
    super.postValue(value)

}

override fun observeForever(observer: Observer<in T>) {

    val observerExit = mHashMap[eventName]
    if (observerExit != null) {
        removeObserver(observerExit as Observer<in T>)
    }
    val wrapperObserver = WrapperObserver(this, observer, sticky, true)
    mHashMap[eventName] = wrapperObserver
    super.observeForever(wrapperObserver)

}

override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
    observerSticky(owner, observer, sticky)
}

private fun observerSticky(owner: LifecycleOwner, observer: Observer<in T>, sticky: Boolean) {
    super.observe(owner, WrapperObserver(this, observer, sticky))
    owner.lifecycle.addObserver(LifecycleEventObserver { source, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            map.let {
                if (!sticky) {
                    it?.remove(eventName)
                }

            }
        }
    })


}

private fun observerSticky(observer: Observer<in T>, sticky: Boolean) {
    super.observeForever(WrapperObserver(this, observer, sticky))

}

} 監聽: editViewModel.inputDataNoSticky.observe(this, Observer { it.let { tv?.text = it } }) ``` 效果如下圖:

2.gif

LiveData定製事件匯流排

LiveData強大的事件分發能力,可以根據LiveData來做一個事件匯流排,用來全域性分發事件。 並且支援粘性事件和非粘性事件兩種方式。 傳送事件: LiveDataBus.withSticky<String>("edit").setValue("********")

監聽: LiveDataBus.withSticky<String>("edit").observe(this, Observer { it.let { tv?.text = it } })

LiveDataBus 原始碼:

``` /* * 訊息匯流排 * 跨 activity / object LiveDataBus {

private val mHashMap = ConcurrentHashMap<String, NoStickyLiveData<*>>()

/**
 * 不帶粘性事件
 */
fun <T> with(eventName: String): NoStickyLiveData<T> {
    var liveData = mHashMap[eventName]
    if (liveData == null) {
        liveData =
            NoStickyLiveData(
                eventName,
                mHashMap as ConcurrentHashMap<String, NoStickyLiveData<T>>
            )
        mHashMap[eventName] = liveData
    }
    return liveData as NoStickyLiveData<T>
}

/**
 * 帶粘性事件的
 */

fun <T> withSticky(eventName: String): NoStickyLiveData<T> {
    var liveData = mHashMap[eventName]
    if (liveData == null) {
        liveData =
            NoStickyLiveData(
                eventName,
                mHashMap as ConcurrentHashMap<String, NoStickyLiveData<T>>,
                true
            )
        mHashMap[eventName] = liveData
    }
    return liveData as NoStickyLiveData<T>
}

} ``` LiveDataBus 粘性事件:

3.gif

LiveDataBus 非粘性事件:

4.gif

總結

ViewModel和LiveData是JetPack中重量級元件,使用頻率之高,熟練掌握必將大大簡化我們的開發任務。 原始碼:https://github.com/ThirdPrince/AppScopViewModel