Jetpack Architecture - LiveData

2021-03-31  本文已影响0人  Whyn

简介

LiveData 是一个数据持有类,基于观察者模式机制,使能数据监测功能。具体来说,被LiveData包裹的数据可以被其他观察者订阅,当该数据发生变化时,观察者能立即得到通知,获取到最新的值。另外,LiveData具备生命周期感知功能,因此只有当系统组件(比如ActivityFragmentServiceLifecycleOwner)处于激活状态(即状态为Lifecycle.State.STARTEDLifecycle.State.RESUMED)时,才能接收到数据更新通知,而当系统组件处于销毁状态(即Lifecycle.State.DESTROY)时,会自动取消订阅,因此可以有效避免内存泄漏问题。这也是LiveData最大的两个特点。

特性

使用LiveData有如下几个优点:

依赖导入

要使用LiveData,只需添加如下依赖:

dependencies {
    def lifecycle_version = "2.3.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
}

如果项目已引入appcompat包,则无需再单独引入LiveData

基本使用

LiveData最大的特性就是使能数据具备观察通知功能,并且支持任意数据更新通知,因此只需将数据交给LiveData持有,且需要数据的组件注册一下LiveData进行监听即可,具体如下所示:

// 数据类
data class User(val name: String, val age: Int)

// LiveData 设置数据(无需考虑所在线程)
fun <T> MutableLiveData<T>.updateValue(value: T) {
    fun isMainThread(): Boolean {
        return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            Looper.getMainLooper().isCurrentThread
        } else {
            Looper.getMainLooper() == Looper.myLooper()
        }
    }

    if (isMainThread()) {
        this.value = value
    } else {
        this.postValue(value)
    }
}

// 系统组件
class MainActivity : AppCompatActivity() {

    private companion object {
        private const val TAG = "MainActivity"
    }

    // LiveData: 数据持有类
    private val reactiveData by lazy {
        MutableLiveData<User>()
    }

    // 更新数据
    private fun updateData() {
        val value = (1..60).random()
        val user = User("user:$value", value)
        this.reactiveData.updateValue(user)
        Log.v(TAG, "update data: $user")

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使能数据监听
        this.reactiveData.observe(this) {
            // 将最新的数据渲染到界面上
            this.tv.text = it.toString()
        }

        // 点击更新数据
        this.btn.setOnClickListener {
            this.updateData()
        }
    }
}

LiveData是一个抽象类,其主要的子类有MutableLiveDataMediatorLiveData,通常使用MutableLiveData即可:

public abstract class LiveData<T> {...}
public class MutableLiveData<T> extends LiveData<T> {...}
public class MediatorLiveData<T> extends MutableLiveData<T> {...}

上述例子定义一个数据类User,然后在系统组件MainActivity中持有一个LiveData实例reactiveData,可以使能对User数据类的监听功能,最后在onCreate方法中采用LiveDate#observe(LifecycleOwner, Observer)MainActivity注册数据监听功能,这样,后续如果reactiveData内部数据User被修改了,则MainActivity就能监听到,并在Observer#onChanged(T)回调方法中获取得到最新值。

:前文已介绍过,LiveData具备页面生命周期感知功能,只有在页面组件处于激活状态时,才能接收数据更新通知,而如果需要实现一直接收LiveData数据更新通知,无论当前页面处于哪种状态,则可以使用LiveData#observeForever(Observer)方法进行注册,需要注意的是,当组件退出时,需要手动调用LiveData#removeObserver(Observer)方法移除组件监听,避免组件被持有,造成内存泄漏。

LiveData提供了两种方法更新数据:LiveData#setValue(T)LiveData#postValue(T),其中:

之所以提供了两种方法更新数据,是为了保证组件永远在主线程中获取到最新值(即Observer#onChanged(T)回调永远发生在主线程中),可以直接渲染界面。

上述例子为MutableLiveData添加了一个扩展函数MutableLiveData#updateValue(T),让我们修改数据时无需考虑所在线程,操作更加方便。

ViewModel 结合 LiveData

LiveData主要是使能数据监听功能,而ViewModel主要用于持有数据,保证数据在相关组件的生命周期内唯一且一直可用。这两者结合使用,就可以很好的让系统组件获取到对应的数据且能自动监听到数据的修改,本质上其实就是实现了「响应式数据」,在数据更新后,可以自动将最新的数据直接渲染到界面上,解耦了数据与界面,开发过程中,只需关注数据操作即可,大大降低开发复杂度。

从 Android 官方推荐的 MVVM 架构图可以更清晰的知道两者的关系:

android MVVM architecture

可以看到,在 MVVM 架构中,ViewModel内部持有一个或多个LiveData实例,每个LiveData会使能内部数据更新通知功能,因此,系统组件只需获取其ViewModel内部的相关LiveData,注册监听数据,这样就可以自动获取最新数据并直接渲染到界面上。

举个例子:我们更改下前面的例子,将LiveData放置到一个ViewModel中,并将数据操作等方法置于ViewModel中,然后让系统组件MainActivityViewModel中获取数据并进行监听。具体步骤如下:

  1. 自定义一个ViewModel,提供数据操作方法:

    class UserViewModel : ViewModel() {
        private companion object {
            private const val TAG = "UserViewModel"
        }
    
        // LiveData
        val reactiveData by lazy {
            MutableLiveData<User>()
        }
    
        // 更新数据
        fun updateData() {
            val value = (1..60).random()
            val user = User("user:$value", value)
            this.reactiveData.updateValue(user)
            Log.v(TAG, "update data: $user")
        }
    }
    
  2. 系统组件创建所需数据相应的ViewModel,获取内部LiveData实例并注册数据监听功能:

    // 系统组件
    class MainActivity : AppCompatActivity() {
    
        // ViewModel
        private val userViewModel by viewModels<UserViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // 使能数据监听
            this.userViewModel.reactiveData.observe(this) {
                // 将最新的数据渲染到界面上
                this.tv.text = it.toString()
            }
    
            // 点击更新数据
            this.btn.setOnClickListener {
                this.userViewModel.updateData()
            }
        }
    }
    

