文章目录
一、简单介绍
Transformations 类是 LiveData 的一个功能扩展类,其是一个私有化构造方法的工具类,且只提供 3 个方法使用,虽然数量不多,但胜在实用,这三个方法分别是:
- map
- switchMap
- distinctUntilChanged
那么接下来我们就一一分析每个方法及其源码实现过程,如果想要更清楚的了解源码建议先了解 这篇文章。
二、map
其实看名字我们就能大概知道这个方法是个什么样的存在……
类似于 RxJava 的 map 操作符、Kotlin 中数组的扩展函数 map,其实 Transformations 的 map 方法也是一个转化的功能。
1、普通实现
先看看效果:
private val originData = MutableLiveData<String>()
// 通过 Transformations.map 返回一个 LiveData
val mapData = Transformations.map(originData, Function<String,String> {
it.plus(" World")
})
mapData.observe(this, Observer {
Log.e("mapData",it)
})
// 点击按钮给 originData 赋值
btn.setOnClickListener {
originData.value = "Hello"
}
控制台输出:
mapData: Hello World
2、转换类型
相信大家也看到了,map()
方法的第二个参数的类型有两个泛型,事实上第一个个泛型的类型是确定的,那就是对应 map()
方法第一个参数 LiveData 的泛型,那么第二个泛型就是控制返回的 LiveData 的类型。改造上面的例子:
... ...
val mapData = Transformations.map(originData, Function<String,Int> {
it.length
})
mapData.observe(this, Observer {
Log.e("mapData",it.toString())
})
... ...
控制台输出:
mapData: 5
3、Kotlin 扩展
... ...
val mapData = originData.map {
it.plus("world")}
... ...
如果遇到报错:Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1,那就在模块的 gradle 中添加:
android{
... ...
kotlinOptions{
jvmTarget = 1.8
}
}
4、源码分析
源码还是非常简单的:
@MainThread
@NonNull
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>(); //1
result.addSource(source, new Observer<X>() {
//2
@Override
public void onChanged(@Nullable X x) {
result.setValue(mapFunction.apply(x)); //3
}
});
return result; //4
}
public interface Function<I, O> {
O apply(I input);
}
- 创建一个 MediatorLiveData 并且在 标记4 返回。
- 使 MediatorLiveData 实例
result
添加监听,使得原source
发生变化的时候会发生回调,执行onChanged()
方法。 mapFunction.apply(x)
类似于 Kotlin 的高阶函数,将源值暴露出去,让我们去直接接触这个值,并且对其操作转换。
三、switchMap
看名字,是不是又联想到了 RxJava 的 switchMap 操作符?
实际上 Transformations 的 switchMap 也是一样的功能:舍弃原来的 LiveData,创建一个新的 LiveData 来接管它的变化。
1、普通实现
private val originData = MutableLiveData<String>()
// 通过 Transformations.switchMap返回一个 LiveData
val switchMapData = Transformations.switchMap(originData, Function {
return@Function MutableLiveData<String>().apply {
this.value = it.plus("world")
}
})
switchMapData.observe(this, Observer {
Log.e("switchMapData",it)
})
// 点击按钮给 originData 赋值
btn.setOnClickListener {
originData.value = "Hello"
}
控制台输出:
switchMapData: Hello world
对比一下 map()
方法,switchMap()
显得如此的不堪……但存在即合理,它的也有它的使用场景。
2、对比 Map
我们先对比一下 switchMap()
和 map()
的使用过程:map()
的操作已经是在消费上层 LiveData 的值,而 switchMap()
同样使消费了上层 LiveData 的值,但是它又创建了新的生产者,所以其真实的消费并不是由 switchMap()
来执行的。
知道了这点后,我们就可以比较清晰的了解 switchMap()
的使用场景了:那就是通过其创建一个新的 LiveData,并且我们可以在其间做一些操作,无论是单纯的转变类型,或是时间上的耗时操作……
3、Kotlin 扩展
... ...
val switchMapData = originData.switchMap {
return@switchMap MutableLiveData<String>().apply {
this.value = it.plus("world")
}
}
... ...
4、源码分析
@MainThread
@NonNull
public static <X, Y> LiveData<Y> switchMap(
@NonNull LiveData<X> source,
@NonNull final Function<X, LiveData<Y>> switchMapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = switchMapFunction.apply(x); //1
if (mSource == newLiveData) {
//2
return;
}
if (mSource != null) {
result.removeSource(mSource); //3
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
//4
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
前面还是同样的操作,创建一个 MediatorLiveData 来接管源 source
的变化。
- 这里的
newLiveData
就是我们在外面自己创建的。 - 如果匿名内部类已经保存了该 LiveData 的实例,那就直接返回,从这里我们就可以得出额外的信息:如果我们在
switchMap()
中返回相同的 LiveData 其实是不起作用的。 - 如果匿名内部类的
mSource
存在值,对应的就是之前switchMap()
中返回的 LiveData ,这时候就会进行一个removeSource()
的操作,从这里我们也恶意得到一个很有用的信息:如果我们在switchMap()
中返回不同的 LiveData ,那么之前的 LiveData 就会失效。 - 这一段就没什么好说的了,就是纯粹的值的传递消费过程。
四、distinctUntilChanged
这个方法的作用就没有 RxJava 作为参考了……
所以就直接说这个方法的作用是怎样的吧:这个方法也能够返回一个监听 source
的 LiveData,当源 LiveData 发生变化 ,其返回的 LiveData 也能够发生变化……(这可不就是map()
吗?( ఠൠఠ )ノ) ,但不同的是,如果源 LiveData 一直进行 setValue()/postValue()
同一个值,那么返回的 LiveData 只接收第一次返回的值,除非源 LiveData 设置新的值。
1、普通实现
private val originData = MutableLiveData<String>()
// 通过 Transformations.distinctUntilChanged 返回一个 LiveData
val newData = Transformations.distinctUntilChanged(originData)
newData.observe(this, Observer {
Log.e("newData",it)
})
// 点击按钮给 originData 赋值
btn.setOnClickListener {
originData.value = "Hello"
}
然后疯狂的点击按钮,而控制台只有一行输出,并且通过源码我们也能够发现它没有转换类型的功能,source
的类型是怎样的,那么返回的 LiveData 的类型就是怎样的。
2、Kotlin 扩展
val newData = originData.distinctUntilChanged()
3、源码分析
@MainThread
@NonNull
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
outputLiveData.addSource(source, new Observer<X>() {
boolean mFirstTime = true; //1
@Override
public void onChanged(X currentValue) {
final X previousValue = outputLiveData.getValue(); //2
if (mFirstTime
|| (previousValue == null && currentValue != null)
|| (previousValue != null && !previousValue.equals(currentValue))) {
//3
mFirstTime = false; //4
outputLiveData.setValue(currentValue); //4
}
}
});
return outputLiveData;
}
前面仍然是索然无味的 MediatorLiveData 来接管上层的 LiveData 的变化。
- 记录一个布尔值,是否是第一次赋值。第一次的话就是默认为 true 了,那么直接看到 标记3 的位置,所以第一次一定会执行 标记4 的代码。
- 那么接下来我们就以非第一次的赋值场景来进行分析。这里会先拿到现有的值。
- 非第一次的话,
mFirstTime
一定为 false,先执行(previousValue == null && currentValue != null)
来进行判空的操作,这里previousValue
一定是不为空的,所以整个句式返回的也是 false,那么接下来就是值的对比,对比现在的值和新的值是否相等,如果相等的话也会返回 false ,所以一系列的分析下来,我们就可以得出上面刚开始的结论。
五、小结
Transformations 的 3 个函数都十分的具有实用性,在具体的开发过程中,我们在业务层完全就可以依赖 LiveData 去实现值的传递的过程,甚至是变换的过程。使用起来也具有较强的灵活性、技巧性,实乃居家必备之良药。