LiveData源码分析4 -- Transformations类解析

「这是我参与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,这个特性必须要理解。

我画了一个大致的流程图:

switchMap.png

这里的注意点,假如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的原因,我们文章继续分析。

猜你喜欢

转载自juejin.im/post/7055820629668265992