Android Jetpack 学习笔记之 LiveData 源码解析
LiveData 是 Android Architecture Components (AAC)中的一个基础类,ViewModel、Room、DataBinding 等多个库用到 LiveData。理解 LiveData 是学习 AAC 的一个关键,本文带你分析 LiveData 源码看看它到底是如何实现的。
介绍
文档对 LiveData 的介绍是这样的:
LiveData is a data holder class that can be observed within a given lifecycle
短短一句介绍,细细体会却能发现包含很大信息量。
- 首先从结构上讲, LiveData 是 holder/wrapper,它持有数据
- 其次,LiveData 是观察者模式的应用,它是被观察的一方
- 最后,LiveData 能感知生命周期
下面结合 LiveData 源码分别就这几点进行分析。
源码解析
- 数据持有
- 数据变更
- 观察者模式
- 线程切换
- 生命周期感知
- 不变性
数据持有
1 | public abstract class LiveData<T> { |
仅从数据持有者的角度来看 LiveData 的话,它相当简单。LiveData 无非就是一个泛型类,可持有任意类型的数据。Android 应用中随便找个 ViewHolder ,代码可能都要比它要复杂。
数据变更
1 | public abstract class LiveData<T> { |
从字面意思上理解,LiveData 的 mVersion
是用于对持有的数据做版本控制。它的实际作用是用于标识 mData
是否发生变化:
- 每次调用
setValue()
时mVersion
加1 - 通过
mLastVersion >= mVersion
判断mData
是否发生变化
其实这里也不一定非要用整型的 mVersion
,java.util.Observable
就是用布尔类型的 changed
:
1 | // java.util.Observable |
在处理 “数据是否发生变化” 这个问题上,java.util.Observable
使用布尔类型要比 LiveData 使用整型更易于理解,代码上也更清晰。
个人猜测使用整型 mVersion
的好处是比布尔值有更多的信息量,也许有更好的扩展性。
观察者模式
1 | public interface Observer<T> { |
数据更新之后,要通知给观察者才有意义。来看看 LiveData 是如何应用观察者模式来通知数据更新的。具体分为以下几个阶段:
- 创建观察者
- 向 LiveData 添加/移除观察者
- 被观察者与观察者之间的交互
- LiveData 通知数据更新
- 观察者响应数据更新
具体步骤如下:
- 实现
Observer
接口来创建观察者。该接口只有一个回调方法onChanged()
- 使用
LiveData.observeForever()
方法添加观察者。被添加的观察者保存在mObservers
字段 - 使用
LiveData.removeObserver()
方法移除观察者- 使用
LiveData.setValue()
方法通知数据更新。数据更新过程最终会调用到LiveData.considerNotify()
方法 Observer.onChanged()
回调方法响应数据更新,该方法的参数即为更新后的数据
- 使用
线程切换
你应该注意到 setValue()
有个限制,即只允许从主线程调用。
1 |
|
这既是限制,也是个编码技巧。编写只允许从主线程调用的方法的技巧包括:
- 技巧一:给方法添加
@MainThread
。注意:这个注解本身并不能让方法一定在主线程中运行,但如果从错误的线程中调用本方法,lint 工具马上能自动揪出这种错误 - 技巧二:方法中第一时间检查当前线程是否主线程,如果不是,立马抛出
IllegalStateException
异常,让代码快速出错。(谷歌关键字 fail-fast in java)
只允许从主线程中调用 setValue()
的好处是避免 Observer.onChanged()
在后台线程中回调这个尴尬的问题。我们知道,最终几乎都是在 Activity/Fragment 实现 Observer
接口,onChanged()
免不了要常常跟 UI 打交道。所以开发者要时时刻刻记得将 onChanged()
切回到主线程,否则 Android 系统一言不合就弹个 crash 提醒你 “Only the original thread that created a view hierarchy can touch its views”。(相当坑是不是?)
所以不得不说 setValue()
只允许在主线程中调用是一种很赞很值得我们学习的设计,它保证你的代码更不容易出错。
如果非要从其他线程修改 LiveData 的数据,需要使用 postValue()
方法。其源码如下:
1 | protected void postValue(T value) { |
postValue()
有几个关键点:
- 一是
synchronized
关键字的使用。mDataLock
作为锁,保证后台线程和主线程对mPendingData
的写操作是互斥的 - 二是通过
postToMainThread()
从后台线程切回主线程 - 最后,回到主线程中后直接调用
setValue()
,完成最终的数据更新
第二点可以作为 Android 开发中线程切换的一个范例。来详细看一下。(注:postToMainThread()
方法涉及到 TaskExecutor
相关的几个类, 我们这里抛开不相关细节直入主题直接看 DefaultTaskExecutor
的代码)
1 | public class DefaultTaskExecutor extends TaskExecutor { |
- 从后台线程切换到主线程很简单,向一个以跟主线程
Looper
绑定的Handler
扔Runnable
消息就可以了。 - 反之,从主线程切换到后台线程更简单,向 恰当的线程池 扔
Runnable
消息即可。赶紧忘记 new Thread() 吧,不要让你的应用中线程数失控!
postValue()
有个小坑值得提一下,以这段代码为例
1 | liveData.postValue("b") |
要注意的是这段代码执行后 liveData 当前值是 “b” 而不是 “a” 。原因见 postValue()
的实现机制。这个结果可能跟直观上不一致,小心使用 LiveData 过程不要踩到这个坑。
生命周期感知
LiveData 是 lifecycle-aware 的,翻译过来就是对组件(这里的组件不妨简单地理解为 Activity/Fragment) 的生命周期有感知的。
你可能会想,尽是新鲜名词,到底有鸟用?先来看 Android 开发中几个经典的坑。
分别沿着这两个 crash 的路径找下去,最后你会发现有个相同之处:在调用链有某处代码会检查 Activity/Fragment 组件的生命周期,如果生命周期状态不对,就抛出 IllegalArgumentException
或 IllegalStateException
异常。
1 | Glide.with() |
1 | DialogFragment.show() |
从设计上讲,生命周期状态不对时抛出 IllegalArgumentException
或 IllegalStateException
是正确的做法。如果上层代码不能正确地处理这些异常,自然就会 crash。
问题是,这些异常太容易被忽略,你甚至完全意识不到。试想以下场景:
- 场景一:某个原因导致 Activity 结束后才调用
Glide.with(context).load(url).into(imageView)
- 场景二:应用收到消息会弹出一个提示框 (
DialogFragment
),但收到消息时应用已经被切到后台去了
时刻记得检查 Activity 是否结束、应用是否在后台显然是不可能的,这也是为什么 Android 开发中这些地方特别容易出现 crash。换个角度考虑,如果 Glide 能自动感知 Activity 是否被销毁,FragmentTransaction 能自动感知 Activity 是否进入后台,我敢断定相关的 crash 会少很多,开发者的日子也会好过很多。
题外话:issue#3612 提议 Glide 支持 Glide.with(LifecycleOwner)
这种形式的调用,好处是 Glide 能感知生命周期,能更好地跟 Android Architecture Components 结合使用。
总的来说,LiveData 能感知生命周期的特性可以解决这个痛点:
- 避免了 Activity/Fragment 不活跃时 listener/observer 被回调导致的 crash
另外,LiveData 一定程度上避免了忘记 remove listener/observer 导致的内存泄漏,原因稍后解释。
1 | private void considerNotify(ObserverWrapper observer) { |
前文讲观察者模式时只提到 observeForever()
方法。从名字上可以知道使用 observeForever()
注册的观察者会持续对当前 LiveData 观察,直到手动调用 removeObserver()
删除这个观察者。
除 observeForever()
外还有另外一个 observe()
,这个方法也是用来注册观察者。 它的神奇之处在于通过它注册的观察者,观察 LiveData 的同时还能感知生命周期。具体说来:
- 通过
observe()
注册的观察者只在 Activity/Fragment 处于活跃的生命周期时才能收到数据更新/回调- 代码一,注册的 初始的观察者 及 LifecycleOwner 被包装成 LifecycleBoundObserver。LifecycleBoundObserver 是 LiveData 真正的观察者
- 代码二,LifecycleBoundObserver 同时作为 Lifecycle 的观察者
- 代码六,LifecycleOwner 生命周期的变化会影响 LifecycleBoundObserver 的活跃状态
- 代码三,LifecycleBoundObserver 处于不活跃状态时,初始的观察者 并不会收到数据更新
- 不必手动删除
observe()
方法注册的观察者,它会随着 Activity/Fragment 的结束而自动清理 (忘记取消注册观察者/监听器是 Android 应用内存泄漏问题的一个重要原因,所以这个特性能在一定程度上减少内存泄漏)- 代码四,
observe()
方法在 LifecycleOwner 的 Lifecycle 处于 DESTROYED 状态时并不会真正注册观察 - 代码五,LifecycleOwner 的 Lifecycle 进入 DESTROYED 时会自动删除 LifecycleBoundObserver
- 代码四,
LifecycleBoundObserver
是否处于活动状态由 shouldBeActive()
来判断。其依据很简单:判断LifecycleOwner 当前的 Lifecycle 是否至少处理 STARTED 状态即可。
1 | public abstract class Lifecycle { |
LiveData 生命周期感知的要点其实很简单,概括一下就是:注册到 LiveData 的观察者同时也观察 LifecycleOwner (Activity/Fragment) 的生命周期状态,仅在 LifecycleOwner 生命周期处于活跃状态时才将 LiveData 中的数据变化通知/加回调给观察者。
LiveData 看似微不足道的改进,解决了 Android 开发长期以来这两个痛点: 1. 不正确的生命周期状态时回调导致 crash 2. 忘记取消注册观察导致内存泄漏
不可变性
应用 LiveData 时,会有两个不同的角色:
- 一方是消费者/观察者,它响应数据更新,具体来说通常是在 UI 中监听 LiveData 的变化
- 另一方是生产者/被观察者,它通知数据更新,具体来说通常是在 ViewModel 中修改 LiveData
来看个例子:
1 | class MainActivity : AppCompatActivity() { |
不过你会发现这里的代码有个编译错误,无法给 liveData.value
赋值,因为无法访问 setValue()
方法。仔细看 LiveData 的源码中相关的你会发现这是有意为之:LiveData 是不可变的!
setValue()/postValue()
的访问修饰符是protected
,不允许子类以外的类来访问getValue()
公开的
1 | public abstract class LiveData<T> { |
MainActivity
作为 LiveData 的观察者和消费方,不应修改 LiveData,所以让 LiveData 对其保持不可变是个非常好的设计。但 MyViewModel
LiveData 的生产方,必须要修改 LiveData,该怎么办?
别急,LiveData 还有个子类 MutableLiveData。由于 MutableLiveData 的代码简单得不能再简单,就完整地贴上来。
1 | public class MutableLiveData<T> extends LiveData<T> { |
MutableLiveData 不过是将 setValue()/postValue()
的访问修饰符从 protected
修改成 public
,让其可变。
使用 MutableLiveData 对之前代码略加修改,这回没有编译错误了,也满足了观察者不修改 LiveData 的要求。
1 | class MainActivity : AppCompatActivity() { |
顺便简单提一下,LiveData 除了 MutableLiveData 子类,还有另一个子类 MediatorLiveData。另外,Transformations 还提供 LiveData 的变换操作 (是不是有些 RxJava 的感觉?),目前提供的变换操作包括:
- map()
- switchMap()
总结
本文从以下角度对 LiveData 的源码进行了简单的分析:
- 数据持有者
- 数据变更和观察者模式
- 生命周期感知
- 线程切换
- 不变性
从数据持有者的角度来看 LiveData,它并没有什么特别的。Android 开发中早已大量使用观察者模式,所以 LiveData 的观察者模式也不是太大亮点。但对比现有的一些解决方案(如 RxJava),LiveData 的特点在于它是一个可感知生命周期的观察者模式。可以这样理解 LiveData,它是专注解决 Android 开发问题的一个观察者模式。
LiveData 的线程切换和不变性的两个技巧则是工程设计上的考虑,这些技巧可以帮助开发者写代码时更不容易出错,写出的代码设计上更好。在日常开发中我们可以有意识地恰当地学习使用这些技巧,以期得到更好的开发质量。
限于篇幅本文只分析 LiveData 源码。LiveData 作为基础设施,在整个 AAC 中被大量使用,所以其实有不少有意思的话题。这里举几个例子以便有兴趣的读者继续深入了解。
例一,Room 中结合使用 LiveData 简直完美。比方说你调用 PersonDao.delete()
删除一条数据,会发现原先注册在 PersonDao.getAll()
上的观察者能感知到这个删除动作,新的数据通过 onChanged()
自动通知给活跃的观察者(响应式 UI 就是这么来的)。那么 Room 内部又是如何基于 LiveData 实现这个特性的?
1 |
|
例二,进行数据绑定时 LiveData 比 BaseObservable 和 ObservableField 方式好用得多。但是,为了保证这里布局中 viewModel.liveData
能正常绑定,事先必须先调用 binding.setLifecycleOwner()
方法。
1 | class MyViewModel { |
1 | <layout> |
为什么必须先调用 binding.setLifecycleOwner()
?binding 又是如何跟 liveData 绑定上的?