LiveData 面试题库、解答、源码分析

简介: LiveData 面试题库、解答、源码分析

引子


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. 数据观察者是否活跃。


  1. 数据观察者绑定的生命周期组件是否活跃。


  1. 数据观察者的版本号是否是最新的。


跨过三道坎后,会将最新的版本号存储在观察者的 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. 当值更新时,遍历所有观察者将最新值分发给它们。


  1. 当与观察者绑定组件的生命周期发生变化时,将最新的值分发给指定观察者。


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

这种“新观察者”会被“老值”通知的现象称为粘性。

目录
相关文章
|
5月前
|
SQL Oracle 关系型数据库
mysql面试题库
mysql面试题库
|
3月前
|
JavaScript 前端开发 小程序
CoderGuide 程序员前后端面试题库,打造全网最高质量题库
CoderGuide涵盖范围包括且不限于:前端面试题(Vue,React,JS,HTTP,HTML,CSS面试题等),后端面试题(Java,Python,Golang,PHP,Linux,Mysql面试题等),以及算法面试题,大厂面试题,高频面试题,校招面试题等,你想要的,这里都有!
65 2
|
5月前
|
存储 Java
java面试题大全带答案_面试题库_java面试宝典2018
java面试题大全带答案_面试题库_java面试宝典2018
|
5月前
|
SQL 前端开发 Java
2019史上最全java面试题题库大全800题含答案(面试宝典)(4)
2019史上最全java面试题题库大全800题含答案(面试宝典)
|
5月前
|
存储 NoSQL Redis
redis面试题库
redis面试题库
|
5月前
|
SQL 关系型数据库 MySQL
sql面试题库
sql面试题库
|
5月前
|
存储 设计模式 Java
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
|
5月前
|
安全 算法 Java
java线程面试题_2019java面试题库
java线程面试题_2019java面试题库
|
5月前
|
前端开发 Dubbo Java
spring面试题_spring mvc面试题_springboot面试题库
spring面试题_spring mvc面试题_springboot面试题库
|
5月前
|
负载均衡 Dubbo 安全
dubbo面试题库
dubbo面试题库