引子

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()。在这个办法中需求跨过三道坎,才干真实地将值分发给数据调查者,别离是:

  1. 数据调查者是否活泼。
  2. 数据调查者绑定的生命周期组件是否活泼。
  3. 数据调查者的版别号是否是最新的。

跨过三道坎后,会将最新的版别号存储在调查者的 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 有两次时机告诉调查者,与之对应的有两种分发值的办法:

  1. 当值更新时,遍历一切调查者将最新值分发给它们。
  2. 当与调查者绑定组件的生命周期发生改变时,将最新的值分发给指定调查者。

假定这样一种场景: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)。

这种“新调查者”会被“老值”告诉的现象称为粘性

4. 粘性的 LiveData 会形成什么问题?怎样处理?

购物车-结算场景:假定有一个购物车界面,点击结算后跳转到结算界面,结算界面能够回退到购物车界面。这两个界面都是 Fragment。

结算界面和购物车界面经过同享ViewModel的办法同享产品列表:

class MyViewModel:ViewModel() {
    // 产品列表
    val selectsListLiveData = MutableLiveData<List<String>>()
    // 更新产品列表
    fun setSelectsList(goods:List<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 办法声明性地构建了布局,具体介绍能够点击Android功能优化 | 把构建布局用时缩短 20 倍(下)

购物车页面如下:

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(goods:List<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),直接给出代码(当时做法有问题),关于 LiveData vs Flow 的具体剖析能够点击怎样把事务代码越写越杂乱?(二)| Flow 替换 LiveData 重构数据链路,愈加 MVI

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.LiveData
public 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"

参考

Jetpack MVVM 七宗罪之四: 运用 LiveData/StateFlow 发送 Events

引荐阅览

面试系列文章列表如下:

面试题 | 怎样写一个又好又快的日志库?(一)

面试题 | 怎样写一个又好又快的日志库?(二)

面试题 | 徒手写一个 ConcurrentLinkedQueue?

来评论下 Android 面试该问什么类型的题目?

RecyclerView 面试题 | 哪些状况下表项会被收回到缓存池?

面试题 | 有用过并发容器吗?有!比方网络恳求埋点