LiveData 和 ViewModel 是 Google 官方的 MVVM 架構(gòu)的一個組成部分桐筏。巧了,昨天分析了一個問題是 ViewModel 的生命周期導(dǎo)致的逛拱。今天又遇到了一個問題是 LiveData 通知導(dǎo)致的柏锄。而 ViewModel 的生命周期和 LiveData 的通知機制是它們的主要責(zé)任。所以眶痰,就這個機會我們也來分析一下 LiveData 通知的實現(xiàn)過程。
- 關(guān)于 ViewModel 的生命周期:《淺談 LiveData 的通知機制》;
- 關(guān)于 MVVM 設(shè)計模式的基本應(yīng)用梯啤,你可以參考這篇文章:《Android 架構(gòu)設(shè)計:MVC、MVP存哲、MVVM和組件化》.
1因宇、一個 LiveData 的問題
今天所遇到的問題是這樣的,
有兩個頁面 A 和 B祟偷,A 是一個 Fragment 察滑,是一個列表的展示頁;B 是其他的頁面修肠。首先贺辰,A 會更新頁面,并且為了防止連續(xù)更新,再每次更新之前需要檢查一個布爾值饲化,只有為 false 的時候才允許從網(wǎng)絡(luò)加載數(shù)據(jù)莽鸭。每次加載數(shù)據(jù)之前會將該布爾值置為 true,拿到了結(jié)果之后置為 false. 這里拿到的結(jié)果是借助 LiveData 來通知給頁面進行更新的吃靠。
現(xiàn)在硫眨,A 打開了 B,B 中對列表中的數(shù)據(jù)進行了更新巢块,然后發(fā)了一條類似于廣播的消息礁阁。此時,A 接收了消息并進行數(shù)據(jù)加載族奢。過了一段時間姥闭,B 準(zhǔn)備退出,再退出的時候又對列表中的項目進行了更新越走,所以此時又發(fā)出了一條消息棚品。
B 關(guān)閉了,我們回到了 A 頁面弥姻。但是南片,此時,我們發(fā)現(xiàn) A 頁面中的數(shù)據(jù)只包含了第一次的數(shù)據(jù)更新庭敦,第二次的數(shù)據(jù)更新沒有體現(xiàn)在列表中疼进。
用代碼來描述的話大致是下面這樣,
// 類 A
public class A extends Fragment {
private boolean loading = false;
private MyViewModel vm;
// ......
/**
* Register load observer.
*/
public void registerObservers() {
vm.getData().observe(this, resources -> {
loading = false;
// ... show in list
})
}
/**
* Load data from server.
*/
public void loadData() {
if (loading) return;
loading = true;
vm.load();
}
/**
* On receive message.
*/
public void onReceive() {
loadData();
}
}
public class B extends Activity {
public void doBusiness1() {
sendMessage(MSG); // Send message when on foreground.
}
@Override
public void onBackpressed() {
// ....
sendMessage(MSG); // Send message when back
}
}
public class MyViewModel extends ViewModel {
private MutableLiveData<Resoucres<Object>> data;
public MutableLiveData<Resoucres<Object>> getData() {
if (data == null) {
data = new MutableLiveData<>();
}
return data;
}
public void load() {
Object result = AsyncGetData.getData(); // Get data
if (data != null) {
data.setValue(Resouces.success(result));
}
}
}
A 打開了 B 之后秧廉,A 處于后臺伞广,B 處于前臺。此時疼电,B 調(diào)用 doBusiness1()
發(fā)送了一條消息 MSG嚼锄,A 中在 onReceive()
中收到消息,并調(diào)用 loadData()
加載數(shù)據(jù)蔽豺。然后区丑,B 處理完了業(yè)務(wù),準(zhǔn)備退出的時候發(fā)現(xiàn)其他數(shù)據(jù)發(fā)生了變化修陡,所以又發(fā)了一條消息沧侥,然后 onReceive()
中收到消息,并調(diào)用 loadData()
. 但此時發(fā)現(xiàn) loading 為 true. 所以魄鸦,我們后來對數(shù)據(jù)的修改沒有體現(xiàn)到列表上面宴杀。
2、問題的原因
如果用上面的示例代碼作為例子拾因,那么出現(xiàn)問題的原因就是當(dāng) A 處于后臺的時候旺罢。雖然調(diào)用了 loadData()
并且從網(wǎng)絡(luò)中拿到了數(shù)據(jù)旷余,但是調(diào)用 data.setValue()
方法的時候無法通知到 A 中。所以扁达,loading = false
這一行無法被調(diào)用到正卧。第二次發(fā)出通知的時候,一樣調(diào)用到了 loadData()
罩驻,但是因為此時 loading
為 true穗酥,所以并沒有執(zhí)行加載數(shù)據(jù)的操作。而當(dāng)從 B 中完全回到 A 的時候惠遏,第一次加載的數(shù)據(jù)被 A 接收到砾跃。所以,列表中的數(shù)據(jù)是第一次加載時的數(shù)據(jù)节吮,第二次加載事件丟失了抽高。
解決這個問題的方法當(dāng)然比較簡單,可以當(dāng)接收到事件的時候使用布爾變量監(jiān)聽透绩,然后回到頁面的時候發(fā)現(xiàn)數(shù)據(jù)發(fā)生變化再執(zhí)行數(shù)據(jù)加載:
// 類 A
public class A extends Fragment {
private boolean dataChanged;
/**
* On receive message.
*/
public void onReceive() {
dataChanged = true;
}
@Override
public void onResume() {
// ...
if (dataChanged) {
loadData();
}
}
}
對于上面的問題翘骂,當(dāng)我們調(diào)用了 setValue()
之后將調(diào)用到 LiveData 類的 setValue()
方法,
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
這里表明該方法必須在主線程中被調(diào)用帚豪,最終事件的分發(fā)將會交給 dispatchingValue()
方法來執(zhí)行:
private 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<T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
// 發(fā)送事件
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
然后碳竟,會調(diào)用 considerNotify()
方法來最終將事件傳遞出去,
private void considerNotify(ObserverWrapper observer) {
// 這里會因為當(dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出方法
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
這里會因為當(dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出 considerNotify()
方法狸臣,從而消息無法被傳遞出去莹桅。
3、LiveData 的通知機制
LiveData 的通知機制并不復(fù)雜烛亦,它的類主要包含在 livedata-core
包下面诈泼,總共也就 3 個類。LiveData 是一個抽象類煤禽,它有一個默認(rèn)的實現(xiàn)就是 MutableLiveData.
LiveData 主要依靠內(nèi)部的變量 mObservers
來緩存訂閱的對象和訂閱信息铐达。其定義如下,使用了一個哈希表進行緩存和映射檬果,
private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
每當(dāng)我們調(diào)用一次 observe()
方法的時候就會有一個映射關(guān)系被加入到哈希表中瓮孙,
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// 持有者當(dāng)前處于被銷毀狀態(tài),因此可以忽略此次觀察
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);
}
從上面的代碼我們可以看出选脊,添加到映射關(guān)系中的類會先被包裝成 LifecycleBoundObserver
對象杭抠。然后使用該對象對 owner 的生命周期進行監(jiān)聽。
這的 LifecycleBoundObserver
和 ObserverWrapper
兩個類的定義如下知牌,
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
@NonNull final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
super(observer);
mOwner = owner;
}
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
private abstract class ObserverWrapper {
final Observer<T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
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);
}
}
}
上面的類中我們先來關(guān)注 LifecycleBoundObserver
中的 onStateChanged()
方法。該方法繼承自 LifecycleObserver
. 這里的 Lifecycle.Event
是一個枚舉類型斤程,定義了一些與生命周期相關(guān)的枚舉值角寸。所以菩混,當(dāng) Activity 或者 Fragment 的生命周期發(fā)生變化的時候會回調(diào)這個方法。從上面我們也可以看出扁藕,該方法內(nèi)部又調(diào)用了基類的 activeStateChanged()
方法沮峡,該方法主要用來更新當(dāng)前的 Observer 是否處于 Active 的狀態(tài)。我們上面無法通知也是因為在這個方法中 mActive 被置為 false 造成的亿柑。
繼續(xù)看 activeStateChanged()
方法邢疙,我們可以看出在最后的幾行中,它調(diào)用了 dispatchingValue(this)
方法望薄。所以疟游,當(dāng) Fragment 從處于后臺切換到前臺之后,會將當(dāng)前緩存的值通知給觀察者痕支。
那么值是如何緩存的颁虐,以及緩存了多少值呢?回到之前的 setValue()
和 dispatchingValue()
方法中卧须,我們發(fā)現(xiàn)值是以一個單獨的變量進行緩存的另绩,
private volatile Object mData = NOT_SET;
因此,在我們的示例中花嘶,當(dāng)頁面從后臺切換到前臺的時候笋籽,只能將最后一次緩存的結(jié)果通知給觀察者就真相大白了。
總結(jié)
從上面的分析中椭员,我們對 LiveData 總結(jié)如下车海,
- 當(dāng)調(diào)用
observe()
方法的時候,我們的觀察者將會和 LifecycleOwner (Fragment 或者 Activity) 一起被包裝到一個類中拆撼,并使用哈希表建立映射關(guān)系容劳。同時,還會對 Fragment 或者 Activity 的生命周期方法進行監(jiān)聽闸度,依次來達到監(jiān)聽觀察者是否處于 active 狀態(tài)的目的竭贩。 - 當(dāng) Fragment 或者 Activity 處于后臺的時候,其內(nèi)部的觀察者將處于非 active 狀態(tài)莺禁,此時使用
setValue()
設(shè)置的值會緩存到 LiveData 中留量。但是這種緩存只能緩存一個值,新的值會替換舊的值哟冬。因此楼熄,當(dāng)頁面從后臺恢復(fù)到前臺的時候只有最后設(shè)置的一個值會被傳遞給觀察者。 - 2 中的當(dāng) Fragment 或者 Activity 從后臺恢復(fù)的時候進行通知也是通過監(jiān)聽其生命周期方法實現(xiàn)的浩峡。
- 調(diào)用了
observe()
之后可岂,F(xiàn)ragment 或者 Activity 被緩存了起來,不會造成內(nèi)存泄漏嗎翰灾?答案是不會的缕粹。因為 LiveData 可以對其生命周期進行監(jiān)聽稚茅,當(dāng)其處于銷毀狀態(tài)的時候,該映射關(guān)系將被從緩存中移除平斩。
以上亚享。
(如有疑問,可以在評論中交流)
如果你喜歡這篇文章绘面,請點贊欺税!你也可以在以下平臺關(guān)注我:
- 博客:https://shouheng88.github.io/
- 掘金:https://juejin.im/user/585555e11b69e6006c907a2a
- Github:https://github.com/Shouheng88
- CSDN:https://blog.csdn.net/github_35186068
- 微博:https://weibo.com/u/5401152113
所有的文章維護在:Github, Android-notes