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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class LiveData<T> {
private volatile Object mData = NOT_SET;

@MainThread
protected void setValue(T value) {
...
mData = value;
...
}

@Nullable
public T getValue() {
Object data = mData;
if (data != NOT_SET) {
//noinspection unchecked
return (T) data;
}
return null;
}

仅从数据持有者的角度来看 LiveData 的话,它相当简单。LiveData 无非就是一个泛型类,可持有任意类型的数据。Android 应用中随便找个 ViewHolder ,代码可能都要比它要复杂。

数据变更

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class LiveData<T> {
private volatile Object mData = NOT_SET;
private int mVersion = START_VERSION;

@MainThread
protected void setValue(T value) {
...
mVersion++;
mData = value;
...
}

int getVersion() {
return mVersion;
}

private void considerNotify(ObserverWrapper observer) {
...
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
}

从字面意思上理解,LiveData 的 mVersion 是用于对持有的数据做版本控制。它的实际作用是用于标识 mData 是否发生变化:

  • 每次调用 setValue()mVersion 加1
  • 通过 mLastVersion >= mVersion 判断 mData 是否发生变化

其实这里也不一定非要用整型的 mVersionjava.util.Observable 就是用布尔类型的 changed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// java.util.Observable
public class Observable {
private boolean changed = false;

protected synchronized void setChanged() {
changed = true;
}

protected synchronized void clearChanged() {
changed = false;
}

public synchronized boolean hasChanged() {
return changed;
}
}

在处理 “数据是否发生变化” 这个问题上,java.util.Observable 使用布尔类型要比 LiveData 使用整型更易于理解,代码上也更清晰。

个人猜测使用整型 mVersion 的好处是比布尔值有更多的信息量,也许有更好的扩展性。

观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public interface Observer<T> {
/**
* Called when the data is changed.
* @param t The new data
*/
void onChanged(@Nullable T t);
}

public abstract class LiveData<T> {
private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();

@MainThread
public void observeForever(@NonNull Observer<T> observer) {
...
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
}

@MainThread
public void removeObserver(@NonNull final Observer<T> observer) {
ObserverWrapper removed = mObservers.remove(observer);
...
}

@MainThread
protected void setValue(T value) {
...
mData = value;
dispatchingValue(null);
}

private void dispatchingValue(@Nullable ObserverWrapper initiator) {
...
for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
...
}
}

private void considerNotify(ObserverWrapper observer) {
...
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
}

数据更新之后,要通知给观察者才有意义。来看看 LiveData 是如何应用观察者模式来通知数据更新的。具体分为以下几个阶段:

  • 创建观察者
  • 向 LiveData 添加/移除观察者
  • 被观察者与观察者之间的交互
    • LiveData 通知数据更新
    • 观察者响应数据更新

具体步骤如下:

  • 实现 Observer 接口来创建观察者。该接口只有一个回调方法 onChanged()
  • 使用 LiveData.observeForever() 方法添加观察者。被添加的观察者保存在 mObservers 字段
  • 使用 LiveData.removeObserver() 方法移除观察者
    • 使用 LiveData.setValue() 方法通知数据更新。数据更新过程最终会调用到 LiveData.considerNotify() 方法
    • Observer.onChanged() 回调方法响应数据更新,该方法的参数即为更新后的数据

线程切换

你应该注意到 setValue() 有个限制,即只允许从主线程调用。

1
2
3
4
5
6
7
8
9
10
11
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}

public boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}

这既是限制,也是个编码技巧。编写只允许从主线程调用的方法的技巧包括:

  • 技巧一:给方法添加 @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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
//noinspection unchecked
setValue((T) newValue);
}
};

postValue() 有几个关键点:

  • 一是 synchronized 关键字的使用。mDataLock 作为锁,保证后台线程和主线程对 mPendingData 的写操作是互斥的
  • 二是通过 postToMainThread() 从后台线程切回主线程
  • 最后,回到主线程中后直接调用 setValue(),完成最终的数据更新

第二点可以作为 Android 开发中线程切换的一个范例。来详细看一下。(注:postToMainThread() 方法涉及到 TaskExecutor 相关的几个类, 我们这里抛开不相关细节直入主题直接看 DefaultTaskExecutor 的代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class DefaultTaskExecutor extends TaskExecutor {
private final Object mLock = new Object();
private ExecutorService mDiskIO = Executors.newFixedThreadPool(2);

@Nullable
private volatile Handler mMainHandler;

@Override
public void executeOnDiskIO(Runnable runnable) {
mDiskIO.execute(runnable);
}

@Override
public void postToMainThread(Runnable runnable) {
if (mMainHandler == null) {
synchronized (mLock) {
if (mMainHandler == null) {
mMainHandler = new Handler(Looper.getMainLooper());
}
}
}
//noinspection ConstantConditions
mMainHandler.post(runnable);
}

@Override
public boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
}
  • 从后台线程切换到主线程很简单,向一个以跟主线程 Looper 绑定的 HandlerRunnable 消息就可以了。
  • 反之,从主线程切换到后台线程更简单,向 恰当的线程池Runnable 消息即可。赶紧忘记 new Thread() 吧,不要让你的应用中线程数失控!

postValue() 有个小坑值得提一下,以这段代码为例

1
2
liveData.postValue("b")
liveData.setValue("a")

要注意的是这段代码执行后 liveData 当前值是 “b” 而不是 “a” 。原因见 postValue() 的实现机制。这个结果可能跟直观上不一致,小心使用 LiveData 过程不要踩到这个坑。

生命周期感知

LiveData 是 lifecycle-aware 的,翻译过来就是对组件(这里的组件不妨简单地理解为 Activity/Fragment) 的生命周期有感知的。

你可能会想,尽是新鲜名词,到底有鸟用?先来看 Android 开发中几个经典的坑。

  • 坑一,使用 Glide 加载图片出现 crash issue#803
  • 坑二,使用 DialogFragment 弹框出现 crash Github

分别沿着这两个 crash 的路径找下去,最后你会发现有个相同之处:在调用链有某处代码会检查 Activity/Fragment 组件的生命周期,如果生命周期状态不对,就抛出 IllegalArgumentExceptionIllegalStateException 异常。

1
2
3
Glide.with()
-> RequestManagerRetriever.get()
-> RequestManagerRetriever.assertNotDestroyed()
1
2
3
4
5
6
7
DialogFragment.show()
-> FragmentTransaction.commit()
-> BackStackRecord.commit()
-> BackStackRecord.commitInternal()
-> FragmentManagerImpl.enqueueAction()
-> FragmentManagerImpl.checkStateLoss()
-> FragmentManagerImpl.isStateSaved()

从设计上讲,生命周期状态不对时抛出 IllegalArgumentExceptionIllegalStateException 是正确的做法。如果上层代码不能正确地处理这些异常,自然就会 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  private void considerNotify(ObserverWrapper observer) {
...
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
// 代码三
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
...
observer.mObserver.onChanged((T) mData);
}

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
// 代码四
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
// 代码一
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
// 代码二
owner.getLifecycle().addObserver(wrapper);
}

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
@NonNull final LifecycleOwner mOwner;

LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
super(observer);
mOwner = owner;
}

@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// 代码五
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// 代码六
activeStateChanged(shouldBeActive());
}

...
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}

前文讲观察者模式时只提到 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Lifecycle {
public enum State {
DESTROYED,

INITIALIZED,

CREATED,

STARTED,

RESUMED;

public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}

LiveData 生命周期感知的要点其实很简单,概括一下就是:注册到 LiveData 的观察者同时也观察 LifecycleOwner (Activity/Fragment) 的生命周期状态,仅在 LifecycleOwner 生命周期处于活跃状态时才将 LiveData 中的数据变化通知/加回调给观察者

LiveData 看似微不足道的改进,解决了 Android 开发长期以来这两个痛点: 1. 不正确的生命周期状态时回调导致 crash 2. 忘记取消注册观察导致内存泄漏

不可变性

应用 LiveData 时,会有两个不同的角色:

  • 一方是消费者/观察者,它响应数据更新,具体来说通常是在 UI 中监听 LiveData 的变化
  • 另一方是生产者/被观察者,它通知数据更新,具体来说通常是在 ViewModel 中修改 LiveData

来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MainActivity : AppCompatActivity() {

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

viewModel.liveData
.observe(this, Observer {newValue ->
Log.i("MainActivity", newValue)
})
}
}

class MyViewModel : ViewModel() {

val liveData = LiveData<String>()

fun doSomething() {
liveData.value = "a new value"
}
}

不过你会发现这里的代码有个编译错误,无法给 liveData.value 赋值,因为无法访问 setValue() 方法。仔细看 LiveData 的源码中相关的你会发现这是有意为之:LiveData 是不可变的!

  • setValue()/postValue() 的访问修饰符是 protected,不允许子类以外的类来访问
  • getValue() 公开的
1
2
3
4
5
public abstract class LiveData<T> {
protected void postValue(T value) {}
protected void setValue(T value) {}
public T getValue() {}
}

MainActivity 作为 LiveData 的观察者和消费方,不应修改 LiveData,所以让 LiveData 对其保持不可变是个非常好的设计。但 MyViewModel LiveData 的生产方,必须要修改 LiveData,该怎么办?

别急,LiveData 还有个子类 MutableLiveData。由于 MutableLiveData 的代码简单得不能再简单,就完整地贴上来。

1
2
3
4
5
6
7
8
9
10
11
public class MutableLiveData<T> extends LiveData<T> {
@Override
public void postValue(T value) {
super.postValue(value);
}

@Override
public void setValue(T value) {
super.setValue(value);
}
}

MutableLiveData 不过是将 setValue()/postValue() 的访问修饰符从 protected 修改成 public,让其可变。

使用 MutableLiveData 对之前代码略加修改,这回没有编译错误了,也满足了观察者不修改 LiveData 的要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainActivity : AppCompatActivity() {

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

viewModel.getLiveData()
.observe(this, Observer {newValue ->
Log.i("MainActivity", newValue)
})
}
}

class MyViewModel : ViewModel() {

val liveData = MutableLiveData<String>()

fun getLiveData(): LiveData<String> = liveData

fun doSomething() {
liveData.value = "a new value"
}
}

顺便简单提一下,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
2
3
4
5
6
7
8
@Dao
public interface PersonDao {
@Delete
void delete(Person person);

@Query("select * from person order by id desc")
LiveData<List<Person>> getAll();
}

例二,进行数据绑定时 LiveData 比 BaseObservable 和 ObservableField 方式好用得多。但是,为了保证这里布局中 viewModel.liveData 能正常绑定,事先必须先调用 binding.setLifecycleOwner() 方法。

1
2
3
4
class MyViewModel {
val liveData = LiveData<String>()

}
1
2
3
4
5
6
7
<layout>
<data>
<variable type="MyViewModel" name="viewModel" />
</data>

<TextView text="@{viewModel.liveData}" />
</layout>

为什么必须先调用 binding.setLifecycleOwner() ?binding 又是如何跟 liveData 绑定上的?