LiveData原理面试一问还不懂?

2 阅读11分钟

最近公司凉了,准备面试一家公司的车控岗位,想着对车控信号的封装应该会用到LiveData吧。学一下他的原理吧,出了很久的技术了。LiveData源码并不复杂,看过源码懂得都懂,网上也有一定的介绍文章了。本文主要看缘分给安卓小白用于扫盲。

先抛出问题,如果以下内容全清楚那么可以右上角点×了。

  1. LiveData更新数据的方法
  2. LiveData如何感知生命周期
  3. LiveData是否具有粘性,以及原因
  4. 是否遇到过LiveData丢失数据的情况
  5. Fragment使用LiveData需要 注意生什么
  6. 想要一直监听LiveData,无论是否活跃怎么办
  7. LiveData怎么转换数据类型

LiveData是啥有啥用,为啥要使用LiveData?

这个如果不知道建议看一下来至于快手大佬的这篇文章 juejin.cn/post/684490…

1. LiveData更新数据的方法

首先我们需要创建一个LiveData对象,然后调用他的observe方法,传入一个生命周期的owner对象和一个我们自定义的观察者(他实现了onChanged接口)如下。

image.png 看一下LiveData源码中的observe方法

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {

    //检测非主线程报错。
    assertMainThread("observe");
    
    //检测owner生命周期
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    
    //将我们传入的生命周期owner对象和我们自定义的观察者对象observer 包装一下 。
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    
    //将包装后的对象,以我们自定义的观察者作为key,存入一个集合mObservers(具体是啥后面介绍)
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    
    //报错检测机制先不关心
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    
    //包装后的对象给到LifecycleRegistry,记住他,这里在介绍第二个问题的时候细讲
    owner.getLifecycle().addObserver(wrapper);
}

源码中LiveData有一个mVersion属性和mObservers集合。

public abstract class LiveData<T> {

//每一个LiveData对象有一个mVersion属性,记住这个属性很关键。
private int mVersion;

//一个支持迭代时更新数据的Iterable
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
        new SafeIterableMap<>();
}

小结,看到这里你需要知道。

每一个LiveData对象中都有一个mObservers集合。mObservers中可以存储该LiveData对象的所有观察者。即一个LiveData对象可以有多个观察者。

LiveData类中更新数据的方法1。setValue简单易懂。

@MainThread
protected void setValue(T value) {
    //检查是否在主线程使用,非主线程报错
    assertMainThread("setValue");
    
    //每一次更新数据将版本属性++
    mVersion++;
    
    //更新数据
    mData = value;
    
    //分发数据
    dispatchingValue(null);
}

LiveData类中更新数据的方法2。postValue一般在子线程调用,他与上面setValue的区别,没有mVersion++

protected void postValue(T value) {
    boolean postTask;
    
    //锁保护线程安全,即支持主线程调用
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    
    //切换到主线程run一个runnnable
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

震惊为啥posetValue不用更新mVersion呢?看一下他的mPostValueRunnable你会发现

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        
        //原来postValue 在切换线程以后又调用了一次 setValue,
        //那么mVersion,自然也是通过setValue更新的
        setValue((T) newValue);
    }
};

再看一下setValue中分发的方法dispatchingValue,内部遍历调用了considerNotify,下面的mObservers前文介绍过是一个支持边迭代,边读写Iterable

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    、、省略其他代码
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
        
        //遍历调用 considerNotify
    considerNotify(iterator.next().getValue());
    if (mDispatchInvalidated) {
        break;
    }
}
 、、省略其他代码
}

considerNotify中对observer的状态进行过滤,以及版本进行判断,全部通过后,使用mObserverdeonChanged进行数据回调。

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    //**********记住这里后面会提到***********
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

