「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」
前言
前面介绍了关于LiveData的一些类,但是我们平时使用还不仅如此,还有几个常用转换方法我们也经常使用,掌握这些方法原理对日常开发至关重要。
正文
在源码中代码给我们提供了Transformations类专门用来处理LiveData,里面最常见的处理函数就是map和switchMap,在我们平时或许用的不多,但是一定要了解其原理和含义,才能用的更好。
因为在其他很多库中都有这2个类似的函数身影。
map函数使用
对于map函数这里注意它的核心是转换,也就是根据一个LiveData的值转换成另一个值,这里其实蛮好理解,我们来看个例子:
//源LiveData,数据类型是UserDataInfo类型
val userLiveData = MutableLiveData<UserDataInfo>()
//转换后的LiveData,数据类型是String
val userInfo:LiveData<String> = Transformations
.map(userLiveData){
user -> "${user.name} + ${user.creator}"
}
这里可以发现当userLiveData值变化的时候,就会生成一个新的String的值。
map函数原理
说起原理,这里必须要理解前一篇文章说的MediatorLiveData,有没看的同学可以查看:
# LiveData源码分析3 -- MediatorLiveData的使用与原理解析
map函数就是利用了MediatorLiveData的巧妙使用,源码如下:
//必须主线程调用
@MainThread
@NonNull
//有2个类型参数,分别是源LiveData的类型和目标LiveData的类型
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
//参数 见分析1
@NonNull final Function<X, Y> mapFunction) {
//新建一个MediatorLiveData对象
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
//见分析2
result.setValue(mapFunction.apply(x));
}
});
返回这个MediatorLiveData即可
return result;
}
分析1:直接看一下这个参数是啥:
//单接口方法
public interface Function<I, O> {
O apply(I input);
}
注意这里X是源LiveData的类型,而Y是目标LiveData的类型,所以这里只是值的转换。
分析2:map原理非常简单,也就是当源LiveData即source里的值发生变化时,把这个值调用mapFunction函数后返回新的值即可,而且使用了MediatorLiveData后,在生成的LiveData添加观察者时只和它的Lifecycle有关,和源source没关系了
switchMap函数使用
说起switchMap很多人感觉和map差不多,但是它的区别还是挺大的,尤其是设计思路,前面所说的map函数它的思想是把值转换,而switchMap的思想是触发,也就是当源liveData的值变化时,这个变化的值就算一个开关即switch然后去获得一个新的LiveData。
//源livedata
val userLiveData = MutableLiveData<UserDataInfo>()
//根据变化的user,再去其他操作生成新的liveData
val userPhoto = Transformations.switchMap(userLiveData){
user -> {repository.getPhoto(user.photo)}
}
从上面这个例子就可以明显看出区别了,这里当user变化时,repository返回的是一个新的LiveData,也就是用user当作开关,去触发这个请求发生。
switchMap函数原理
既然有区别,那我们立马看看它的原理:
//必须主线程
@MainThread
@NonNull
public static <X, Y> LiveData<Y> switchMap(
@NonNull LiveData<X> source,
//注意参数,见分析1
@NonNull final Function<X, LiveData<Y>> switchMapFunction) {
//依旧创建MediatorLiveData
final MediatorLiveData<Y> result = new MediatorLiveData<>();
//给result添加source
result.addSource(source, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
//当源liveData数据发生变化时,直接运行function得到新的liveData
LiveData<Y> newLiveData = switchMapFunction.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
//具体步骤见分析2
//第一次时有新LiveData,必须执行到这里
mSource = newLiveData;
if (mSource != null) {
//这里又添加了一个source
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
//真正的设置值
result.setValue(y);
}
});
}
}
});
return result;
}
分析1:注意的参数类型和map函数完全不一样,它的参数是X即源LiveData的值,但是其返回值确是一个LiveData,到这里你或许就有疑惑了,比如X从A -> B -> C进行变化,从而产生了多个LiveData,但是我们知道一件事它返回的是一个LiveData,那这中间要怎么操作了,具体见分析2
分析2:对于具体操作还是有点绕的,首先是result.addSource(source)把源LiveData给添加到MediatorLiveData中,也就是源LiveData里的值变化时,始终能监听到,而且进行处理。
如何处理呢,前面说了,每当值变化时都会得到一个newLiveData,然后当这个newLiveData是第一个生成的LiveData,就把这个newLiveData赋值给mSource,把mSource添加到result中,当这个newLiveData值变化时去设置result值变化。
但是当源LiveData值又发生了变化,假如又生成了一个newLiveData,但是这个newLiveData和之前的不一样,这时就要注意了,通过下面代码:
if (mSource != null) {
result.removeSource(mSource);
}
会把之前的mSource给移除掉,添加新的newLiveData,这个特性必须要理解。
我画了一个大致的流程图:
这里的注意点,假如LiveDataY1的值一直在变化,但是这时生成了新的LiveDataY2,即使LiveDataY1的值再变化,这个通知也不会告诉给结果LiveData了。
distinctUntilChanged函数原理
在这个类中还有一个这个方法,这个是啥呢,从其名字大概就可以看出。
在我们之前介绍LiveData时,它有个特别不好的点,就是setValue方法没有判断新、旧值是否一样,都会去通知观察者,所以为了达到当只有这个LiveData的值和旧值不一样时才去通知观察者,但是直接改LiveData或许不太好,这里就有了另一个思路,把源LiveData转换成一个新的LiveData,这样就好实现这个功能了。
源码如下:
//必须主线程
@MainThread
@NonNull
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
//同样使用MediatorLiveData当作返回值
final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
outputLiveData.addSource(source, new Observer<X>() {
boolean mFirstTime = true;
@Override
public void onChanged(X currentValue) {
final X previousValue = outputLiveData.getValue();
//判断是否新旧值一样
if (mFirstTime
|| (previousValue == null && currentValue != null)
|| (previousValue != null && !previousValue.equals(currentValue))) {
mFirstTime = false;
outputLiveData.setValue(currentValue);
}
}
});
return outputLiveData;
}
这里代码非常简单就不用说了,但是这里给我们提供了一个思路,当你不想去更改LiveData代码时,可以通过这种方式间接的实现功能。
总结
这篇文章可以说和之前几篇有很大关系,基本都是利用MediatorLiveData来实现,同时对于Map和SwitchMap这2个函数有了设计思想的认识和区别,以及SwitchMap的注意点。
接下来还有一些关于LiveData的内容,主要就是结合现实问题以及LiveData的问题比如“数据倒灌”等,还有就算Google推荐实现Flow来替代LiveData的原因,我们文章继续分析。