最近公司凉了,准备面试一家公司的车控岗位,想着对车控信号的封装应该会用到LiveData吧。学一下他的原理吧,出了很久的技术了。LiveData源码并不复杂,看过源码懂得都懂,网上也有一定的介绍文章了。本文主要看缘分给安卓小白用于扫盲。
先抛出问题,如果以下内容全清楚那么可以右上角点×了。
- LiveData更新数据的方法
- LiveData如何感知生命周期
- LiveData是否具有粘性,以及原因
- 是否遇到过LiveData丢失数据的情况
- Fragment使用LiveData需要 注意生什么
- 想要一直监听LiveData,无论是否活跃怎么办
- 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 的状态进行过滤,以及版本进行判断,全部通过后,使用mObserverde 的onChanged进行数据回调。
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 都节省内存,很常规的用法。但是这个setValue 里mVersion 一直++,极端的情况会不会有超过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中LifecycleBoundObserver 的onStateChanged 更新生命周期。满足条件后,继续分发(注意下面的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 的加载方式有add 和replace两种。
- 使用add 添加新的Fragment ,那么原本旧的Fragment 不会被移除栈,会继续保留在栈内,旧的Fragment 也就不会触发OnDestroy,LiveData也就不会去进行解注册操作。
- 而使用replace 添加新的Fragment ,会使旧的Fragment 进行出栈,导致旧的Fragment 触发OnDestroy 如果将Fragment 作为LiveData 的LifecycleOwner
所以是说我们使用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)。这里的AlwaysActiveObserver 是ObserverWrapper 的子类,并且本身没有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...