简介
LiveData
是一个数据持有类,基于观察者模式机制,使能数据监测功能。具体来说,被LiveData
包裹的数据可以被其他观察者订阅,当该数据发生变化时,观察者能立即得到通知,获取到最新的值。另外,LiveData
具备生命周期感知功能,因此只有当系统组件(比如Activity
、Fragment
或Service
等LifecycleOwner
)处于激活状态(即状态为Lifecycle.State.STARTED
或Lifecycle.State.RESUMED
)时,才能接收到数据更新通知,而当系统组件处于销毁状态(即Lifecycle.State.DESTROY
)时,会自动取消订阅,因此可以有效避免内存泄漏问题。这也是LiveData
最大的两个特点。
特性
使用LiveData
有如下几个优点:
-
响应式数据:
LiveData
采用观察者模式,当底层数据改变时,会自动通知注册组件,注册组件可以直接将数据渲染到界面上。 -
具备生命周期感知:
LiveData
底层使用了Lifecycle
组件,因此可以感知系统组件(确切来说是LifecycleOwner
)生命周期,在系统组件销毁时,自动取消订阅,防止内存泄漏。 -
数据分发与否与组件当前状态相关:只有当系统组件处于激活状态时,数据更新才会进行通知,也即当系统组件处于后台或销毁状态时,即使数据改变了,也不会通知到相关系统组件,而当系统组件从后台转到前台时,
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
是一个抽象类,其主要的子类有MutableLiveData
和MediatorLiveData
,通常使用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)
,其中:
-
setValue(T)
:用于在主线程中更新数据 -
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
中,然后让系统组件MainActivity
从ViewModel
中获取数据并进行监听。具体步骤如下:
-
自定义一个
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") } }
-
系统组件创建所需数据相应的
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")
}
...
}
对外暴露的是LiveData
,LiveData
的setValue(T)
和postValue(T)
都是protected
,因此即使外部获取到LiveData
,也不能进行数据修改等操作。
合并 LiveData
通常一个LiveData
持有一个数据,而当一个页面需要多个数据时,就需要注册到多个LiveData
中,这样会导致页面组件代码臃肿,此时可借助MediatorLiveData
,它可以合并多个LiveData
实例,当任意一个LiveData
底层数据改变时,都会通知到页面组件,而页面组件只需注册一次(即注册到MediatorLiveData
中)。
举个例子:比如现在页面MainActivity
需要两个数据源,而这个两个数据分别由liveData1
和liveData2
持有,此时我们可以合并liveData1
和livedata2
,然后MainActivity
只需注册到合并的LiveData
中,即可实现数据监听。具体步骤如下所示:
-
创建一个
ViewModel
,持有两个数据源liveData1
和liveData2
,合并这两个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
的外部页面组件就会得到通知...这样就完成了一个数据传递过程。 -
页面组件源码如下所示:
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() } } }
以上,当源数据
liveData1
或liveData2
任意一个数据更新时,MainActivity
都能得到通知。
数据转换
有时候,LiveData
的底层数据类型并不是页面组件所需的直接类型,当然我们可以通过在页面组件内手动将类型进行转换,但是其实LiveData
本身已提供了一些数据转换操作,大致有如下几种:
-
Transformations.map()
:将源LiveData
中的数据转换成其他类型数据,然后包装到一个新的LiveData
中。Transformations#map()
函数的源码如下所示:@MainThread public static <X, Y> LiveData<Y> map( @NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { @Override public void onChanged(@Nullable X x) { result.setValue(mapFunction.apply(x)); } }); return result; }
map()
的源码很简单,其实内部就是创建了一个MediatorLiveData
,并绑定到了当前的源LiveData
中,当源LiveData
改变时,MediatorLiveData
就会接收到并将数据通过mapFunction
进行转换,这样外部组件就能接收到已转换的类型数据。举个例子:比如源数据为
LiveData<User>
,现在假设页面组件MainActivity
需要的数据为User
的 JSON 字符串类型,即LiveData<String>
类型,那么借助Transformations#map()
,就可以很轻易完成数据类型转换:class UserViewModel : ViewModel() { // 源数据 private val _reactiveData by lazy { MutableLiveData<User>() } // 目标 LiveData val jsonUser: LiveData<String> = Transformations.map(this._reactiveData) { user: User -> "{\"name\": \"${user.name}\", \"age\": ${user.age} }" } ... }
外部组件只需注册到目标
LiveData
即可:class MainActivity : AppCompatActivity() { private val userViewModel by viewModels<UserViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 注册到目标 LiveData this.userViewModel.jsonUser.observe(this) { this.tv.text = it.toString() } ... } }
以上,则当源数据
_reactiveData
底层数据改变时,目标LiveData
(即jsonUser
)会自动感知到,并自动将数据进行转换,通知给到页面组件,完成一轮数据自动转换并渲染过程。 -
Transformations#switchMap()
:将源LiveData
中的数据转换为一个新的LiveData
,当该新LiveData
数据改变时,外部组件也能监听到。switchMap()
和map()
的区别之处在于:switchMap()
的转换函数(即第二个参数)类型为Function<X, LiveData<Y>>
,而map()
函数的转换函数类型为Function<X, Y>
,因此,switchMap()
会将源LiveData
最新数据转换为一个新的LiveData
。switchMap()
的原理其实也很简单,与map()
函数一致,底层都是通过创建一个MediatorLiveData
作为桥接,具体源码如下所示:@MainThread public static <X, Y> LiveData<Y> switchMap( @NonNull LiveData<X> source, @NonNull final Function<X, LiveData<Y>> switchMapFunction) { // 创建一个 MediatorLiveData final MediatorLiveData<Y> result = new MediatorLiveData<>(); // 添加源 LiveData result.addSource(source, new Observer<X>() { LiveData<Y> mSource; @Override public void onChanged(@Nullable X x) { // 转换函数生成一个新的 LiveData LiveData<Y> newLiveData = switchMapFunction.apply(x); // 新旧为同一个 LiveData,则无需处理 if (mSource == newLiveData) { return; } // 移除旧的 LiveData if (mSource != null) { result.removeSource(mSource); } mSource = newLiveData; if (mSource != null) { // 添加新的 LiveData result.addSource(mSource, new Observer<Y>() { @Override public void onChanged(@Nullable Y y) { result.setValue(y); } }); } } }); return result; }
switchMap()
和map()
都是返回一个MediatorLiveData
,外部组件通过订阅该MediatorLiveData
即可间接监听到源LiveData
数据变更,switchMap()
由于转换函数每次都返回一个新的LiveData
,因此需要进行移除旧LiveData
和注册新LiveData
等操作,这些都已经由MediatorLiveData
默认处理了,因此,外部组件无需关心这些底层操作,直接注册到switchMap()
函数返回的MediatorLiveData
即可。举个例子:假设
UserViewModel
内部持有一个源LiveData<String>
,装载用户名称,现有一个接口getUser(String)
,可通过用户名称返回一个完整的用户信息,类型为LiveData<User>
,主页面需要获取该User
实例数据进行渲染。UserViewModel
的大致内容如下:class UserViewModel : ViewModel() { // 源 LiveData private val nameLiveData by lazy { MutableLiveData<String>() } fun getUser(name: String): LiveData<User> { val age = (1..60).random() val user = User(name, age) return MutableLiveData<User>(user) } }
由于
getUser()
每次都返回一个新的LiveData<User>
对象,因此,主页面MainActivity
不能直接注册到这个LiveData
中,在每一次调用getUser()
前,必须先手动移除旧的LiveData
,然后再注册到这个新的LiveData
中,这样才能继续完成数据监听功能,手动如此操作略显繁琐,此时可借助Transformations#switchMap()
函数,直接一步到位:class UserViewModel : ViewModel() { // 源 LiveData private val nameLiveData by lazy { MutableLiveData<String>() } // 目标 LiveData val userLiveData: LiveData<User> = Transformations.switchMap(this.nameLiveData) { name: String -> this.getUser(name) } private fun getUser(name: String): LiveData<User> { val age = (1..60).random() val user = User(name, age) return MutableLiveData<User>(user) } // 更新数据 fun updateData(name: String) { this.nameLiveData.value = name } }
通过
Transformations#switchMap()
函数,后续只要源LiveData
数据改变了,switchMap()
就会感知到,然后将最新的数据进行转换,生成一个新的LiveData
,主页面只需一次注册到switchMap()
返回的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.userLiveData.observe(this) { this.tv.text = it.toString() } // 点击更新数据 this.btn.setOnClickListener { this.userViewModel.updateData("user: ${(1..60).random()}") } } }
网友评论