引子
LiveData 是能感知生命周期的栖忠,可觀察的,粘性的贸街,數(shù)據(jù)持有者庵寞。LiveData 用于以“數(shù)據(jù)驅(qū)動(dòng)”方式更新界面。
換一種描述方式:LiveData 緩存了最新的數(shù)據(jù)并將其傳遞給正活躍的組件薛匪。
這一篇就 LiveData 的面試題做一個(gè)歸總捐川、分析蜘矢、解答皇筛。
1. LiveData 如何感知生命周期的變化?
先總結(jié)胀蛮,再分析:
- Jetpack 引入了 Lifecycle,讓任何組件都能方便地感知界面生命周期的變化渐白。只需實(shí)現(xiàn) LifecycleEventObserver 接口并注冊(cè)給生命周期對(duì)象即可尊浓。
- LiveData 的數(shù)據(jù)觀察者在內(nèi)部被包裝成另一個(gè)對(duì)象(實(shí)現(xiàn)了 LifecycleEventObserver 接口),它同時(shí)具備了數(shù)據(jù)觀察能力和生命周期觀察能力纯衍。
常規(guī)的觀察者模式中栋齿,只要被觀察者發(fā)生變化,就會(huì)無(wú)條件地通知所有觀察者襟诸。比如java.util.Observable
:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!hasChanged())
return;
arrLocal = obs.toArray();
clearChanged();
}
// 無(wú)條件地遍歷所有觀察者并通知
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
}
// 觀察者
public interface Observer {
void update(Observable o, Object arg);
}
LiveData 在常規(guī)的觀察者模式上附加了條件瓦堵,若生命周期未達(dá)標(biāo),即使數(shù)據(jù)發(fā)生變化也不通知觀察者歌亲。這是如何實(shí)現(xiàn)的菇用?
生命周期
生命周期是一個(gè)對(duì)象從構(gòu)建到消亡過(guò)程中的各個(gè)狀態(tài)的統(tǒng)稱(chēng)。
比如 Activity 的生命周期用如下函數(shù)依次表達(dá):
onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
要觀察生命周期就不得不繼承 Activity 重寫(xiě)這些方法陷揪,想把生命周期的變化分發(fā)給其他組件就很麻煩惋鸥。
于是 Jetpack 引入了 Lifecycle,以讓任何組件都可方便地感知生命周期的變化:
public abstract class Lifecycle {AtomicReference<>();
// 添加生命周期觀察者
public abstract void addObserver(LifecycleObserver observer);
// 移除生命周期觀察者
public abstract void removeObserver(LifecycleObserver observer);
// 獲取當(dāng)前生命周期狀態(tài)
public abstract State getCurrentState();
// 生命周期事件
public enum Event {
ON_CREATE,
ON_START,
ON_RESUME,
ON_PAUSE,
ON_STOP,
ON_DESTROY,
ON_ANY;
}
// 生命周期狀態(tài)
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
}
// 判斷至少到達(dá)了某生命周期狀態(tài)
public boolean isAtLeast(State state) {
return compareTo(state) >= 0;
}
}
Lifecycle 即是生命周期對(duì)應(yīng)的類(lèi)悍缠,提供了添加/移除生命周期觀察者的方法卦绣,在其內(nèi)部還定義了全部生命周期的狀態(tài)及對(duì)應(yīng)事件。
生命周期狀態(tài)是有先后次序的飞蚓,分別對(duì)應(yīng)著由小到大的 int 值滤港。
生命周期擁有者
描述生命周期的對(duì)象已經(jīng)有了,如何獲取這個(gè)對(duì)象需要個(gè)統(tǒng)一的接口(不然直接在 Activity 或者 Fragment 中新增一個(gè)方法嗎趴拧?)溅漾,這個(gè)接口叫LifecycleOwner
:
public interface LifecycleOwner {
Lifecycle getLifecycle();
}
Activity 和 Fragment 都實(shí)現(xiàn)了這個(gè)接口。
只要拿到 LifecycleOwner八堡,就能拿到 Lifecycle樟凄,然后就能注冊(cè)生命周期觀察者。
生命周期 & 數(shù)據(jù)觀察者
生命周期觀察者是一個(gè)接口:
// 生命周期觀察者(空接口兄渺,用于表征一個(gè)類(lèi)型)
public interface LifecycleObserver {}
// 生命周期事件觀察者
public interface LifecycleEventObserver extends LifecycleObserver {
void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
}
要觀察生命周期只要實(shí)現(xiàn)LifecycleEventObserver
接口缝龄,并注冊(cè)給LifeCycle
即可。
除了生命周期觀察者外挂谍,LiveData 場(chǎng)景中還有一個(gè)數(shù)據(jù)觀察者:
// 數(shù)據(jù)觀察者
public interface Observer<T> {
// 數(shù)據(jù)發(fā)生變化時(shí)回調(diào)
void onChanged(T t);
}
數(shù)據(jù)觀察者 會(huì)和 生命周期擁有者 進(jìn)行綁定:
public abstract class LiveData<T> {
// 數(shù)據(jù)觀察者容器
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();
public void observe(
LifecycleOwner owner, // 被綁定的生命周期擁有者
Observer<? super T> observer // 數(shù)據(jù)觀察者
) {
...
// 將數(shù)據(jù)觀察者包裝成 LifecycleBoundObserver
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// 存儲(chǔ)觀察者到 map 結(jié)構(gòu)
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
// 注冊(cè)生命周期觀察者叔壤。
owner.getLifecycle().addObserver(wrapper);
}
}
在觀察 LiveData 時(shí),需傳入兩個(gè)參數(shù)口叙,生命周期擁有者和數(shù)據(jù)觀察者炼绘。這兩個(gè)對(duì)象經(jīng)過(guò)LifecycleBoundObserver
的包裝被綁定在了一起:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
// 持有生命周期擁有者
final LifecycleOwner mOwner;
LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
// 生命周期變化回調(diào)
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
...
activeStateChanged(shouldBeActive())
...
}
}
// 觀察者包裝類(lèi)型
private abstract class ObserverWrapper {
// 持有原始數(shù)據(jù)觀察者
final Observer<? super T> mObserver;
// 注入數(shù)據(jù)觀察者
ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}
// 嘗試將最新值分發(fā)給當(dāng)前數(shù)據(jù)觀察者
void activeStateChanged(boolean newActive) {...}
...
}
LifecycleBoundObserver 實(shí)現(xiàn)了LifecycleEventObserver
接口,并且它被注冊(cè)給了綁定的生命周期對(duì)象妄田,遂具備了生命周期感知能力俺亮。同時(shí)它還持有了數(shù)據(jù)觀察者驮捍,所以它還具備了數(shù)據(jù)觀察能力。
2. LiveData 是如何避免內(nèi)存泄漏的脚曾?
先總結(jié)东且,再分析:
- LiveData 的數(shù)據(jù)觀察者通常是匿名內(nèi)部類(lèi),它持有界面的引用本讥,可能造成內(nèi)存泄漏珊泳。
- LiveData 內(nèi)部會(huì)將數(shù)據(jù)觀察者進(jìn)行封裝,使其具備生命周期感知能力拷沸。當(dāng)生命周期狀態(tài)為 DESTROYED 時(shí)色查,自動(dòng)移除觀察者。
內(nèi)存泄漏是因?yàn)?strong>長(zhǎng)生命周期的對(duì)象持有了短生命周期對(duì)象撞芍,阻礙了其被回收秧了。
觀察 LiveData 數(shù)據(jù)的代碼通常這樣寫(xiě):
class LiveDataActivity : AppCompatActivity() {
private val viewModel by lazy {
ViewModelProviders.of(this@LiveDataActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.livedata.observe(this@LiveDataActivity) {
// 觀察 LiveData 數(shù)據(jù)更新(匿名內(nèi)部類(lèi))
}
}
}
Observer 作為界面的匿名內(nèi)部類(lèi),它會(huì)持有界面的引用序无,同時(shí) Observer 被 LiveData 持有示惊,LivData 被 ViewModel 持有,而 ViewModel 的生命周期比 Activity 長(zhǎng)愉镰。
最終的持有鏈如下:NonConfigurationInstances 持有 ViewModelStore 持有 ViewModel 持有 LiveData 持有 Observer 持有 Activity。
所以得在界面生命周期結(jié)束的時(shí)候移除 Observer钧汹,這件事情丈探,LiveData 幫我們做了。
在 LiveData 內(nèi)部 Observer 會(huì)被包裝成LifecycleBoundObserver
:
class LifecycleBoundObserver extends ObserverWrapper
implements LifecycleEventObserver {
final LifecycleOwner mOwner;
LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// 獲取當(dāng)前生命周期
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
// 若生命周期為 DESTROYED 則移除數(shù)據(jù)觀察者并返回
if (currentState == DESTROYED) {
removeObserver(mObserver);
return
}
...
}
...
}
3. LiveData 是粘性的嗎拔莱?若是碗降,它是怎么做到的?
先總結(jié)塘秦,再分析:
- LiveData 的值被存儲(chǔ)在內(nèi)部的字段中讼渊,直到有更新的值覆蓋,所以值是持久的尊剔。
- 兩種場(chǎng)景下 LiveData 會(huì)將存儲(chǔ)的值分發(fā)給觀察者爪幻。一是值被更新,此時(shí)會(huì)遍歷所有觀察者并分發(fā)之须误。二是新增觀察者或觀察者生命周期發(fā)生變化(至少為 STARTED)挨稿,此時(shí)只會(huì)給單個(gè)觀察者分發(fā)值。
- LiveData 的觀察者會(huì)維護(hù)一個(gè)“值的版本號(hào)”京痢,用于判斷上次分發(fā)的值是否是最新值奶甘。該值的初始值是-1,每次更新 LiveData 值都會(huì)讓版本號(hào)自增祭椰。
- LiveData 并不會(huì)無(wú)條件地將值分發(fā)給觀察者臭家,在分發(fā)之前會(huì)經(jīng)歷三道坎:1. 數(shù)據(jù)觀察者是否活躍疲陕。2. 數(shù)據(jù)觀察者綁定的生命周期組件是否活躍。3. 數(shù)據(jù)觀察者的版本號(hào)是否是最新的钉赁。
- “新觀察者”被“老值”通知的現(xiàn)象叫“粘性”蹄殃。因?yàn)樾掠^察者的版本號(hào)總是小于最新版號(hào),且添加觀察者時(shí)會(huì)觸發(fā)一次老值的分發(fā)橄霉。
如果把 sticky 翻譯成“持久的”窃爷,會(huì)更好理解一些。數(shù)據(jù)是持久的姓蜂,意味著它不是轉(zhuǎn)瞬即逝的按厘,不會(huì)因?yàn)楸幌M(fèi)了就不見(jiàn)了,它會(huì)一直在那钱慢。而且當(dāng)新的觀察者被注冊(cè)時(shí)逮京,持久的數(shù)據(jù)會(huì)將最新的值分發(fā)給它。
“持久的數(shù)據(jù)”是怎么做到的束莫?
顯然是被存起來(lái)了懒棉。以更新 LiveData 數(shù)據(jù)的方法為切入點(diǎn)找找線索:
public abstract class LiveData<T> {
// 存儲(chǔ)數(shù)據(jù)的字段
private volatile Object mData;
// 值版本號(hào)
private int mVersion;
// 更新值
protected void setValue(T value) {
assertMainThread("setValue");
// 版本號(hào)自增
mVersion++;
// 存儲(chǔ)值
mData = value;
// 分發(fā)值
dispatchingValue(null);
}
}
setValue() 是更新 LiveData 值時(shí)必然會(huì)調(diào)用的一個(gè)方法,即使是通過(guò) postValue() 更新值览绿,最終也會(huì)走這個(gè)方法策严。
LiveData 持有一個(gè)版本號(hào)字段,用于標(biāo)識(shí)“值的版本”饿敲,就像軟件版本號(hào)一樣妻导,這個(gè)數(shù)字用于判斷“當(dāng)前值是否是最新的”,若版本號(hào)小于最新版本號(hào)怀各,則表示當(dāng)前值需要更新倔韭。
LiveData 用一個(gè) Object 字段mData
存儲(chǔ)了“值”。所以這個(gè)值會(huì)一直存在瓢对,直到被更新的值覆蓋寿酌。
LiveData 分發(fā)值即是通知數(shù)據(jù)觀察者:
public abstract class LiveData<T> {
// 用鍵值對(duì)方式持有一組數(shù)據(jù)觀察者
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();
void dispatchingValue(ObserverWrapper initiator) {
...
// 指定分發(fā)給單個(gè)數(shù)據(jù)觀察者
if (initiator != null) {
considerNotify(initiator);
initiator = null;
}
// 遍歷所有數(shù)據(jù)觀察者分發(fā)值
else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
}
}
...
}
// 真正地分發(fā)值
private void considerNotify(ObserverWrapper observer) {
// 1. 若觀察者不活躍則不分發(fā)給它
if (!observer.mActive) {
return;
}
// 2. 根據(jù)觀察者綁定的生命周期再次判斷它是否活躍,若不活躍則不分發(fā)給它
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
// 3. 若值已經(jīng)是最新版本硕蛹,則不分發(fā)
if (observer.mLastVersion >= mVersion) {
return;
}
// 更新觀察者的最新版本號(hào)
observer.mLastVersion = mVersion;
// 真正地通知觀察者
observer.mObserver.onChanged((T) mData);
}
}
分發(fā)值有兩種情況:“分發(fā)給單個(gè)觀察者”和“分發(fā)給所有觀察者”醇疼。當(dāng) LiveData 值更新時(shí),需分發(fā)給所有觀察者妓美。
所有的觀察者被存在一個(gè) Map 結(jié)構(gòu)中僵腺,分發(fā)的方式是通過(guò)遍歷 Map 并逐個(gè)調(diào)用considerNotify()
。在這個(gè)方法中需要跨過(guò)三道坎壶栋,才能真正地將值分發(fā)給數(shù)據(jù)觀察者辰如,分別是:
- 數(shù)據(jù)觀察者是否活躍。
- 數(shù)據(jù)觀察者綁定的生命周期組件是否活躍贵试。
- 數(shù)據(jù)觀察者的版本號(hào)是否是最新的琉兜。
跨過(guò)三道坎后凯正,會(huì)將最新的版本號(hào)存儲(chǔ)在觀察者的 mLastVersion 字段中,即版本號(hào)除了保存在LiveData.mVersion
豌蟋,還會(huì)在每個(gè)觀察者中保存一個(gè)副本mLastVersion
廊散,最后才將之前暫存的mData
的值分發(fā)給數(shù)據(jù)觀察者。
每個(gè)數(shù)據(jù)觀察者都和一個(gè)組件的生命周期對(duì)象綁定(見(jiàn)第一節(jié))梧疲,當(dāng)組件生命周期發(fā)生變化時(shí)允睹,會(huì)嘗試將最新值分發(fā)給該數(shù)據(jù)觀察者。
每一個(gè)數(shù)據(jù)觀察者都會(huì)被包裝(見(jiàn)第一節(jié))幌氮,包裝類(lèi)型為ObserverWrapper
:
// 原始數(shù)據(jù)觀察者
public interface Observer<T> {
void onChanged(T t);
}
// 觀察者包裝類(lèi)型
private abstract class ObserverWrapper {
// 持有原始數(shù)據(jù)觀察者
final Observer<? super T> mObserver;
// 當(dāng)前觀察者是否活躍
boolean mActive;
// 當(dāng)前觀察者最新值版本號(hào)缭受,初始值為 -1
int mLastVersion = START_VERSION;
// 注入原始觀察者
ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}
// 當(dāng)數(shù)據(jù)觀察者綁定的組件生命周期變化時(shí),嘗試將最新值分發(fā)給當(dāng)前觀察者
void activeStateChanged(boolean newActive) {
// 若觀察者活躍狀態(tài)未變该互,則不分發(fā)值
if (newActive == mActive) {
return;
}
// 更新活躍狀態(tài)
mActive = newActive;
// 若活躍米者,則將最新值分發(fā)給當(dāng)前觀察者
if (mActive) {
dispatchingValue(this);
}
}
// 是否活躍,供子類(lèi)重寫(xiě)
abstract boolean shouldBeActive();
}
觀察者的包裝類(lèi)型通過(guò)組合的方式持有了一個(gè)原始觀察者宇智,并在此基礎(chǔ)上為其擴(kuò)展了活躍狀態(tài)和版本號(hào)的概念蔓搞。
觀察者包裝類(lèi)型是抽象的,是否活躍由子類(lèi)定義:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
final LifecycleOwner mOwner;
LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
// 當(dāng)與觀察者綁定的生命周期組件至少為STARTED時(shí)随橘,表示觀察者活躍
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged( LifecycleOwner source, Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
// 當(dāng)生命周期狀態(tài)發(fā)生變化喂分,則嘗試將最新值分發(fā)給數(shù)據(jù)觀察者
while (prevState != currentState) {
prevState = currentState;
// 調(diào)用父類(lèi)方法,進(jìn)行分發(fā)
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
}
總結(jié)一下机蔗,LiveData 有兩次機(jī)會(huì)通知觀察者妻顶,與之對(duì)應(yīng)的有兩種分發(fā)值的方式:
- 當(dāng)值更新時(shí),遍歷所有觀察者將最新值分發(fā)給它們蜒车。
- 當(dāng)與觀察者綁定組件的生命周期發(fā)生變化時(shí),將最新的值分發(fā)給指定觀察者幔嗦。
假設(shè)這樣一種場(chǎng)景:LiveData 的值被更新了一次酿愧,隨后它被添加了一個(gè)新的數(shù)據(jù)觀察者,與之綁定組件的生命周期也正好發(fā)生了變化(變化到RESUMED)邀泉,即數(shù)據(jù)更新在添加觀察者之前嬉挡,此時(shí)更新值會(huì)被分發(fā)到新的觀察者嗎?
會(huì)汇恤!首先庞钢,更新值會(huì)被存儲(chǔ)在 mData 字段中。
其次因谎,在添加觀察者時(shí)會(huì)觸發(fā)一次生命周期變化:
// androidx.lifecycle.LifecycleRegistry
public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
...
// 將生命周期事件分發(fā)給新進(jìn)的觀察者
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
...
}
// LifecycleBoundObserver 又被包了一層
static class ObserverWithState {
State mState;
GenericLifecycleObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer, State initialState) {
mLifecycleObserver = Lifecycling.getCallback(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
// 分發(fā)生命周期事件給 LifecycleBoundObserver
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
最后基括,這次嘗試必然能跨過(guò)三道坎,因?yàn)樾陆ㄓ^察者版本號(hào)總是小于 LiveData 的版本號(hào)(-1 < 0财岔,LiveData.mVersion 經(jīng)過(guò)一次值更新后自增為0)风皿。
這種“新觀察者”會(huì)被“老值”通知的現(xiàn)象稱(chēng)為粘性河爹。
4. 粘性的 LiveData 會(huì)造成什么問(wèn)題?怎么解決桐款?
購(gòu)物車(chē)-結(jié)算場(chǎng)景:假設(shè)有一個(gè)購(gòu)物車(chē)界面咸这,點(diǎn)擊結(jié)算后跳轉(zhuǎn)到結(jié)算界面,結(jié)算界面可以回退到購(gòu)物車(chē)界面魔眨。這兩個(gè)界面都是 Fragment媳维。
結(jié)算界面和購(gòu)物車(chē)界面通過(guò)共享ViewModel的方式共享商品列表:
class MyViewModel:ViewModel() {
// 商品列表
val selectsListLiveData = MutableLiveData<List<String>>()
// 更新商品列表
fun setSelectsList(goods:List<String>){
selectsListLiveData.value = goods
}
}
下面是倆 Fragment 界面依托的 Activity
class StickyLiveDataActivity : AppCompatActivity() {
// 用 DSL 構(gòu)建視圖
private val contentView by lazy {
ConstraintLayout {
layout_id = "container"
layout_width = match_parent
layout_height = match_parent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentView)
// 加載購(gòu)物車(chē)界面
supportFragmentManager.beginTransaction()
.add("container".toLayoutId(), TrolleyFragment())
.commit()
}
}
其中使用了 DSL 方式聲明性地構(gòu)建了布局。
購(gòu)物車(chē)頁(yè)面如下:
class TrolleyFragment : Fragment() {
// 獲取與宿主 Activity 綁定的 ViewModel
private val myViewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
// 向購(gòu)物車(chē)添加兩件商品
onClick = {
myViewModel.setSelectsList(listOf("meet","water"))
}
TextView {
layout_id = "balance"
layout_width = wrap_content
layout_height = wrap_content
text = "balance"
gravity = gravity_center
// 跳轉(zhuǎn)結(jié)算頁(yè)面
onClick = {
parentFragmentManager.beginTransaction()
.replace("container".toLayoutId(), BalanceFragment())
.addToBackStack("trolley")
.commit()
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 觀察商品列表變化
myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods ->
// 若商品列表超過(guò)2件商品遏暴,則 toast 提示已滿
goods.takeIf { it.size >= 2 }?.let {
Toast.makeText(context,"購(gòu)物車(chē)已滿",Toast.LENGTH_LONG).show()
}
}
}
}
在 onViewCreated() 中觀察購(gòu)物車(chē)的變化侄刽,如果購(gòu)物車(chē)超過(guò) 2 件商品,則 toast 提示拓挥。
下面是結(jié)算頁(yè)面:
class BalanceFragment:Fragment() {
private val myViewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 結(jié)算界面獲取購(gòu)物列表的方式也是觀察商品 LiveData
myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {...}
}
}
跑一下 demo唠梨,當(dāng)跳轉(zhuǎn)到結(jié)算界面后,點(diǎn)擊返回購(gòu)物車(chē)侥啤,toast 會(huì)再次提示購(gòu)物車(chē)已滿当叭。
因?yàn)樵谔D(zhuǎn)結(jié)算頁(yè)面之前,購(gòu)物車(chē)列表 LiveData 已經(jīng)被更新過(guò)盖灸。當(dāng)購(gòu)物車(chē)頁(yè)面重新展示時(shí)蚁鳖,onViewCreated()
會(huì)再次執(zhí)行,這樣一個(gè)新觀察者被添加赁炎,因?yàn)?LiveData 是粘性的醉箕,所以上一次購(gòu)物車(chē)列表會(huì)分發(fā)給新觀察者,這樣 toast 邏輯再一次被執(zhí)行徙垫。
解決方案一:帶消費(fèi)記錄的值
// 一次性值
open class OneShotValue<out T>(private val value: T) {
// 值是否被消費(fèi)
private var handled = false
// 獲取值讥裤,如果值未被處理則返回,否則返回空
fun getValue(): T? {
return if (handled) {
null
} else {
handled = true
value
}
}
// 獲取上次被處理的值
fun peekValue(): T = value
}
在值的外面套一層姻报,新增一個(gè)標(biāo)記位標(biāo)識(shí)是否被處理過(guò)己英。
用這個(gè)方法重構(gòu)下 ViewModel:
class MyViewModel:ViewModel() {
// 已選物品列表
val selectsListLiveData = MutableLiveData<OneShotValue<List<String>>>()
// 更新已選物品
fun setSelectsList(goods:List<String>){
selectsListLiveData.value = OneShotValue(goods)
}
}
觀察購(gòu)物車(chē)的邏輯也要做修改:
class TrolleyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods ->
goods.getValue()?.takeIf { it.size >= 2 }?.let {
Toast.makeText(context,"購(gòu)物車(chē)滿了",Toast.LENGTH_LONG).show()
}
}
}
}
重復(fù)彈 toast 的問(wèn)題是解決了,但引出了一個(gè)新的問(wèn)題:當(dāng)購(gòu)物車(chē)滿彈出 toast 時(shí)吴旋,購(gòu)物車(chē)列表已經(jīng)被消費(fèi)掉了损肛,導(dǎo)致結(jié)算界面就無(wú)法再消費(fèi)了。
這時(shí)候只能用peekValue()
來(lái)獲取已經(jīng)被消費(fèi)的值:
class BalanceFragment:Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {
val list = it.peekValue()// 使用 peekValue() 獲取購(gòu)物車(chē)列表
}
}
}
bug 全解完了荣瑟。但不覺(jué)得這樣處理有一些擰巴嗎治拿?
用“一次性值”封裝 LiveData 的值,以去除其粘性笆焰。使用該方案得甄別出哪些觀察者需要粘性值劫谅,哪些觀察者需要非粘性事件。當(dāng)觀察者很多的時(shí)候,就很難招架了同波。若把需要粘性處理和非粘性處理的邏輯寫(xiě)在一個(gè)觀察者中鳄梅,就 GG,還得新建觀察者將它們分開(kāi)未檩。
解決方案二:帶有最新版本號(hào)的觀察者
通知觀察者前需要跨過(guò)三道坎(詳見(jiàn)第三節(jié))戴尸,其中有一道坎是版本號(hào)的比對(duì)。若新建的觀察者版本號(hào)小于最新版本號(hào)冤狡,則表示觀察者落后了孙蒙,需要將最新值分發(fā)給它。
LiveData 源碼中悲雳,新建觀察者的版本號(hào)總是 -1挎峦。
// 觀察者包裝類(lèi)型
private abstract class ObserverWrapper {
// 當(dāng)前觀察者最新值版本號(hào),初始值為 -1
int mLastVersion = START_VERSION;
...
}
若能夠讓新建觀察者的版本號(hào)被最新版本號(hào)賦值合瓢,那版本號(hào)對(duì)比的那道坎就過(guò)不了坦胶,新值就無(wú)法分發(fā)到新建觀察者。
所以得通過(guò)反射修改 mLastVersion 字段晴楔。
該方案除了傾入性強(qiáng)之外顿苇,把 LiveData 粘性徹底破壞了。但有的時(shí)候税弃,我們還是想利用粘性的纪岁。。则果。
解決方案三:SingleLiveEvent
這是谷歌給出的一個(gè)解決方案幔翰,源碼可以點(diǎn)擊這里
public class SingleLiveEvent<T> extends MutableLiveData<T> {
// 標(biāo)志位,用于表達(dá)值是否被消費(fèi)
private final AtomicBoolean mPending = new AtomicBoolean(false);
public void observe(LifecycleOwner owner, final Observer<T> observer) {
// 中間觀察者
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
// 只有當(dāng)值未被消費(fèi)過(guò)時(shí)西壮,才通知下游觀察者
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
public void setValue(@Nullable T t) {
// 當(dāng)值更新時(shí)遗增,置標(biāo)志位為 true
mPending.set(true);
super.setValue(t);
}
public void call() {
setValue(null);
}
}
專(zhuān)門(mén)設(shè)立一個(gè) LiveData,它不具備粘性款青。它通過(guò)新增的“中間觀察者”贡定,攔截上游數(shù)據(jù)變化,然后在轉(zhuǎn)發(fā)給下游可都。攔截之后通常可以做一點(diǎn)手腳蚓耽,比如增加一個(gè)標(biāo)記位mPending
是否消費(fèi)過(guò)的判斷渠牲,若消費(fèi)過(guò)則不轉(zhuǎn)發(fā)給下游。
在數(shù)據(jù)驅(qū)動(dòng)的 App 界面下步悠,存在兩種值:1. 非暫態(tài)數(shù)據(jù) 2. 暫態(tài)數(shù)據(jù)
demo 中用于提示“購(gòu)物車(chē)已滿”的數(shù)據(jù)就是“暫態(tài)數(shù)據(jù)”签杈,這種數(shù)據(jù)是一次性的,轉(zhuǎn)瞬即逝的,可以消費(fèi)一次就扔掉答姥。
demo 中購(gòu)物車(chē)中的商品列表就是“非暫態(tài)數(shù)據(jù)”铣除,它的生命周期要比暫態(tài)數(shù)據(jù)長(zhǎng)一點(diǎn),在購(gòu)物車(chē)界面和結(jié)算界面存活的期間都應(yīng)該能被重復(fù)消費(fèi)鹦付。
SingleLiveEvent 的設(shè)計(jì)正是基于對(duì)數(shù)據(jù)的這種分類(lèi)方法尚粘,即暫態(tài)數(shù)據(jù)使用 SingleLiveEvent,非暫態(tài)數(shù)據(jù)使用常規(guī)的 LiveData敲长。
這樣塵歸塵土歸土的解決方案是符合現(xiàn)實(shí)情況的郎嫁。將 demo 改造一下:
class MyViewModel : ViewModel() {
// 非暫態(tài)購(gòu)物車(chē)列表 LiveData
val selectsListLiveData = MutableLiveData<List<String>>()
// 暫態(tài)購(gòu)物車(chē)列表 LiveData
val singleListLiveData = SingleLiveEvent<List<String>>()
// 更新購(gòu)物車(chē)列表,同時(shí)更新暫態(tài)和非暫態(tài)
fun setSelectsList(goods: List<String>) {
selectsListLiveData.value = goods
singleListLiveData.value = goods
}
}
在購(gòu)物車(chē)界面做相應(yīng)的改動(dòng):
class TrolleyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 只觀察非暫態(tài)購(gòu)物車(chē)列表
myViewModel.singleListLiveData.observe(viewLifecycleOwner) { goods ->
goods.takeIf { it.size >= 2 }?.let {
Toast.makeText(context,"full",Toast.LENGTH_LONG).show()
}
}
}
}
但該方案有局限性祈噪,若為 SingleLiveEvent 添加多個(gè)觀察者泽铛,則當(dāng)?shù)谝粋€(gè)觀察者消費(fèi)了數(shù)據(jù)后,其他觀察者就沒(méi)機(jī)會(huì)消費(fèi)了辑鲤。因?yàn)?code>mPending是所有觀察者共享的盔腔。
解決方案也很簡(jiǎn)單,為每個(gè)中間觀察者都持有是否消費(fèi)過(guò)數(shù)據(jù)的標(biāo)記位:
open class LiveEvent<T> : MediatorLiveData<T>() {
// 持有多個(gè)中間觀察者
private val observers = ArraySet<ObserverWrapper<in T>>()
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
observers.find { it.observer === observer }?.let { _ ->
return
}
// 構(gòu)建中間觀察者
val wrapper = ObserverWrapper(observer)
observers.add(wrapper)
super.observe(owner, wrapper)
}
@MainThread
override fun observeForever(observer: Observer<in T>) {
observers.find { it.observer === observer }?.let { _ ->
return
}
val wrapper = ObserverWrapper(observer)
observers.add(wrapper)
super.observeForever(wrapper)
}
@MainThread
override fun removeObserver(observer: Observer<in T>) {
if (observer is ObserverWrapper && observers.remove(observer)) {
super.removeObserver(observer)
return
}
val iterator = observers.iterator()
while (iterator.hasNext()) {
val wrapper = iterator.next()
if (wrapper.observer == observer) {
iterator.remove()
super.removeObserver(wrapper)
break
}
}
}
@MainThread
override fun setValue(t: T?) {
// 通知所有中間觀察者月褥,有新數(shù)據(jù)
observers.forEach { it.newValue() }
super.setValue(t)
}
// 中間觀察者
private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {
// 標(biāo)記當(dāng)前觀察者是否消費(fèi)了數(shù)據(jù)
private var pending = false
override fun onChanged(t: T?) {
// 保證只向下游觀察者分發(fā)一次數(shù)據(jù)
if (pending) {
pending = false
observer.onChanged(t)
}
}
fun newValue() {
pending = true
}
}
}
解決方案四:Kotlin Flow
限于篇幅原因及主題的原因(主題是 LiveData)弛随,直接給出代碼(當(dāng)前做法有問(wèn)題),會(huì)再起一篇做關(guān)于 Flow 替代 LiveData 的剖析吓坚。
class MyViewModel : ViewModel() {
// 商品列表流
val selectsListFlow = MutableSharedFlow<List<String>>()
// 更新商品列表
fun setSelectsList(goods: List<String>) {
viewModelScope.launch {
selectsListFlow.emit(goods)
}
}
}
購(gòu)物車(chē)代碼如下:
class TrolleyFragment : Fragment() {
private val myViewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1.先產(chǎn)生數(shù)據(jù)
myViewModel.setSelectsList(listOf("food_meet", "food_water", "book_1"))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 2.再訂閱商品列表流
lifecycleScope.launch {
myViewModel.selectsListFlow.collect { goods ->
goods.takeIf { it.size >= 2 }?.let {
Log.v("ttaylor", "購(gòu)物車(chē)滿")
}
}
}
}
}
數(shù)據(jù)生產(chǎn)在訂閱之前撵幽,訂閱后并不會(huì)打印 log。
如果這樣修改 SharedFlow 的構(gòu)建參數(shù)礁击,則可以讓其變得粘性:
class MyViewModel : ViewModel() {
val selectsListFlow = MutableSharedFlow<List<String>>(replay = 1)
}
replay = 1 表示會(huì)將最新的那個(gè)數(shù)據(jù)通知給新進(jìn)的訂閱者盐杂。
這只是解決了粘性/非粘性之間方便切換的問(wèn)題,并未解決仍需多個(gè)流的問(wèn)題哆窿。帶下一篇繼續(xù)深入分析链烈。
5. 什么情況下 LiveData 會(huì)丟失數(shù)據(jù)?
先總結(jié)挚躯,再分析:
在高頻數(shù)據(jù)更新的場(chǎng)景下使用 LiveData.postValue() 時(shí)强衡,會(huì)造成數(shù)據(jù)丟失。因?yàn)椤霸O(shè)值”和“分發(fā)值”是分開(kāi)執(zhí)行的码荔,之間存在延遲漩勤。值先被緩存在變量中,再向主線程拋一個(gè)分發(fā)值的任務(wù)缩搅。若在這延遲之間再一次調(diào)用 postValue()越败,則變量中緩存的值被更新,之前的值在沒(méi)有被分發(fā)之前就被擦除了硼瓣。
下面是 LiveData.postValue() 的源碼:
public abstract class LiveData<T> {
// 暫存值字段
volatile Object mPendingData = NOT_SET;
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
// 同步地獲取暫存值
newValue = mPendingData;
mPendingData = NOT_SET;
}
// 分發(fā)值
setValue((T) newValue);
}
};
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
// 暫存值
mPendingData = value;
}
...
// 向主線程拋 runnable
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
}
6. 在 Fragment 中使用 LiveData 需注意些什么究飞?
先總結(jié),再分析:
在 Fragment 中觀察 LiveData 時(shí)使用
viewLifecycleOwner
而不是this
。因?yàn)?Fragment 和 其中的 View 生命周期不完全一致亿傅。LiveData 內(nèi)部判定生命周期為 DESTROYED 時(shí)媒峡,才會(huì)移除數(shù)據(jù)觀察者。存在一種情況葵擎,當(dāng) Fragment 之間切換時(shí)谅阿,被替換的 Fragment 不執(zhí)行 onDestroy(),當(dāng)它再次展示時(shí)會(huì)再次訂閱 LiveData坪蚁,于是乎就多出一個(gè)訂閱者奔穿。
還是購(gòu)物-結(jié)算的場(chǎng)景:購(gòu)物車(chē)和結(jié)算頁(yè)都是兩個(gè) Fragment,將商品列表存在共享 ViewMode 的 LiveData 中敏晤,購(gòu)物車(chē)及結(jié)算頁(yè)都觀察它贱田,結(jié)算頁(yè)除了用它列出購(gòu)物清單之外,還可以通過(guò)更改商品數(shù)量來(lái)修改 LiveData嘴脾。當(dāng)從結(jié)算頁(yè)返回購(gòu)物車(chē)頁(yè)面時(shí)男摧,購(gòu)物車(chē)界面得刷新商品數(shù)量。
上述場(chǎng)景译打,若購(gòu)物車(chē)頁(yè)面觀察 LiveData 時(shí)使用this
會(huì)發(fā)生什么耗拓?
// 購(gòu)物車(chē)界面
class TrolleyFragment : Fragment() {
private val myViewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
onClick = {
parentFragmentManager.beginTransaction()
.replace("container".toLayoutId(), BalanceFragment())
.addToBackStack("trolley")// 將購(gòu)物車(chē)頁(yè)面添加到 back stack
.commit()
}
}
}
// 不得不增加這個(gè)注釋?zhuān)驗(yàn)?this 會(huì)飄紅
@SuppressLint("FragmentLiveDataObserve")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 將 this 作為生命周期擁有者傳給 LiveData
myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> {
override fun onChanged(t: List<String>?) {
Log.v("ttaylor", "商品數(shù)量發(fā)生變化")
}
})
}
}
這樣寫(xiě)this
會(huì)飄紅,AndroidStudio 不推薦使用它作為生命周期擁有者奏司,不得不加 @SuppressLint("FragmentLiveDataObserve")
結(jié)算界面修改商品數(shù)量的代碼如下:
// 結(jié)算界面
class BalanceFragment:Fragment() {
private val myViewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 模擬結(jié)算界面修改商品數(shù)量
myViewModel.selectsListLiveData.value = listOf("數(shù)量+1")
}
}
當(dāng)從結(jié)算頁(yè)返回購(gòu)物車(chē)時(shí)乔询,“商品數(shù)量發(fā)生變化” 會(huì)打印兩次,如果再進(jìn)一次結(jié)算頁(yè)并返回購(gòu)物車(chē)韵洋,就會(huì)打印三次竿刁。
若換成viewLifecycleOwner
就不會(huì)有這個(gè)煩惱。因?yàn)槭褂?replace 更換 Fragment 時(shí)搪缨,Fragment.onDestroyView()
會(huì)執(zhí)行食拜,即 Fragment 對(duì)應(yīng) View 的生命周期狀態(tài)會(huì)變?yōu)?DESTROYED。
LiveData 內(nèi)部會(huì)將生命周期為 DESTROYED 的數(shù)據(jù)觀察者移除(詳見(jiàn)第二節(jié))副编。當(dāng)再次返回購(gòu)物車(chē)時(shí)负甸,onViewCreated() 重新執(zhí)行,LiveData 會(huì)添加一個(gè)新的觀察者痹届。一刪一增呻待,整個(gè)過(guò)程 LiveData 始終只有一個(gè)觀察者。又因?yàn)?LiveData 是粘性的队腐,即使修改商品數(shù)量發(fā)生在觀察之前蚕捉,最新的商品數(shù)量還是會(huì)被分發(fā)到新觀察者。(詳見(jiàn)第三節(jié))
但當(dāng)使用 replace 更換 Fragment 并將其壓入 back stack 時(shí)香到,Fragment.onDestroy()
不會(huì)調(diào)用(因?yàn)楸粔簵A耍⑽幢讳N(xiāo)毀)。這導(dǎo)致 Fragment 的生命周期狀態(tài)不會(huì)變?yōu)?DESTROYED悠就,所以 LiveData 的觀察者不會(huì)被自動(dòng)移除千绪。當(dāng)重新返回購(gòu)物車(chē)時(shí),又添加了新的觀察者梗脾。如果不停地在購(gòu)物車(chē)和結(jié)算頁(yè)間橫跳荸型,則觀察者數(shù)據(jù)會(huì)不停地增加。
在寫(xiě) demo 的時(shí)候遇到一個(gè)坑:
// 購(gòu)物車(chē)界面
class TrolleyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 故意使用 object 語(yǔ)法
myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> {
override fun onChanged(t: List<String>?) {
Log.v("ttaylor", "商品數(shù)量發(fā)生變化")
}
})
}
}
在構(gòu)建 Observer 實(shí)例的時(shí)候炸茧,我特意使用了 Kotlin 的 object 語(yǔ)法瑞妇,其實(shí)明明可以使用 lambda 將其寫(xiě)得更簡(jiǎn)潔:
class TrolleyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectsListLiveData.observe(this) {
Log.v("ttaylor", "商品數(shù)量發(fā)生變化")
}
}
}
如果這樣寫(xiě),那 bug 就無(wú)法復(fù)現(xiàn)了梭冠。辕狰。。控漠。
因?yàn)?java 編譯器會(huì)擅作主張地將同樣的 lambda 優(yōu)化成靜態(tài)的蔓倍,可以提升性能,不用每次都重新構(gòu)建內(nèi)部類(lèi)盐捷。但不巧的是 LiveData 在添加觀察者時(shí)會(huì)校驗(yàn)是否已存在偶翅,若存在則直接返回:
// `androidx.lifecycle.LiveData
public void observe( LifecycleOwner owner, Observer<? super T> observer) {
...
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// 調(diào)用 map 結(jié)構(gòu)的寫(xiě)操作,若 key 已存在碉渡,則返回對(duì)應(yīng) value
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
// 已存在則直接返回
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
這樣的話聚谁,F(xiàn)ragment 界面之間反復(fù)橫跳也不會(huì)新增觀察者。
7. 如何變換 LiveData 數(shù)據(jù)及注意事項(xiàng)滞诺?
先總結(jié)形导,再分析:
androidx.lifecycle.Transformations
類(lèi)提供了三個(gè)變換 LiveData 數(shù)據(jù)的方法,最常用的是Transformations.map()
铭段,它使用MediatorLiveData
作為數(shù)據(jù)的中間消費(fèi)者骤宣,并將變換后的數(shù)據(jù)傳遞給最終消費(fèi)者。需要注意的是序愚,數(shù)據(jù)變化操作都發(fā)生在主線程憔披,主線程有可能被耗時(shí)操作阻塞。解決方案是將 LiveData 數(shù)據(jù)變換操作異步化爸吮,比如通過(guò)CoroutineLiveData
芬膝。
還是購(gòu)物-結(jié)算的場(chǎng)景:購(gòu)物車(chē)和結(jié)算頁(yè)都是兩個(gè) Fragment,將商品列表存在 LiveData 中形娇,購(gòu)物車(chē)及結(jié)算頁(yè)都觀察它锰霜。結(jié)算界面對(duì)打折商品有一個(gè)特殊的 UI 展示。
此時(shí)就可以將商品列表 LiveData 進(jìn)行一次變換(過(guò)濾)得到一個(gè)新的打折商品列表:
class MyViewModel : ViewModel() {
// 商品列表
val selectsListLiveData = MutableLiveData<List<String>>()
// 打折商品列表
val foodListLiveData = Transformations.map(selectsListLiveData) { list ->
list.filter { it.startsWith("discount") }
}
}
每當(dāng)商品列表發(fā)生變化桐早,打折商品列表都會(huì)收到通知癣缅,并過(guò)濾出新的打折商品厨剪。打折商品列表是一個(gè)新的 LiveData,可以單獨(dú)被觀察友存。
其中的過(guò)濾列表操作發(fā)生在主線程祷膳,如果業(yè)務(wù)略復(fù)雜,數(shù)據(jù)變換操作耗時(shí)的話屡立,可能阻塞主線程直晨。
如何將 LiveData 變換數(shù)據(jù)異步化?
LiveData 的 Kotlin 擴(kuò)展包里提供了一個(gè)將 LiveData 和協(xié)程結(jié)合的產(chǎn)物:
class MyViewModel : ViewModel() {
// 商品列表
val selectsListLiveData = MutableLiveData<List<String>>()
// 用異步方式獲取打折商品列表
val asyncLiveData = selectsListLiveData.switchMap { list ->
// 將源 LiveData 中的值轉(zhuǎn)換成一個(gè) CoroutineLiveData
liveData(Dispatchers.Default) {
emit( list.filter { it.startsWith("discount") } )
}
}
}
其中的switchMap()
是 LiveData 的擴(kuò)展方法膨俐,它是對(duì)Transformations.switchMap()
的封裝勇皇,用于方便鏈?zhǔn)秸{(diào)用:
public inline fun <X, Y> LiveData<X>.switchMap(
crossinline transform: (X) -> LiveData<Y>
): LiveData<Y> = Transformations.switchMap(this) { transform(it) }
switchMap() 內(nèi)部將源 LiveData 的每個(gè)值都轉(zhuǎn)換成一個(gè)新的 LiveData 并訂閱。
liveData
是一個(gè)頂層方法焚刺,用于構(gòu)建CoroutineLiveData
:
public fun <T> liveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
CoroutineLiveData 將更新 LiveData 值的操作封裝到一個(gè)掛起方法中敛摘,可以通過(guò)協(xié)程上下文指定執(zhí)行的線程。
使用 CoroutineLiveData 需要添加如下依賴(lài):
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"