距离上一篇文章差不多有一个月,感觉进度有点慢。唉,在这一个月里面,自己有点颓废啊!这篇文章居然拖了这么久。废话少说,今天我们就来学习一下Jetpack中的LiveData
,研究一下它的原理。
本文参考资料:
注意,本文LiveData的所有源码均来自于2.0.0版本
1. 概述
在正式分析LiveData之前,我们先来了解一下LiveData是一个什么东西吧(虽然大家可能都知道),不过我还是多说几句。
从官方文档的介绍来看的话,LiveData
是一个可观察的数据存储器类。但是与普通可观察类不同的是,LiveData
是能感知组件(Activity 、Fragment或者Service等)的生命周期。正因为拥有这个感知能力,使得LiveData
在组件Destroy时能自动移除观察者对象,这样便能避免很多NPE和内存泄漏的问题。
LiveData
的优点还有很多,这里就不过多的介绍,有兴趣的同学可以看看官方文档:LiveData 概览,官方文档介绍的非常详细。
本文从LiveData的基本使用开始,由浅入深的介绍LiveData的方方面面。同时由于本系列文章还未介绍ViewModel,所以本文LiveData的所有例子都在放在Activity中,但是按照官方推荐,LiveData和ViewModel结合使用,希望大家不要被我的例子误导。
本文打算从如下几个方面来介绍LiveData:
- LiveData的基本使用。
- LiveDdata的原理分析,其中会着重分析几个点:1. Version的控制,LiveData是怎么知道哪些Observer需要通知,哪些Observer不需要通知;2. LiveData是怎么实现只在活跃状态下才会通知Observer,同时又是如何实现从非活跃状态变为活跃状态会通知Observer;3. LiveData是怎么实现在组件Destroy时自动移除所有的Observer;4.
onActive
方法和onInactive
方法的作用和回调时机。MediatorLiveData
的基本使用和实现原理、以及怎么merge多个LiveData的数据。Transformations
的map
方法和switchMap
方法的区别和实现原理。
1. LiveData 的基本使用
首先,我们先来看看LiveData是怎么使用的:
class LiveDataDemoActivity : AppCompatActivity() {
private lateinit var mTextView: TextView
private lateinit var mButton: Button
private val mTextLiveData: MutableLiveData<String> = MutableLiveData()
private var mUpdateCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_data_demo)
mTextView = findViewById(R.id.textView)
mButton = findViewById(R.id.button)
mTextLiveData.observe(this, Observer {
mTextView.text = it
})
mButton.setOnClickListener {
mTextLiveData.value = "update content ${mUpdateCount++}"
}
}
}
上面的例子展示了一个简单的效果,当我们点击Button时,更新TextView显示的内容。相比较于以前的例子,我们这个例子不会直接操作TextView来实现更新的,而是更新LiveData,间接的更新了TextView显示的内容。可能有人在想,这不是多此一举吗?
答案当然是否定的,这样做有一个很多的好处,就是实现了View层和Model层的解耦。这样来想,如果Model层很多地方都需要更新TextView的内容,岂不是都要持有TextView的引用吗?而有了LiveData之后,只需要持有LiveData对象即可。这样就不会导致内存泄漏和空指针异常。简而言之,LiveData就是数据驱动,数据更新了,对应的UI也跟着更新。
切记,真实开发不应该把LiveData直接放在View层。它应该放在Model层,用来连接View层和Mode层。
同时,我们可以从上面的例子中发现一个小细节,我们并没有在Activity的onDestroy
方法里面去remove它的Observer。这是因为,LiveData它会自己监听组件的生命周期,当组件Destroy时,会自动remove Observer,从而避免空指针异常。
如上便是LiveData的基本使用,是不是很简单的?确实比较简单,我们还有很多扩展使用没有介绍,这些都会在后面的内容中介绍。
2. LiveData的原理分析
我们已经了解了LiveData的基本使用,至此我们就来分析它的源码。我们先来看看几个方法:
方法名 | 解释 |
---|---|
observe | observe是LiveData非常重要的一个方法,作用是给LiveData添加一个 观察者对象(Observer)。这个方法两个参数,其中第一个参数是 LifecycleOwner 对象,表示LiveData需要感知它的生命周期。 |
observeForever | 与observe方法相同作用还有一个observeForever 方法。该方法跟observe 方法有一个很大的区别就是,通过该方法不需要感知组件的生命周期,也就是无论在何时更新了数据,通过该方法添加 的Observer都会被通知到。使用该方法需要注意的是:必须在适 当的时机中调用 removeObserver 方法,用来remove对应的Observer。 |
(1). observe方法
我们先来看看observe
方法的源码:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
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;
}
owner.getLifecycle().addObserver(wrapper);
}
observe
方法只要分为如下几步:
- check 组件的生命中周期。如果组件的生命已经处于Destroy状态,没有必要再添加Observer。
- 将传递进来的两个参数包装成一个
LifecycleBoundObserver
对象,同时检查该Observer是否已经添加到其他组件里面,如果已经添加, 则会抛出IllegalArgumentException
。- 给
LifecycleOwner
添加一个Lifecycle的观察者对象(即LifecycleBoundObserver 对象)。从这里,我们可以看出来,LifecycleBoundObserver
的本质就是一个LifecycleObserver
。
(2). observeForever方法
看完了observe
方法的实现,我们再来看看observeForever
:
@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
assertMainThread("observeForever");
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
wrapper.activeStateChanged(true);
}
跟observe
方法相比,observeForever
方法主要有两个区别:
- 使用
AlwaysActiveObserver
来监听生命周期。同时从AlwaysActiveObserver
的实现中,我们可以看出来,shouldBeActive
方法永远为true,表示说是AlwaysActiveObserver
永远能够接受到通知的。- 直接调用了
activeStateChanged
用来更新状态,并且尝试着通知Observer。而observe
方法是通过监听生命周期进而间接的回调activeStateChanged
方法。
(3).ObserverWrapper和LifecycleBoundObserver
在正式了解LifecycleBoundObserver
之前,我们先来看看它的uml类图吧。
在我们了解了Lifecycle之后,我们知道生命周期的回调是在onStateChanged方法里面。所以,我们来看看
LifecycleBoundObserver
对onStateChanged
方法的实现:
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
从上面的代码中,我们可以得到一个信息,就是当组件(Activity、Fragment等)Destroy时,LiveData会自动移除对应的Observer,这恰好与我们前面所说的内容与之呼应。其次,我们会发现onStateChanged
方法里面回调的是activeStateChanged
方法,我们来看看这个方法:
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
activeStateChanged
方法主要分为如下几步:
- 如果状态就是最新状态,那么表示更新通知过所有的Observer,没必要再通知了。比如说,在Activity的onStart和onResume的两个生命周期里面,都会回调
activeStateChanged
方法,并且newActive
都为true,当我们已经在onStart
通知过了,在onResume
时就没必要再通知了。- 更新LiveData的mActiveCount,mActiveCount表示的就是LiveData活跃的Observer。如果LiveData活跃的Observer为0,同时该Observer将更新为活跃状态,那么就会回调LiveData的
onActive
方法;反之,如果该Observer变为非活跃状态,同时mmActiveCount为0,则回调onInActive
方法。- 如果该Observer更新到活跃状态,会调用
dispatchingValue
方法,去尝试通知每个Observer更新数据。
我们来看看dispatchingValue
方法:
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
在dispatchingValue
方法中,我们需要如下2点:
initiator
为null和不为null的区别。其中initiator
为null,则会遍历所有的Observer,然后尝试所有的Observer;initiator
不为null,则只会通知initiator
对象中持有的Observer。这种情况,我们可以简单的理解为局部通知和全局通知。那么什么时候为null,什么时候不为null。纵观整个LiveData的源码,当我们通过setValue和postValue方法更新LiveData持有的数据时,此时initiator
为null,则会通知所有的Observer;当时Observer新添加到LiveData中来,或者Activity和Fragment生命周期变化了,此时initiator
不为null,此时就是ObserverWrapper 通知自己内部持有的Observer。mDispatchingValue
和mDispatchInvalidated
这两个变量设计的非常巧妙。当第一个ObserverWrapper 调用dispatchingValue
方法时,此时mDispatchingValue将设置为true,mDispatchInvalidated
会未设置为false。如果第一个ObserverWrapper 还未执行完dispatchingValue
方法,第二个ObserverWrapper因为部分原因(比如数据更新了或者生命周期变换了)也来调用dispatchingValue
,此时它会将第一个ObserverWrapper的调用全部失效,即mDispatchInvalidated
设置为true,然后让第一个ObserverWrapper 重新遍历通知所有的Observer。这样能保证每个Observer能收到最新的通知,接受到最新的数据。
我们知道,在dispatchingValue
方法中并没有真正的Observer回调方法。真正回调Observer的方法的地方是在considerNotify
方法里面。我们来看看:
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
considerNotify
方法主要分为如下几步:
- 判断
ObserverWrapper
是否活跃状态,如果不是活跃状态,则不用通知更新。- 判断当前组件是否处于活跃状态,如果不是活跃状态,则调用
activeStateChanged
方法,将当前ObserverWrapper
设置为非活跃状态。可能有人有疑问,前面我们已经判断是否活跃,为啥在这里还要判断组件是否为活跃状态,这俩不是同步的吗?从Google爸爸的注释来看,可能存在组件已经更新了状态,但是我们还没有收到对应的Event来更新ObserverWrapper
的状态。至于具体是什么情形呢?官方也没有给出准确的答案,对此我也有疑问。- 判断当前
ObserverWrapper
的Version,如果当前的Version大于等于LiveData本身的版本,表示当前ObserverWrapper
已经通知更新了,不需要再通知。- 调用Observer的
onChanged
方法。在这里,我们终于看到了真正的回调。
对于onChanged
方法回调的路径真是不容易,由于过程比较繁琐,我在这里简单的总结。
onChanged方法调用路径主要分为两条:
LifecycleBoundObserver # onStateChanged
->LifecycleBoundObserver # activeStateChanged
->LiveData # dispatchingValue
->LiveData # considerNotify
。此条路径主要处理的case是组件生命周期的变化。其中activeStateChanged
方法保证了只有处于活跃状态Observer才会收到通知;dispatchingValue
方法保证此条路径自己一个Observer收到通知;considerNotify
方法再次保证处于活跃的Observer才会通知,同时还保证了一个Observer对于同一次更新收到一次通知(使用Version控制的)。LiveData # setValue
->LiveData # dispatchingValue
->LiveData # considerNotify
。此条路径处理的case是外部条件更新LiveData持有的数据。其他地方跟路径1类似,唯一区别就是在considerNotify
会通知所有的Observer。
3. MediatorLiveData的基本使用和原理分析
说完了最基本的LiveData,我们再来看看它的一个子类--MediatorLiveData
。用我们上面的那个例子,假设TextView显示的内容不是被一个LiveData控制的,而是多个,比如说10个,我们应该怎么实现呢?有一个简单的方法就是定义10个LiveData,同时监听这10个LiveData的变化。
对于这种方法的好与坏我不置可否,但是有一点,我需要提出来。我们都知道,LiveData是被定义ViewModel,View层从VIewModel里面获取LiveData从而监听。像上面的解决方法,我们就要从ViewModel获取这10个LiveData,想一想这代码会写成什么样子?那么有没有一种办法能帮助获取一个LiveData对象就能监听这个10个LiveData的变化,那当然就要让MediatorLiveData
出手了。
(1). MediatorLiveData的基本使用
MediatorLiveData
的作用就是合并多个LiveData源,我们先来看看它是怎么使用的吧。
class MediatorLiveDataDemoActivity : AppCompatActivity() {
private lateinit var mTextView: TextView
private lateinit var mButton: Button
private val mTextLiveData: MutableLiveData<String> = MutableLiveData()
private val mText2LiveData: MutableLiveData<String> = MutableLiveData()
private val mMergeTextLiveData: MediatorLiveData<String> = MediatorLiveData();
private var mUpdateCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mediator_live_data_demo)
mTextView = findViewById(R.id.textView)
mButton = findViewById(R.id.button)
mButton.setOnClickListener {
if (mUpdateCount % 2 == 0) {
mTextLiveData.value = "update content ${mUpdateCount++}"
} else {
mText2LiveData.value = "update content ${mUpdateCount++}"
}
}
}
private fun observeV1() {
mTextLiveData.observe(this, Observer {
mTextView.text = it
})
mText2LiveData.observe(this, Observer {
mTextView.text = it
})
}
private fun observeV2() {
val observer = Observer<String> {
mMergeTextLiveData.value = it;
}
mMergeTextLiveData.addSource(mTextLiveData, observer)
mMergeTextLiveData.addSource(mText2LiveData, observer)
mMergeTextLiveData.observe(this, Observer {
mTextView.text = it
})
}
}
这里我将普通实现方案(即observeV1
方法)和MediatorLiveData
实现方案(即observeV2
方法)。从具体实现的代码,虽然V2的代码比V1的多,但是V2更加收敛,我们只要知道mMergeTextLiveData
的存在即可,不需要管TextView的内容具体由几个LiveData控制的。
(2). MediatorLiveData的原理
从本质来看,MediatorLiveData
就是一个LiveData
,所以LiveData有的特点,MediatorLiveData都有。那么,我们直接来看一下,MediatorLiveData
独有的特点,就是能够合并多个LiveData源,我们去瞧瞧是怎么实现的。
我们直接从addSource
方法入手,看看其内部的实现:
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
throw new IllegalArgumentException(
"This source was already added with the different observer");
}
if (existing != null) {
return;
}
if (hasActiveObservers()) {
e.plug();
}
}
addSource
方法内部做的事情非常的少,总结起来就是分为两步:
- 将传递进来的LiveData和对应的Observer包装成一个Source对象。
- 如果此时MediatorLiveData已经有活跃的Observer,就表示此时对应的组件已经处于活跃状态了,那么就是调用plug方法。
我们从addSource
方法看不出来有啥特殊的地方,我们要想知道MediatorLiveData
是怎么同时监听多个LiveData,必须从Source
下手。
private static class Source<V> implements Observer<V> {
final LiveData<V> mLiveData;
final Observer<? super V> mObserver;
int mVersion = START_VERSION;
Source(LiveData<V> liveData, final Observer<? super V> observer) {
mLiveData = liveData;
mObserver = observer;
}
void plug() {
mLiveData.observeForever(this);
}
void unplug() {
mLiveData.removeObserver(this);
}
@Override
public void onChanged(@Nullable V v) {
if (mVersion != mLiveData.getVersion()) {
mVersion = mLiveData.getVersion();
mObserver.onChanged(v);
}
}
}
Source
的实现非常的简单,内部持有一个LiveData
对象和一个Observer
对象。同时我们看到的是,在plug
方法里面,通过调用LiveData的observeForever
方法用来监听LiveData的变化。当LiveData持有的数据发生了变化,会回调Source的onChanged
方法,onChanged
方法会调用内部持有的Observer的onChanged
方法,从而调用到外面我们定义的Observer对象中去。在那里,我们做了一件事--件更新的值设置到MediatorLiveData
中去,由于我们监听了MediatorLiveData
,所以所有LivaData变化都会经过MediatorLiveData
更新到TextView上去。
从这里,我们可以看出来,MediatorLiveData
类似于一个主管道,所有子管道的更新到都会流到这个主管道中去。
(3). 如何使用MediatorLiveData
merge多个LiveData呢?
我们知道在RxJava,zip操作符可以将两个数据流合并成一个数据流,再传递到下游去。LiveData中并没有现成的API供我们使用,所以需要自我实现。下面我将贴出一个简单的Demo来展示如何实现的,主要思路来源于:LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData。
fun <T, A, B> LiveData<A>.mergeOtherLiveData(
other: LiveData<B>,
onChanged: (A, B) -> T
): MediatorLiveData<T> {
val result = MediatorLiveData<T>()
val mergeInvoker = {
val selfValue = this.value
val otherValue = other.value
if (selfValue != null && otherValue != null) {
result.value = onChanged(selfValue, otherValue)
}
}
result.addSource(this) {
mergeInvoker.invoke()
}
result.addSource(other) {
mergeInvoker.invoke()
}
return result
}
这里,我给LiveData定义了一个扩展方法,主要就是通过MediatorLiveData
实现的,具体实现也比较简单,这里就不解释。我们来看一下怎么使用的:
class MergeLiveDataActivity : AppCompatActivity() {
private val mAvatarLiveData: MutableLiveData<Avatar> = MutableLiveData()
private val mIntroductionLiveData: MutableLiveData<Introduction> = MutableLiveData()
private lateinit var mUserLiveData: MutableLiveData<User>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mUserLiveData =
mAvatarLiveData.mergeOtherLiveData(mIntroductionLiveData) { avatar, introduction ->
User(avatar, introduction)
}
}
}
在这里,我简单的定义了一个User类。其中User类内部分为两个部分:Avatar和Introduction。我们可以这样来假设,在实际开发中有两个请求分别这两部分的数据,只有等待两个请求回来时,才算是User信息请求完成。所以,这里我们通过mergeOtherLiveData
方法来合并两个LiveData的数据。
4. Transformations的map方法和switchMap方法的区别和实现原理
我们在使用LiveData时,有一个场景应该是非常常见的--类型映射,比如说从A类型映射到B类型。这里我们就需要了解map和switchMap。
(1).map方法
假设,有一个LiveData<User>对象,我们想要从中获取User的name和age拼接成一个String,这个应该怎么办呢?普通的方法就是给这个对象添加Observer对象,然后在手动拼接处理。这种方法有一个问题就是,如果很多地方都需要这样处理,那岂不是要添加很多的Observer对象。此时,更好的办法就是通过map方法进行简单的转换:
val mUserLiveData:MutableLiveData<User> = MutableLiveData()
val mInfoLiveData:MutableLiveData<String> = map(mUserLiveData) { user->
MutableLiveData(user.mName + " " + user.age)
}
上面是我随手写的伪代码,大家先凑合着吧。接下来我们来看一下map方法的实现:
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(mapFunction.apply(x));
}
});
return result;
}
map方法最大的特点就是只支持静态转换。什么叫做静态转换,就是要想转换的LiveData对象已经确定,即已经初始化了。如果我们要想动态转换应该怎么办呢?那就是switchMap方法,这也是这两个方法最大的区别。
(2). switchMap方法
在了解switchMap方法之前,我们先来了解一下什么叫做动态转换吧。假设有两个LiveData,其中一个LiveData持有的userId,我们称之为mUserIdLiveData
,一个LiveData持有的User,我们称之为mUserLiveData
。当mUserIdLiveData
发生了变化,我们想要的结果是mUserLiveData
也随之发生变化。
因为User要等请求回来,我们才知道具体的对象,所以此时map方法不适合此情形,那么用switchMap 应该怎么实现的呢?
val mUserIdLiveData = MutableLiveData<String>()
val mUserLiveData = switchMap(mUserIdLiveData){ userId ->
repository.loadUserById(userId)
}
整个过程是如此的:mUserIdLiveData
的更新导致了User的更新,如果不通过switchMap方法,mUserLiveData 根本不能知道Id更新。
接下来,我们再看看switchMap的实现:
@MainThread
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;
// (1)
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = switchMapFunction.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
// (2)
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
实现也是非常的简单,我结合上面的例子简单的解释一下。当mUserIdLiveData
更新,(1)处方法会被调用,此时通过我们传递进去的Callback接口创建一个新的LiveData,然后在这个新的LiveData添加一个观察者,当这个新的LiveData数据请求回来,会回调(2)方法,此时result就被更新。如果我们给这个result对象设置了Observer观察者,我们就能监听到数据的更新。
不得不说,switchMap方法设计真的太巧妙,短短几行代码就实现了效果。
5. 总结
到此为止,LiveData相关的介绍和分析也结束了。在这里,我对本文做一个简单的总结。
- LiveData通知Observer主要有两个路径:1. 通过setValue或者postValue,此路径对所有活跃的Observer都生效;2. 添加一个新的Observer,如果此时对应的组件处于活跃状态,那么该Observer的
onChanged
方法也会被回调,但是只有该Observer的onChanged
方法被回调,并不会影响到其他Observer。MediatorLiveData
可以merge多个LiveData源。内部是通过一个Source类实现,当某一个LiveData更新了,Source类将对应的更新作用到MediatorLiveData
的Observer。- map 和switchMap区别在于:map只支持静态转换,而switchMap同时支持静态转换和动态转换。同时map和switchMap内部都是用
MediatorLiveData
实现的。
网友评论