以上,当我们点击界面按钮更新数据时,最新的数据会自动渲染到界面TextView上。

:上述例子中MainActivity可以直接获取得到ViewModel内部的LiveData,这样MainActivity可以直接使用这个LiveData实例设置更改数据,破坏了ViewModel数据的封装性,一种解决思路是让ViewModel向外只暴露一个只读LiveData,具体如下所示:

class UserViewModel : ViewModel() {
    // 对外暴露只读 LiveData
    val reactiveData: LiveData<User>
        get() = _reactiveData

    // 底层可读可写 LiveData
    private val _reactiveData by lazy {
        MutableLiveData<User>()
    }

    // 更新数据
    fun updateData() {
        val value = (1..60).random()
        val user = User("user:$value", value)
        this._reactiveData.updateValue(user)
        Log.v(TAG, "update data: $user")
    }
    ...
}

对外暴露的是LiveDataLiveDatasetValue(T)postValue(T)都是protected,因此即使外部获取到LiveData,也不能进行数据修改等操作。

合并 LiveData

通常一个LiveData持有一个数据,而当一个页面需要多个数据时,就需要注册到多个LiveData中,这样会导致页面组件代码臃肿,此时可借助MediatorLiveData,它可以合并多个LiveData实例,当任意一个LiveData底层数据改变时,都会通知到页面组件,而页面组件只需注册一次(即注册到MediatorLiveData中)。

举个例子:比如现在页面MainActivity需要两个数据源,而这个两个数据分别由liveData1liveData2持有,此时我们可以合并liveData1livedata2,然后MainActivity只需注册到合并的LiveData中,即可实现数据监听。具体步骤如下所示:

  1. 创建一个ViewModel,持有两个数据源liveData1liveData2,合并这两个LiveData并暴露给外部组件:

    inline val <reified T> T.TAG: String
        get() = "Why8n"
    
    class MyViewModel : ViewModel() {
    
        // 数据源1
        private val liveData1 = MutableLiveData<String>() 
    
        // 数据源2
        private val liveData2 = MutableLiveData<String>() 
    
        // 合并数据源(此处向外暴露为 LiveData 类型,外部只能进行读取,不能进行设置)
        val liveDataMerged: LiveData<String> = MediatorLiveData<String>().apply {
            // 完整写法
            this.addSource(this@MyViewModel.liveData1, object : Observer<String> {
                override fun onChanged(str: String?) {
                    this@apply.value = str
                }
            })
    
            // Lambda简化写法
            this.addSource(this@MyViewModel.liveData2) {
                this.value = it
            }
        }
    
        // 更新数据
        fun updateData() {
            val value = (0..100).random()
            if (value % 2 == 0) {
                Log.v(TAG, "liveData1 update: $value")
                this.liveData1.value = "liveData1 update: $value"
            } else {
                Log.v(TAG, "liveData2 update: $value")
                this.liveData2.value = "liveData2 update: $value"
            }
        }
    }
    

    其实就是通过MediatorLiveData#addSource(LiveData<T> source, Observer<? super S> onChanged)方法为MediatorLiveData添加合并数据源,当源数据source底层数据改变时,MediatorLiveData会得到通知,因此其onChanged函数会得到回调,而我们在onChanged函数中手动调用MediatorLiveData#setValue(T)source的最新值赋值给到MediatorLiveData,因此,注册到MediatorLiveData的外部页面组件就会得到通知...这样就完成了一个数据传递过程。

  2. 页面组件源码如下所示:

    class MainActivity : AppCompatActivity() {
    
        // ViewModel
        private val myViewModel by viewModels<MyViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            this.myViewModel.liveDataMerged.observe(this) {
                this.tv.text = it
                Log.v(TAG, "MainActivity receives: $it")
            }
    
            // 点击更新数据
            this.btn.setOnClickListener {
                this.myViewModel.updateData()
            }
        }
    }
    

    以上,当源数据liveData1liveData2任意一个数据更新时,MainActivity都能得到通知。

数据转换

有时候,LiveData的底层数据类型并不是页面组件所需的直接类型,当然我们可以通过在页面组件内手动将类型进行转换,但是其实LiveData本身已提供了一些数据转换操作,大致有如下几种:

参考

上一篇 下一篇

猜你喜欢

热点阅读