引子
LiveData 是能感知生命周期的,可观察的,粘性的,数据持有者。LiveData 用于以“数据驱动”方式更新界面。
换一种形貌方式:LiveData 缓存了最新的数据并将其通报给正活泼的组件。
这一篇就 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).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.LifecycleRegistrypublic 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)。
这种“新观察者”会被“老值”关照的征象称为粘性。
4. 粘性的 LiveData 会造成什么标题?怎么办理?
购物车-结算场景:假设有一个购物车界面,点击结算后跳转到结算界面,结算界面可以回退到购物车界面。这两个界面都是 Fragment。
结算界面和购物车界面通过共享ViewModel的方式共享商品列表:
class MyViewModel:ViewModel() { // 商品列表 val selectsListLiveData = MutableLiveData<List<String>>() // 更新商品列表 fun setSelectsList(goodsist<String>){ selectsListLiveData.value = goods }}下面是俩 Fragment 界面依托的 Activity
class StickyLiveDataActivity : AppCompatActivity() { // 用 DSL 构建视图 private val contentView by lazy { ConstraintLayout { layout_id = "container" layout_width = match_parent layout_height = match_parent } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(contentView) // 加载购物车界面 supportFragmentManager.beginTransaction() .add("container".toLayoutId(), TrolleyFragment()) .commit() }}此中使用了 DSL 方式声明性地构建了布局。
购物车页面如下:
class TrolleyFragment : Fragment() { // 获取与宿主 Activity 绑定的 ViewModel private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ConstraintLayout { layout_width = match_parent layout_height = match_parent // 向购物车添加两件商品 onClick = { myViewModel.setSelectsList(listOf("meet","water")) } TextView { layout_id = "balance" layout_width = wrap_content layout_height = wrap_content text = "balance" gravity = gravity_center // 跳转结算页面 onClick = { parentFragmentManager.beginTransaction() .replace("container".toLayoutId(), BalanceFragment()) .addToBackStack("trolley") .commit() } } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 观察商品列表变革 myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods -> // 若商品列表高出2件商品,则 toast 提示已满 goods.takeIf { it.size >= 2 }?.let { Toast.makeText(context,"购物车已满",Toast.LENGTH_LONG).show() } } }}在 onViewCreated() 中观察购物车的变革,如果购物车高出 2 件商品,则 toast 提示。
下面是结算页面:
class BalanceFragment:Fragment() { private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ConstraintLayout { layout_width = match_parent layout_height = match_parent } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 结算界面获取购物列表的方式也是观察商品 LiveData myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {...} }}跑一下 demo,当跳转到结算界面后,点击返回购物车,toast 会再次提示购物车已满。
因为在跳转结算页面之前,购物车列表 LiveData 已经被更新过。当购物车页面重新展示时,onViewCreated()会再次实行,如许一个新观察者被添加,因为 LiveData 是粘性的,以是上一次购物车列表会分发给新观察者,如许 toast 逻辑再一次被实行。
办理方案一:带斲丧记载的值
// 一次性值open class OneShotValue<out T>(private val value: T) { // 值是否被斲丧 private var handled = false // 获取值,如果值未被处置惩罚则返回,否则返回空 fun getValue(): T? { return if (handled) { null } else { handled = true value } } // 获取前次被处置惩罚的值 fun peekValue(): T = value}在值的表面套一层,新增一个标记位标识是否被处置惩罚过。
用这个方法重构下 ViewModel:
class MyViewModel:ViewModel() { // 已选物品列表 val selectsListLiveData = MutableLiveData<OneShotValue<List<String>>>() // 更新已选物品 fun setSelectsList(goodsist<String>){ selectsListLiveData.value = OneShotValue(goods) }}观察购物车的逻辑也要做修改:
class TrolleyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods -> goods.getValue()?.takeIf { it.size >= 2 }?.let { Toast.makeText(context,"购物车满了",Toast.LENGTH_LONG).show() } } }}重复弹 toast 的标题是办理了,但引出了一个新的标题:当购物车满弹出 toast 时,购物车列表已经被斲丧掉了,导致结算界面就无法再斲丧了。
这时间只能用peekValue()来获取已经被斲丧的值:
class BalanceFragment:Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { val list = it.peekValue()// 使用 peekValue() 获取购物车列表 } }}bug 全解完了。但不以为如许处置惩罚有一些拧巴吗?
用“一次性值”封装 LiveData 的值,以去除其粘性。使用该方案得甄别出哪些观察者须要粘性值,哪些观察者须要非粘性变乱。当观察者许多的时间,就很难招架了。若把须要粘性处置惩罚和非粘性处置惩罚的逻辑写在一个观察者中,就 GG,还得新建观察者将它们分开。
办理方案二:带有最新版本号的观察者
关照观察者前须要跨过三道坎(详见第三节),此中有一道坎是版本号的比对。若新建的观察者版本号小于最新版本号,则表现观察者落后了,须要将最新值分发给它。
LiveData 源码中,新建观察者的版本号总是 -1。
// 观察者包装范例private abstract class ObserverWrapper { // 当前观察者最新值版本号,初始值为 -1 int mLastVersion = START_VERSION; ...}若可以或许让新建观察者的版本号被最新版本号赋值,那版本号对比的那道坎就过不了,新值就无法分发到新建观察者。
以是得通过反射修改 mLastVersion 字段。
该方案除了倾入性强之外,把 LiveData 粘性彻底粉碎了。但有的时间,我们还是想使用粘性的。。。
办理方案三:SingleLiveEvent
这是谷歌给出的一个办理方案,源码可以点击这里
public class SingleLiveEvent<T> extends MutableLiveData<T> { // 标记位,用于表达值是否被斲丧 private final AtomicBoolean mPending = new AtomicBoolean(false); public void observe(LifecycleOwner owner, final Observer<T> observer) { // 中间观察者 super.observe(owner, new Observer<T>() { @Override public void onChanged(@Nullable T t) { // 只有当值未被斲丧逾期,才关照卑鄙观察者 if (mPending.compareAndSet(true, false)) { observer.onChanged(t); } } }); } public void setValue(@Nullable T t) { // 当值更新时,置标记位为 true mPending.set(true); super.setValue(t); } public void call() { setValue(null); }}专门设立一个 LiveData,它不具备粘性。它通过新增的“中间观察者”,拦截上游数据变革,然后在转发给卑鄙。拦截之后通常可以做一点手脚,比如增加一个标记位mPending是否斲丧过的判定,若斲丧过则不转发给卑鄙。
在数据驱动的 App 界面下,存在两种值:1. 非暂态数据 2. 暂态数据
demo 中用于提示“购物车已满”的数据就是“暂态数据”,这种数据是一次性的,转瞬即逝的,可以斲丧一次就抛弃。
demo 中购物车中的商品列表就是“非暂态数据”,它的生命周期要比暂态数据长一点,在购物车界面和结算界面存活的期间都应该能被重复斲丧。
SingleLiveEvent 的操持正是基于对数据的这种分类方法,即暂态数据使用 SingleLiveEvent,非暂态数据使用通例的 LiveData。
如许尘归灰尘归土的办理方案是符合实际情况的。将 demo 改造一下:
class MyViewModel : ViewModel() { // 非暂态购物车列表 LiveData val selectsListLiveData = MutableLiveData<List<String>>() // 暂态购物车列表 LiveData val singleListLiveData = SingleLiveEvent<List<String>>() // 更新购物车列表,同时更新暂态和非暂态 fun setSelectsList(goods: List<String>) { selectsListLiveData.value = goods singleListLiveData.value = goods }}在购物车界面做相应的改动:
class TrolleyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 只观察非暂态购物车列表 myViewModel.singleListLiveData.observe(viewLifecycleOwner) { goods -> goods.takeIf { it.size >= 2 }?.let { Toast.makeText(context,"full",Toast.LENGTH_LONG).show() } } }}但该方案有范围性,若为 SingleLiveEvent 添加多个观察者,则当第一个观察者斲丧了数据后,其他观察者就没时机斲丧了。因为mPending是全部观察者共享的。
办理方案也很简单,为每个中间观察者都持有是否斲丧过数据的标记位:
open class LiveEvent<T> : MediatorLiveData<T>() { // 持有多个中间观察者 private val observers = ArraySet<ObserverWrapper<in T>>() @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { observers.find { it.observer === observer }?.let { _ -> return } // 构建中间观察者 val wrapper = ObserverWrapper(observer) observers.add(wrapper) super.observe(owner, wrapper) } @MainThread override fun observeForever(observer: Observer<in T>) { observers.find { it.observer === observer }?.let { _ -> return } val wrapper = ObserverWrapper(observer) observers.add(wrapper) super.observeForever(wrapper) } @MainThread override fun removeObserver(observer: Observer<in T>) { if (observer is ObserverWrapper && observers.remove(observer)) { super.removeObserver(observer) return } val iterator = observers.iterator() while (iterator.hasNext()) { val wrapper = iterator.next() if (wrapper.observer == observer) { iterator.remove() super.removeObserver(wrapper) break } } } @MainThread override fun setValue(t: T?) { // 关照全部中间观察者,有新数据 observers.forEach { it.newValue() } super.setValue(t) } // 中间观察者 private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> { // 标记当前观察者是否斲丧了数据 private var pending = false override fun onChanged(t: T?) { // 包管只向卑鄙观察者分发一次数据 if (pending) { pending = false observer.onChanged(t) } } fun newValue() { pending = true } }}办理方案四:Kotlin Flow
限于篇幅缘故起因及主题的缘故起因(主题是 LiveData),直接给出代码(当前做法有标题),会再起一篇做关于 Flow 替代 LiveData 的分析。
class MyViewModel : ViewModel() { // 商品列表流 val selectsListFlow = MutableSharedFlow<List<String>>() // 更新商品列表 fun setSelectsList(goods: List<String>) { viewModelScope.launch { selectsListFlow.emit(goods) } }}购物车代码如下:
class TrolleyFragment : Fragment() { private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1.先产生数据 myViewModel.setSelectsList(listOf("food_meet", "food_water", "book_1")) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 2.再订阅商品列表流 lifecycleScope.launch { myViewModel.selectsListFlow.collect { goods -> goods.takeIf { it.size >= 2 }?.let { Log.v("ttaylor", "购物车满") } } } }}数据生产在订阅之前,订阅后并不会打印 log。
如果如许修改 SharedFlow 的构建参数,则可以让其变得粘性:
class MyViewModel : ViewModel() { val selectsListFlow = MutableSharedFlow<List<String>>(replay = 1)}replay = 1 表现会将最新的谁人数据关照给新进的订阅者。
这只是办理了粘性/非粘性之间方便切换的标题,并未办理仍需多个流的标题。带下一篇继续深入分析。
5. 什么情况下 LiveData 会丢失数据?
先总结,再分析:
在高频数据更新的场景下使用 LiveData.postValue() 时,会造成数据丢失。因为“设值”和“分发值”是分开实行的,之间存在延长。值先被缓存在变量中,再向主线程抛一个分发值的任务。若在这延长之间再一次调用 postValue(),则变量中缓存的值被更新,之前的值在没有被分发之前就被擦除了。
下面是 LiveData.postValue() 的源码:
public abstract class LiveData<T> { // 暂存值字段 volatile Object mPendingData = NOT_SET; private final Runnable mPostValueRunnable = new Runnable() { @Override public void run() { Object newValue; synchronized (mDataLock) { // 同步地获取暂存值 newValue = mPendingData; mPendingData = NOT_SET; } // 分发值 setValue((T) newValue); } }; protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; // 暂存值 mPendingData = value; } ... // 向主线程抛 runnable ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }}6. 在 Fragment 中使用 LiveData 需注意些什么?
先总结,再分析:
在 Fragment 中观察 LiveData 时使用viewLifecycleOwner而不是this。因为 Fragment 和 此中的 View 生命周期不完全划一。LiveData 内部判定生命周期为 DESTROYED 时,才会移除数据观察者。存在一种情况,当 Fragment 之间切换时,被更换的 Fragment 不实行 onDestroy(),当它再次展示时会再次订阅 LiveData,于是乎就多出一个订阅者。
还是购物-结算的场景:购物车和结算页都是两个 Fragment,将商品列表存在共享 ViewMode 的 LiveData 中,购物车及结算页都观察它,结算页除了用它列出购物清单之外,还可以通过更改商品数量来修改 LiveData。当从结算页返回购物车页面时,购物车界面得革新商品数量。
上述场景,若购物车页面观察 LiveData 时使用this会发生什么?
// 购物车界面class TrolleyFragment : Fragment() { private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ConstraintLayout { layout_width = match_parent layout_height = match_parent onClick = { parentFragmentManager.beginTransaction() .replace("container".toLayoutId(), BalanceFragment()) .addToBackStack("trolley")// 将购物车页面添加到 back stack .commit() } } } // 不得不增加这个解释,因为 this 会飘红 @SuppressLint("FragmentLiveDataObserve") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 将 this 作为生命周期拥有者传给 LiveData myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> { override fun onChanged(t: List<String>?) { Log.v("ttaylor", "商品数量发生变革") } }) }}如许写this会飘红,AndroidStudio 不推荐使用它作为生命周期拥有者,不得不加 @SuppressLint("FragmentLiveDataObserve")
结算界面修改商品数量的代码如下:
// 结算界面class BalanceFragment:Fragment() { private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 模拟结算界面修改商品数量 myViewModel.selectsListLiveData.value = listOf("数量+1") }}当从结算页返回购物车时,“商品数量发生变革” 会打印两次,如果再进一次结算页并返回购物车,就会打印三次。
若换成viewLifecycleOwner就不会有这个烦恼。因为使用 replace 更换 Fragment 时,Fragment.onDestroyView()会实行,即 Fragment 对应 View 的生命周期状态会变为 DESTROYED。
LiveData 内部会将生命周期为 DESTROYED 的数据观察者移除(详见第二节)。当再次返回购物车时,onViewCreated() 重新实行,LiveData 会添加一个新的观察者。一删一增,整个过程 LiveData 始终只有一个观察者。又因为 LiveData 是粘性的,即使修改商品数量发生在观察之前,最新的商品数量还是会被分发到新观察者。(详见第三节)
但当使用 replace 更换 Fragment 并将其压入 back stack 时,Fragment.onDestroy() 不会调用(因为被压栈了,并未被烧毁)。这导致 Fragment 的生命周期状态不会变为 DESTROYED,以是 LiveData 的观察者不会被自动移除。当重新返回购物车时,又添加了新的观察者。如果不绝地在购物车和结算页间横跳,则观察者数据会不绝地增加。
在写 demo 的时间碰到一个坑:
// 购物车界面class TrolleyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 故意使用 object 语法 myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> { override fun onChanged(t: List<String>?) { Log.v("ttaylor", "商品数量发生变革") } }) }}在构建 Observer 实例的时间,我特意使用了 Kotlin 的 object 语法,实在明显可以使用 lambda 将其写得更简便:
class TrolleyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) myViewModel.selectsListLiveData.observe(this) { Log.v("ttaylor", "商品数量发生变革") } }}如果如许写,那 bug 就无法复现了。。。。
因为 java 编译器会擅作主张地将同样的 lambda 优化成静态的,可以提升性能,不消每次都重新构建内部类。但不巧的是 LiveData 在添加观察者时会校验是否已存在,若存在则直接返回:
// `androidx.lifecycle.LiveDatapublic void observe( LifecycleOwner owner, Observer<? super T> observer) { ... LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); // 调用 map 布局的写操纵,若 key 已存在,则返回对应 value ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); ... // 已存在则直接返回 if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper);}如许的话,Fragment 界面之间反复横跳也不会新增观察者。
7. 怎样变更 LiveData 数据及注意事项?
先总结,再分析:
androidx.lifecycle.Transformations类提供了三个变更 LiveData 数据的方法,最常用的是 Transformations.map(),它使用MediatorLiveData作为数据的中间斲丧者,并将变更后的数据通报给终极斲丧者。须要注意的是,数据变革操纵都发生在主线程,主线程有大概被耗时操纵壅闭。办理方案是将 LiveData 数据变更操纵异步化,比如通过CoroutineLiveData。
还是购物-结算的场景:购物车和结算页都是两个 Fragment,将商品列表存在 LiveData 中,购物车及结算页都观察它。结算界面对打折商品有一个特别的 UI 展示。
此时就可以将商品列表 LiveData 举行一次变更(过滤)得到一个新的打折商品列表:
class MyViewModel : ViewModel() { // 商品列表 val selectsListLiveData = MutableLiveData<List<String>>() // 打折商品列表 val foodListLiveData = Transformations.map(selectsListLiveData) { list -> list.filter { it.startsWith("discount") } }}每当商品列表发生变革,打折商品列表都会收到关照,并过滤出新的打折商品。打折商品列表是一个新的 LiveData,可以单独被观察。
此中的过滤列表操纵发生在主线程,如果业务略复杂,数据变更操纵耗时的话,大概壅闭主线程。
怎样将 LiveData 变更数据异步化?
LiveData 的 Kotlin 扩展包里提供了一个将 LiveData 和协程结合的产物:
class MyViewModel : ViewModel() { // 商品列表 val selectsListLiveData = MutableLiveData<List<String>>() // 用异步方式获取打折商品列表 val asyncLiveData = selectsListLiveData.switchMap { list -> // 将源 LiveData 中的值转换成一个 CoroutineLiveData liveData(Dispatchers.Default) { emit( list.filter { it.startsWith("discount") } ) } }}此中的switchMap()是 LiveData 的扩展方法,它是对Transformations.switchMap()的封装,用于方便链式调用:
public inline fun <X, Y> LiveData<X>.switchMap( crossinline transform: (X) -> LiveData<Y>): LiveData<Y> = Transformations.switchMap(this) { transform(it) }switchMap() 内部将源 LiveData 的每个值都转换成一个新的 LiveData 并订阅。
liveData是一个顶层方法,用于构建CoroutineLiveData:
public fun <T> liveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, block: suspend LiveDataScope<T>.() -> Unit): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)CoroutineLiveData 将更新 LiveData 值的操纵封装到一个挂起方法中,可以通过协程上下文指定实行的线程。
使用 CoroutineLiveData 须要添加如下依赖:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" |