Android Jetpack 架构组件之ViewModel 与 LiveData

1,024 阅读4分钟

前言

v2-002fb8a8d679f26b205a6306343157b4_1440w.webp

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<T>(
    var liveData: NoStickyLiveData<T>,
    var observer: Observer<in T>,
    sticky: Boolean, observerForever: Boolean = false
) : Observer<T> {

    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<T>(
    private val eventName: String = "default",
    private val map: ConcurrentHashMap<String, NoStickyLiveData<T>>? = null,
    private val sticky: Boolean = false
) : MutableLiveData<T>() {

    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中重量级组件,使用频率之高,熟练掌握必将大大简化我们的开发任务。 源码:github.com/ThirdPrince…