扩展:不知道你看完会不会有这么一个疑问。mVersion版本信息用int很合理吧。比long 和 double都节省内存,很常规的用法。但是这个setValuemVersion一直++,极端的情况会不会有超过int最大值的情况,那再++ 超过了最大值会不会报错了?看代码里好像没有对超过int最大值的判断,难道是这个点默认不在框架设计的范围内吗?

百度一下你会得到这样的答案

在Java中,int 类型的变量是有固定范围的,其取值范围是从 -2,147,483,648 到 2,147,483,647。当你尝试将一个 int 类型的变量增加到超出这个范围时,会发生整数溢出(integer overflow),而不是抛出异常。整数溢出会导致变量的值回绕到 int 类型的最小值。

例如,如果你有一个 int 类型的变量,其值为 Integer.MAX_VALUE(即2,147,483,647),然后你执行 int++,这个变量的值会变为 Integer.MIN_VALUE(即-2,147,483,648),而不是抛出一个错误。

这里有一个简单的例子:不信的可以去试一下,反正我信。

int value = Integer.MAX_VALUE;
value++; // 这会导致整数溢出,value 的值现在变成了 Integer.MIN_VALUE
System.out.println(value); // 输出 -2147483648

基础薄弱的作者此时的内心独白:卧焯妙啊。

(不过捏,虽然不会报错,但是mVersion如果是int最大值的话,再++,会导致mLastVersion > mVersion,从而触发一次return)

2. LiveData如何感知生命周期

LiveData本身是不具备感知生命周期能力的。liveData之所以能够感知生命周期,完全归功于lifecycle。 在官方对于lifecycle的介绍中提到了一个类LifecycleOwner,说你可以自己建一个类,实现了LifecycleOwner接口即可。 developer.android.google.cn/reference/k…

一般LiveData我们是在Activity里进行观察。liveData.observe(),的第一个参数我们会传入this

image.png

在Activity的super,super,super...中你会发现ComponentActivity,并且他实现了LifecycleOwner 我们可以参考它实现一个自己的LifecycleOwner

那为啥要自己实现一个呢?,面试别人问我,我就说我只要实现lifecyce提供的LifecycleOwner接口就可以。

那这时候如果面试官继续问你,那具体该实现啥呢,有啥常规的功能或者技巧。我们是不是就懵逼了。为了让自己的回答更有底气,我们看一下如何自定义一个LifecycleOwner

image.png 然后在Activity里把liveData.observe的第一个参数,传入我们自定义的TestLifecycleOwner image.png 运行一下,看一下日志。那么不出意外的话,意外就发生了。日志中只有funCreate()funOnCreateCallback() 中的日志输出。而没有Livedata的数据变化日志。 1709364767375.png 沃德发,为啥呢。给data.value加上断点(如果是java那也就是,data.setValue("111"))。Debug一下你会发现卡在了这里被return了。 image.png 这个shouldBeActive会检测生命周期的状态,至少为STARTED,否则会return不继续执行

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

那么我们给TestLifecycleOwner加两方法。更新它的生命周期到OnStarted image.png 再改一下我们的Activity代码。让他3s后更新TestLifecycleOwner生命周期到OnStart

Handler().postDelayed({
    owner.funStarted()
    data.value = "222"
},3000)

再看日志,一切正常3s后生命周期变为OnStart,liveData的数据变化也可以观察到 image.png 疑问:在OnCreate状态下,set的数据 111,由于生命周期的关系导致没收到。后面生命周期更新为OnStart后,收到了之前的数据111。

*这个是不是表示liveData数据具有粘性啊? 是的,那么我们继续下个议题。

3. LiveData是否具有粘性,以及原因

还是上面的栗子,debug一下funStarted()我们会发现。(就是java中的setCurrentState)

fun funStarted(){
    mRegistry.currentState = Lifecycle.State.STARTED
    Log.i(TAG, "主动触发 funStarted: 生命周期更新为 ${mRegistry.currentState}")
}

会先到LifecycleRegistry.setCurrentState更新生命周期状态

