LiveData原理面试一问还不懂?

最近公司凉了,准备面试一家公司的车控岗位,想着对车控信号的封装应该会用到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接口)如下。

看一下LiveData源码中的observe方法

less 复制代码
@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集合。

csharp 复制代码
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简单易懂。

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

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

ini 复制代码
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你会发现

less 复制代码
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

less 复制代码
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进行数据回调。

kotlin 复制代码
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),而不是抛出一个错误。

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

java 复制代码
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

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

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

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

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

scss 复制代码
@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

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

ini 复制代码
Handler().postDelayed({
    owner.funStarted()
    data.value = "222"
},3000)

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

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

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

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

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

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

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

forwardPass中会进行observerEvent的分发

dispatchEvent 中进行mLifecycleObserver.onStateChanged回调

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

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

kotlin 复制代码
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解注册问题

less 复制代码
@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生命周期状态了。它会默认一直是活跃的。所以在我们使用的时候,也需要自己进行解注册的处理。

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

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

7. LiveData怎么转换数据类型

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

ini 复制代码
val data = MutableLiveData<UserInfo>()

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

kotlin 复制代码
val userLv: LiveData<Int> = Transformations.map(data) { it.userLv }

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

kotlin 复制代码
val userLv: LiveData<Int> = Transformations.switchMap(data) {
    MutableLiveData(it.userLv)
}

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

kotlin 复制代码
// 这是本地数据源
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

kotlin 复制代码
// 这是本地数据源
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...

相关推荐
熏鱼的小迷弟Liu3 分钟前
【消息队列】如何在RabbitMQ中处理消息的重复消费问题?
面试·消息队列·rabbitmq
求梦8201 小时前
前端八股文【CSS核心面试题库】
前端·css·面试
NAGNIP8 小时前
万字长文!回归模型最全讲解!
算法·面试
qq_318121599 小时前
互联网大厂Java面试故事:从Spring Boot到微服务架构的技术挑战与解答
java·spring boot·redis·spring cloud·微服务·面试·内容社区
且去填词10 小时前
Go 语言的“反叛”——为什么少即是多?
开发语言·后端·面试·go
青莲84313 小时前
RecyclerView 完全指南
android·前端·面试
青莲84313 小时前
Android WebView 混合开发完整指南
android·前端·面试
37手游后端团队16 小时前
gorm回读机制溯源
后端·面试·github
C雨后彩虹16 小时前
竖直四子棋
java·数据结构·算法·华为·面试
CC码码17 小时前
不修改DOM的高亮黑科技,你可能还不知道
前端·javascript·面试