引子
LiveData 是能感知生命周期的,可观察的,粘性的,数据持有者。LiveData 用于以“数据驱动”方式更新界面。
换一种描述方式:LiveData 缓存了最新的数据并将其传递给正活跃的组件。
关于数据驱动的详解可以点击我是怎么把业务代码越写越复杂的 | MVP - MVVM - Clean Architecture。
这一篇就 LiveData 的面试题做一个归总、分析、解答。
1. LiveData 如何感知生命周期的变化?
先总结,再分析:
- Jetpack 引入了 Lifecycle,让任何组件都能方便地感知界面生命周期的变化。只需实现 LifecycleEventObserver 接口并注册给生命周期对象即可。
- LiveData 的数据观察者在内部被包装成另一个对象(实现了 LifecycleEventObserver 接口),它同时具备了数据观察能力和生命周期观察能力。
常规的观察者模式中,只要被观察者发生变化,就会无条件地通知所有观察者。比如java.util.Observable
:
public class Observable { private boolean changed = false; private Vector<Observer> obs; public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!hasChanged()) return; arrLocal = obs.toArray(); clearChanged(); } // 无条件地遍历所有观察者并通知 for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } } // 观察者 public interface Observer { void update(Observable o, Object arg); }
LiveData 在常规的观察者模式上附加了条件,若生命周期未达标,即使数据发生变化也不通知观察者。这是如何实现的?
生命周期
生命周期是一个对象从构建到消亡过程中的各个状态的统称。
比如 Activity 的生命周期用如下函数依次表达:
onCreate() onStart() onResume() onPause() onStop() onDestroy()
要观察生命周期就不得不继承 Activity 重写这些方法,想把生命周期的变化分发给其他组件就很麻烦。
于是 Jetpack 引入了 Lifecycle,以让任何组件都可方便地感知生命周期的变化:
public abstract class Lifecycle {AtomicReference<>(); // 添加生命周期观察者 public abstract void addObserver(LifecycleObserver observer); // 移除生命周期观察者 public abstract void removeObserver(LifecycleObserver observer); // 获取当前生命周期状态 public abstract State getCurrentState(); // 生命周期事件 public enum Event { ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY; } // 生命周期状态 public enum State { DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED; } // 判断至少到达了某生命周期状态 public boolean isAtLeast(State state) { return compareTo(state) >= 0; } }
Lifecycle 即是生命周期对应的类,提供了添加/移除生命周期观察者的方法,在其内部还定义了全部生命周期的状态及对应事件。
生命周期状态是有先后次序的,分别对应着由小到大的 int 值。
生命周期拥有者
描述生命周期的对象已经有了,如何获取这个对象需要个统一的接口(不然直接在 Activity 或者 Fragment 中新增一个方法吗?),这个接口叫LifecycleOwner
:
public interface LifecycleOwner { Lifecycle getLifecycle(); }
Activity 和 Fragment 都实现了这个接口。
只要拿到 LifecycleOwner,就能拿到 Lifecycle,然后就能注册生命周期观察者。
生命周期 & 数据观察者
生命周期观察者是一个接口:
// 生命周期观察者(空接口,用于表征一个类型) public interface LifecycleObserver {} // 生命周期事件观察者 public interface LifecycleEventObserver extends LifecycleObserver { void onStateChanged(LifecycleOwner source, Lifecycle.Event event); }
要观察生命周期只要实现LifecycleEventObserver
接口,并注册给LifeCycle
即可。
除了生命周期观察者外,LiveData 场景中还有一个数据观察者:
// 数据观察者 public interface Observer<T> { // 数据发生变化时回调 void onChanged(T t); }
数据观察者 会和 生命周期拥有者 进行绑定:
public abstract class LiveData<T> { // 数据观察者容器 private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>(); public void observe( LifecycleOwner owner, // 被绑定的生命周期拥有者 Observer<? super T> observer // 数据观察者 ) { ... // 将数据观察者包装成 LifecycleBoundObserver LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); // 存储观察者到 map 结构 ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); ... // 注册生命周期观察者。 owner.getLifecycle().addObserver(wrapper); } }
在观察 LiveData 时,需传入两个参数,生命周期拥有者和数据观察者。这两个对象经过LifecycleBoundObserver
的包装被绑定在了一起:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { // 持有生命周期拥有者 final LifecycleOwner mOwner; LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } // 生命周期变化回调 @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { ... activeStateChanged(shouldBeActive()) ... } } // 观察者包装类型 private abstract class ObserverWrapper { // 持有原始数据观察者 final Observer<? super T> mObserver; // 注入数据观察者 ObserverWrapper(Observer<? super T> observer) {mObserver = observer;} // 尝试将最新值分发给当前数据观察者 void activeStateChanged(boolean newActive) {...} ... }
LifecycleBoundObserver 实现了LifecycleEventObserver
接口,并且它被注册给了绑定的生命周期对象,遂具备了生命周期感知能力。同时它还持有了数据观察者,所以它还具备了数据观察能力。
2. LiveData 是如何避免内存泄漏的?
先总结,再分析:
- LiveData 的数据观察者通常是匿名内部类,它持有界面的引用,可能造成内存泄漏。
- LiveData 内部会将数据观察者进行封装,使其具备生命周期感知能力。当生命周期状态为 DESTROYED 时,自动移除观察者。
内存泄漏是因为长生命周期的对象持有了短生命周期对象,阻碍了其被回收。
观察 LiveData 数据的代码通常这样写:
class LiveDataActivity : AppCompatActivity() { private val viewModel by lazy { ViewModelProviders.of(this@LiveDataActivity).get(MyViewModel::class.java) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.livedata.observe(this@LiveDataActivity) { // 观察 LiveData 数据更新(匿名内部类) } } }
Observer 作为界面的匿名内部类,它会持有界面的引用,同时 Observer 被 LiveData 持有,LivData 被 ViewModel 持有,而 ViewModel 的生命周期比 Activity 长。(为啥比它长,可以点击这里)。
最终的持有链如下:NonConfigurationInstances 持有 ViewModelStore 持有 ViewModel 持有 LiveData 持有 Observer 持有 Activity。
所以得在界面生命周期结束的时候移除 Observer,这件事情,LiveData 帮我们做了。
在 LiveData 内部 Observer 会被包装成LifecycleBoundObserver
:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { final LifecycleOwner mOwner; LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { // 获取当前生命周期 Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); // 若生命周期为 DESTROYED 则移除数据观察者并返回 if (currentState == DESTROYED) { removeObserver(mObserver); return } ... } ... }
3. LiveData 是粘性的吗?若是,它是怎么做到的?
先总结,再分析:
- LiveData 的值被存储在内部的字段中,直到有更新的值覆盖,所以值是持久的。
- 两种场景下 LiveData 会将存储的值分发给观察者。一是值被更新,此时会遍历所有观察者并分发之。二是新增观察者或观察者生命周期发生变化(至少为 STARTED),此时只会给单个观察者分发值。
- LiveData 的观察者会维护一个“值的版本号”,用于判断上次分发的值是否是最新值。该值的初始值是-1,每次更新 LiveData 值都会让版本号自增。
- LiveData 并不会无条件地将值分发给观察者,在分发之前会经历三道坎:1. 数据观察者是否活跃。2. 数据观察者绑定的生命周期组件是否活跃。3. 数据观察者的版本号是否是最新的。
- “新观察者”被“老值”通知的现象叫“粘性”。因为新观察者的版本号总是小于最新版号,且添加观察者时会触发一次老值的分发。
如果把 sticky 翻译成“持久的”,会更好理解一些。数据是持久的,意味着它不是转瞬即逝的,不会因为被消费了就不见了,它会一直在那。而且当新的观察者被注册时,持久的数据会将最新的值分发给它。
“持久的数据”是怎么做到的?
显然是被存起来了。以更新 LiveData 数据的方法为切入点找找线索:
public abstract class LiveData<T> { // 存储数据的字段 private volatile Object mData; // 值版本号 private int mVersion; // 更新值 protected void setValue(T value) { assertMainThread("setValue"); // 版本号自增 mVersion++; // 存储值 mData = value; // 分发值 dispatchingValue(null); } }
setValue() 是更新 LiveData 值时必然会调用的一个方法,即使是通过 postValue() 更新值,最终也会走这个方法。
LiveData 持有一个版本号字段,用于标识“值的版本”,就像软件版本号一样,这个数字用于判断“当前值是否是最新的”,若版本号小于最新版本号,则表示当前值需要更新。
LiveData 用一个 Object 字段mData
存储了“值”。所以这个值会一直存在,直到被更新的值覆盖。
LiveData 分发值即是通知数据观察者:
public abstract class LiveData<T> { // 用键值对方式持有一组数据观察者 private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>(); void dispatchingValue(ObserverWrapper initiator) { ... // 指定分发给单个数据观察者 if (initiator != null) { considerNotify(initiator); initiator = null; } // 遍历所有数据观察者分发值 else { for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); } } ... } // 真正地分发值 private void considerNotify(ObserverWrapper observer) { // 1. 若观察者不活跃则不分发给它 if (!observer.mActive) { return; } // 2. 根据观察者绑定的生命周期再次判断它是否活跃,若不活跃则不分发给它 if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } // 3. 若值已经是最新版本,则不分发 if (observer.mLastVersion >= mVersion) { return; } // 更新观察者的最新版本号 observer.mLastVersion = mVersion; // 真正地通知观察者 observer.mObserver.onChanged((T) mData); } }
分发值有两种情况:“分发给单个观察者”和“分发给所有观察者”。当 LiveData 值更新时,需分发给所有观察者。
所有的观察者被存在一个 Map 结构中,分发的方式是通过遍历 Map 并逐个调用considerNotify()
。在这个方法中需要跨过三道坎,才能真正地将值分发给数据观察者,分别是:
- 数据观察者是否活跃。
- 数据观察者绑定的生命周期组件是否活跃。
- 数据观察者的版本号是否是最新的。
跨过三道坎后,会将最新的版本号存储在观察者的 mLastVersion 字段中,即版本号除了保存在LiveData.mVersion
,还会在每个观察者中保存一个副本mLastVersion
,最后才将之前暂存的mData
的值分发给数据观察者。
每个数据观察者都和一个组件的生命周期对象绑定(见第一节),当组件生命周期发生变化时,会尝试将最新值分发给该数据观察者。
每一个数据观察者都会被包装(见第一节),包装类型为ObserverWrapper
:
// 原始数据观察者 public interface Observer<T> { void onChanged(T t); } // 观察者包装类型 private abstract class ObserverWrapper { // 持有原始数据观察者 final Observer<? super T> mObserver; // 当前观察者是否活跃 boolean mActive; // 当前观察者最新值版本号,初始值为 -1 int mLastVersion = START_VERSION; // 注入原始观察者 ObserverWrapper(Observer<? super T> observer) {mObserver = observer;} // 当数据观察者绑定的组件生命周期变化时,尝试将最新值分发给当前观察者 void activeStateChanged(boolean newActive) { // 若观察者活跃状态未变,则不分发值 if (newActive == mActive) { return; } // 更新活跃状态 mActive = newActive; // 若活跃,则将最新值分发给当前观察者 if (mActive) { dispatchingValue(this); } } // 是否活跃,供子类重写 abstract boolean shouldBeActive(); }
观察者的包装类型通过组合的方式持有了一个原始观察者,并在此基础上为其扩展了活跃状态和版本号的概念。
观察者包装类型是抽象的,是否活跃由子类定义:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { final LifecycleOwner mOwner; LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } // 当与观察者绑定的生命周期组件至少为STARTED时,表示观察者活跃 @Override boolean shouldBeActive() { return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged( LifecycleOwner source, Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); // 当生命周期状态发生变化,则尝试将最新值分发给数据观察者 while (prevState != currentState) { prevState = currentState; // 调用父类方法,进行分发 activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); } } }
总结一下,LiveData 有两次机会通知观察者,与之对应的有两种分发值的方式:
- 当值更新时,遍历所有观察者将最新值分发给它们。
- 当与观察者绑定组件的生命周期发生变化时,将最新的值分发给指定观察者。
假设这样一种场景:LiveData 的值被更新了一次,随后它被添加了一个新的数据观察者,与之绑定组件的生命周期也正好发生了变化(变化到RESUMED),即数据更新在添加观察者之前,此时更新值会被分发到新的观察者吗?
会!首先,更新值会被存储在 mData 字段中。
其次,在添加观察者时会触发一次生命周期变化:
// androidx.lifecycle.LifecycleRegistry public void addObserver(@NonNull LifecycleObserver observer) { State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver = new ObserverWithState(observer, initialState); ... // 将生命周期事件分发给新进的观察者 statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState)); ... } // LifecycleBoundObserver 又被包了一层 static class ObserverWithState { State mState; GenericLifecycleObserver mLifecycleObserver; ObserverWithState(LifecycleObserver observer, State initialState) { mLifecycleObserver = Lifecycling.getCallback(observer); mState = initialState; } void dispatchEvent(LifecycleOwner owner, Event event) { State newState = getStateAfter(event); mState = min(mState, newState); // 分发生命周期事件给 LifecycleBoundObserver mLifecycleObserver.onStateChanged(owner, event); mState = newState; } }
最后,这次尝试必然能跨过三道坎,因为新建观察者版本号总是小于 LiveData 的版本号(-1 < 0,LiveData.mVersion 经过一次值更新后自增为0)。
这种“新观察者”会被“老值”通知的现象称为粘性。