前言
本文翻译自【Understanding LiveData made simple】,详细介绍了 liveData 的使用。感谢作者 Elye。水平有限,欢迎指正讨论。
Architecture Components 可以说是 Google 提供给 Android 开发者的一大福利。LiveData 是其中的一个主要组件,下面我们一起看下该怎么使用好 LiveData
。
如果你之前没了解过 Architecture Components
,可以看下作者的另一篇文章:Android Architecture Components for Dummies in Kotlin (50 lines of code)。
在上一篇文章中,作者提到了 ViewModel
和 LiveData
,其中 LiveData 是用来从 ViewModel 层向 View 层传递数据。但当时并没有完整地介绍 LiveData 的作用,现在我们来详细看下 LiveData 的定义和使用。
那么,LiveData 有什么特别的地方呢?
正文
什么是 LiveData
官方 定义是:
LiveData 是一个可被观察的数据持有类。与普通的被观察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命周期感知的,也就是说,它能感知其它应用组件(Activity,Fragment,Service)的生命周期。这种感知能力可以确保只有处于 active 状态的组件才能收到 LiveData 的更新。详情可查看 Lifecycle。
这就是官方对 LiveData 的定义。
为了简单起见,我们先来看一些之前的开发习惯,以便更好地理解。
起源
当 Android 刚诞生的时候,大多数开发者写的代码都放在一个 Activity 中。
然而,把所有逻辑都放在一个 Activity 类中并不理想。因为 Activity 很难进行单元测试。
鉴于此,业界出现了MVC、MVP、MVVM 等开发架构,通过 Controller、Presenter、ViewModel 等分层抽离 Activity 中的代码。
这种架构能把逻辑从 View 层分离出来。然而,它的问题是 Controller、Presenter、ViewModel 等不能感知 Activity 的生命周期,Activity 的生命周期必须通知这些组件。
为了统一解决方案,Google 开始重视这个问题,于是 Architecture Components 诞生了。
其中 ViewModel 组件有一个特殊能力,我们不需要手动通知它们,就可以感知 Activity 的生命周期。这是系统内部帮我们做的事情。
除了 ViewModel 外,用于从 ViewModel 层暴露到 View 层的数据,也有生命周期感知的能力,这就是为什么叫做 LiveData
的原因。作为一个被观察者,它可以感知观察它的 Activity 的生命周期。
举例说明
为了更好地理解,下图将 LiveData 作为数据中心:
从上图可以看到,LiveData 的数据来源一般是 ViewModel,或者其它用来更新 LiveData 的组件。一旦数据更新后,LiveData 就会通知它的所有观察者,例如 Activity、Fragment、Service 等组件。但是,与其他类似 RxJava 的方法不同的是,LiveData 并不是盲目的通知所有观察者,而是首先检查它们的实时状态。LiveData 只会通知处于 Actie 的观察者,如果一个观察者处于 Paused 或 Destroyed 状态,它将不会受到通知。
这样的好处是,我们不需要在 onPause
或 onDestroy
方法中解除对 LiveData 的订阅/观察。此外,一旦观察者重新恢复 Resumed 状态,它将会重新收到 LiveData 的最新数据。
LiveData 的子类
LiveData
是一个抽象类,我们不能直接使用。幸运的是,Google 提供了一些其简单实现,让我们来使用。
MutableLiveData
MutableLiveData 是 LiveData 的一个最简单实现,它可以接收数据更新并通知观察者。
例如:
// Declaring it
val liveDataA = MutableLiveData<String>()
// Trigger the value change
liveDataA.value = someValue
// Optionally, one could use liveDataA.postValue(value)
// to get it set on the UI thread
观察 LiveData 也很简单,下面展示了在 Fragment 中订阅 LiveDataA
:
class MutableLiveDataFragment : Fragment() {
private val changeObserver = Observer<String> { value ->
value?.let { txt_fragment.text = it }
}
override fun onAttach(context: Context?) {
super.onAttach(context)
getLiveDataA().observe(this, changeObserver)
}
// .. some other Fragment specific code ..
}
结果如下,一旦 LiveDataA
数据发生变化(例如7567和6269),Fragment 就会收到更新。
上面的代码中有这么一行:
getLiveDataA().observe(this, changeObserver)
这就是订阅 LiveData 的地方,但是并没有在 Fragment pausing 或 terminating 时解除订阅。
即使我们没有解除订阅,也不会有什么问题。看下面的例子,当 Fragment 销毁时,LiveData 不会因为产生一个新数据(1428)通知给 inactive 的 Fragment 而崩溃(Crash)。
同时,也可以看到当 Fragment 重新 active 时,将会收到最新的 LiveData 数据:1428。
Transformations#map()
我们一般定义一个 Repository 负责从网络或数据库获取数据,在将这些数据传递到 View 层之前,可能需要做一些处理。
如下图,我们使用 LiveData 在各个层之间传递数据:
我们可以使用 Transformations#map() 方法将数据从一个 LiveData 传递到另一个 LiveData。
class TransformationMapFragment : Fragment() {
private val changeObserver = Observer<String> { value ->
value?.let { txt_fragment.text = it }
}
override fun onAttach(context: Context?) {
super.onAttach(context)
val transformedLiveData = Transformations.map(
getLiveDataA()) { "A:$it" }
transformedLiveData.observe(this, changeObserver)
}
// .. some other Fragment specific code ..
}
结果如下所示,以上代码将 LiveDataA
的数据(5116)进行处理后变为 A:5116。
使用 Transformations#map()
有助于确保 LiveData 的数据不会传递给处于 dead 状态的 ViewModel 和 View。
这很酷,我们不用担心解除订阅。
下面来看下 Transformations#map()
的源码:
@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
这里用到了 LiveData 的另一个子类 MediatorLiveData
。接下来看一看这是个什么东西。
MediatorLiveData
从 Transformations#map()
源码中可以看到,MediatorLiveData 有一个 MediatorLiveData#addSource() 方法,这个方法改变了数据内容。
也就是说,我们可以通过 MediatorLiveData
将多个 LiveData 源数据集合起来,如下图所示:
代码如下:
class MediatorLiveDataFragment : Fragment() {
private val changeObserver = Observer<String> { value ->
value?.let { txt_fragment.text = it }
}
override fun onAttach(context: Context?) {
super.onAttach(context)
val mediatorLiveData = MediatorLiveData<String>()
mediatorLiveData.addSource(getliveDataA())
{ mediatorLiveData.value = "A:$it" }
mediatorLiveData.addSource(getliveDataB())
{ mediatorLiveData.value = "B:$it" }
mediatorLiveData.observe(this, changeObserver)
}
// .. some other Fragment specific code ..
}
这样,Fragment 就可以同时接收到 LiveDataA
和 LiveDataB
的数据变化,如下图所示:
有一点需要注意的是:当 Fragment 不 再处于 active
状态时,如果 LiveDataA
和 LiveDataB
的数据都发生了变化,那么当 Fragment 重新恢复 active 状态时,MediatorLiveData
将获取最后添加的 LiveData 的数据发送给 Fragment,这里即 LiveDataB
。
从上图可以看到,当 Fragment 恢复活动状态时,它就会收到 LiveDataB
的最新数据,无论 LiveDataB
变化的比 LiveDataA
变化的早或晚。从上面代码可以看到,这是因为 LiveDataB
是最后被添加到 MediatorLiveData
中的。
Transformations#switchMap
上面的示例中展示了我们可以同时监听两个 LiveData 的数据变化,这是很有用的。但是,如果我们想要手动控制只监听其中一个的数据变化,并能根据需要随时切换,这时应怎么办呢?
答案是:Transformations#switchMap(),Google 已经为我们提供了这个方法。它的定义如下:
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
这个方法用来添加一个新数据源并相应地删除前一个数据源。因此 MediatorLiveData
只会包含一个 LiveData 数据源。这个控制开关也是一个 LiveData。整个过程如下所示:
使用方法如下:
class TransformationSwitchMapFragment : Fragment() {
private val changeObserver = Observer<String> { value ->
value?.let { txt_fragment.text = it }
}
override fun onAttach(context: Context?) {
super.onAttach(context)
val transformSwitchedLiveData =
Transformations.switchMap(getLiveDataSwitch()) {
switchToB ->
if (switchToB) {
getLiveDataB()
} else {
getLiveDataA()
}
}
transformSwitchedLiveData.observe(this, changeObserver)
}
// .. some other Fragment specific code ..
}
这样,我们就能很容易地控制用哪个数据来更新 View 视图,如下所示,当正在观察的 LiveData 发生变化,或者切换观察的 LiveData 时,Fragment 都会收到数据更新。
一个实际的使用场景是,我们可以通过特定设置(如用户登录 session)的不同数据源,来处理不同的业务逻辑。
源码地址
以上示例代码可以在作者的 Github 上找到:https://github.com/elye/demo_android_livedata_illustration。
下载源码查看,能更好地理解。
更多示例
如果订阅了一个 LiveData,但又不想收到数据更新的通知,可以参考一下文章:
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)。