image.png 接着会到LifecycleRegistry中moveToState方法的Sync() image.png 接着到Sync的forwardPass

image.png forwardPass中会进行observerEvent的分发

image.png dispatchEvent中进行mLifecycleObserver.onStateChanged回调

image.png 上面会回调到。LiveData中LifecycleBoundObserveronStateChanged image.png 更新生命周期。满足条件后,继续分发(注意下面的this是LiveData) image.png 后面就到了我们熟悉的流程。

image.png 检测生命周期,检查LiveData的mVersion和观察者的version是否相等。触发观察者回调(这里就是我们在Activity中实现的onChanged方法) image.png 结束

val owner = TestLifecycleOwner()
data.observe(owner,object : Observer<String> {
    override fun onChanged(t: String?) {
        Log.i(TAG, "onChanged: $t")
    }
}

4. 是否遇到过LiveData丢失数据的情况

如果有在子线程或者多线性频繁调用liveData.postValue的话。那应该会遇到过这种问题。 这个其实人家官方是不建议你这样去使用liveData的。

非要用liveData处理,要么单线程每次更新加延时,要么频繁切换线程到主线程setValue去更新,也是可以的代价就是浪费性能。

业务上如果不是必须展示每次数据的变化,那我们就不处理。如果业务上一定要处理那就改用其他的方式,不使用liveData,比如Rxjava或者Flow。

如果使用Flow可以参考来自于字节大佬的这篇文章。 juejin.cn/post/700107…

5. Fragment使用LiveData需要 注意生什么

首先这个问题为什么是Fragment而不是Activity

前文我们了解到 liveData通过observe设置生命周期的Owner。 在LiveData类中的LifecycleBoundObserver有这样一段代码,在LiveData绑定的LifecycleOwner的生命周期变化为OnDestory时会移除对应的观察者。所以将Activity作为LifecycleOwner时可以自动的处理LiveData解注册问题

@Override
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    //注意看这里*******onDestroy的时候移除了Observer*************
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    Lifecycle.State prevState = null;
    while (prevState != currentState) {
        prevState = currentState;
        activeStateChanged(shouldBeActive());
        currentState = mOwner.getLifecycle().getCurrentState();
    }
}

那么Fragment有什么不一样。Fragment的加载方式有addreplace两种。

  1. 使用add添加新的Fragment,那么原本旧的Fragment不会被移除栈,会继续保留在栈内,旧的Fragment也就不会触发OnDestroy,LiveData也就不会去进行解注册操作。
  2. 而使用replace添加新的Fragment,会使旧的Fragment进行出栈,导致旧的Fragment触发OnDestroy 如果将Fragment作为LiveDataLifecycleOwner

所以是说我们使用Fragment作为LiveData的lifecycleOwner的时候要,使用replace不使用add吗

不是的,这样做太局限了,某些业务的场景下,可能存在就是要使用add的情况。 那这个时候我们可以用viewLifecycleOwner,其实就是FragmentViewLifecycleOwner,它在package androidx.fragment.app中。官方推荐使用viewLifecycleOwner,在Fragment视图不显示的时候,会触发OnDestroy帮助我们进行LiveData的解注册。

6. 想要一直监听LiveData,无论是否活跃怎么办

这个问题就有点偏了,看业务经验。常规业务可能不太容易遇到。

可以使用LiveData的observeForever函数 这里查看一下源码,你会发现observeForever,是没有传lifecycleOwner的只有观察者一个参数。它连Owner都没有,就也没有相应的Owner生命周期状态了。它会默认一直是活跃的。所以在我们使用的时候,也需要自己进行解注册的处理。

image.pngobserveForever中会把每次传入的observer包装成一个新的wrapper,再调用每个新的warpper的activeStateChanged(true)。这里的AlwaysActiveObserverObserverWrapper的子类,并且本身没有activeStateChanged方法,所以其实是调用父类的方法。

1709454226388.png 也就是说,一个LiveData支持多个observeForever。即使这样写,传入的是相同的观察者,它也会给你每次调用时包装成一个新的wrapper

1709454836021.png

7. LiveData怎么转换数据类型

这个感觉是锦上添花的东西。比如你使用一个接口的回调结果展示获取用户的详细信息。

val data = MutableLiveData<UserInfo>()

后面,还有个其他的业务,需要监听用户等级的变化,展示其他的业务。使用Transformations可以实现这一操作。

val userLv: LiveData<Int> = Transformations.map(data) { it.userLv }

除了map,还有一个switchMap,switchMap每次会返回一个新的LiveData对象。

val userLv: LiveData<Int> = Transformations.switchMap(data) {
    MutableLiveData(it.userLv)
}

普通的类型转换,我们使用map就可以了。那什么场景下会使用到switchMap呢。 假设我们的ViewModel中暴露一个itemsLiveData用于给用户观察。 我们通过用户的意图(提供一个带有参数的getItemsLiveData方法),用户可以选择获取数据库中旧的数据,或者网络请求新的是数据。 那么我们可以通过以下代码实现。

// 这是本地数据源
private val localDataSource = MutableLiveData<List<Item>>()
// 这是网络数据源
private val networkDataSource = MutableLiveData<List<Item>>()
// 暴露给用户的liveData
val itemsLiveData = MutableLiveData<List<Item>>()

// 用于获取最终数据流的方法
fun getItems(dataSourceType: DataSourceType): LiveData<List<Item>> {
    // 使用switchMap根据dataSourceType的值切换数据源
    Transformations.switchMap(dataSourceType) { type ->
        when (type) {
            DataSourceType.LOCAL -> {
                // 如果选择本地数据源,返回本地数据的LiveData
                localDataSource
            }

            DataSourceType.NETWORK -> {
                // 如果选择网络数据源,返回网络数据的LiveData
                networkDataSource
            }
        }
    }
}

我们知道SwitchMap每次都会创建一个新的LiveData,很明显他不适合频繁调用的情况,必定浪费性能,那怎么优化一下呢。我们可以使用MediatorLiveData

// 这是本地数据源
private val localDataSource = MutableLiveData<List<Item>>()
// 这是网络数据源
private val networkDataSource = MutableLiveData<List<Item>>()
// 暴露给用户的liveData
val mediatorLiveData = MediatorLiveData<List<Item>>()

// 用于获取最终数据流的方法
fun getItems(dataSourceType: DataSourceType): LiveData<List<Item>> {
    // 使用switchMap根据dataSourceType的值切换数据源
        when (dataSourceType) {
            DataSourceType.LOCAL -> {
                // 如果选择本地数据源,返回本地数据的LiveData
                localDataSource.value = loadLocalData()
                mediatorLiveData.addSource(localDataSource) {
                    mediatorLiveData.value = localDataSource.value
                }
                return localDataSource
            }
            else ->{
                // 如果选择网络数据源,返回网络数据的LiveData
                networkDataSource.value = getNetWorkData()
                mediatorLiveData.addSource(localDataSource) {
                    mediatorLiveData.value = localDataSource.value
                }
                return networkDataSource
            }
        }
    }

疑问:那你说,我不用这个MediatorLiveData,我直接定义一个LiveData,数据来了,无论是网络,还是本地,还是别人给我传的,我酷酷一顿setValue做不了吗?

这样搞他兴许也能做,只不过有点违背常理。人家LiveData本身就是给你用来观察一个数据的变化用的。网络数据,本地数据,明显两个数据源。非要把他定义成一个数据去观察,代码也能写就是不优雅,不符合常理。

文章如有错误,请康概指出 thanks

参考

juejin.cn/post/708503… juejin.cn/post/697568… zhuanlan.zhihu.com/p/504082214 blog.csdn.net/shenshizhon…