LiveData 和 ViewModel 是 Google 官方的 MVVM 架構(gòu)的一個(gè)組成部分。巧了,昨天分析了一個(gè)問題是 ViewModel 的生命周期導(dǎo)致的。今天又遇到了一個(gè)問題是 LiveData 通知導(dǎo)致的。而 ViewModel 的生命周期和 LiveData 的通知機(jī)制是它們的主要責(zé)任。所以,就這個(gè)機(jī)會(huì)我們也來分析一下 LiveData 通知的實(shí)現(xiàn)過程。
- 關(guān)于 ViewModel 的生命周期:《淺談 LiveData 的通知機(jī)制》;
- 關(guān)于 MVVM 設(shè)計(jì)模式的基本應(yīng)用,你可以參考這篇文章:《Android 架構(gòu)設(shè)計(jì):MVC、MVP、MVVM和組件化》.
1、一個(gè) LiveData 的問題
今天所遇到的問題是這樣的,
有兩個(gè)頁面 A 和 B,A 是一個(gè) Fragment ,是一個(gè)列表的展示頁;B 是其他的頁面。首先,A 會(huì)更新頁面,并且為了防止連續(xù)更新,再每次更新之前需要檢查一個(gè)布爾值,只有為 false 的時(shí)候才允許從網(wǎng)絡(luò)加載數(shù)據(jù)。每次加載數(shù)據(jù)之前會(huì)將該布爾值置為 true,拿到了結(jié)果之后置為 false. 這里拿到的結(jié)果是借助 LiveData 來通知給頁面進(jìn)行更新的。
現(xiàn)在,A 打開了 B,B 中對(duì)列表中的數(shù)據(jù)進(jìn)行了更新,然后發(fā)了一條類似于廣播的消息。此時(shí),A 接收了消息并進(jìn)行數(shù)據(jù)加載。過了一段時(shí)間,B 準(zhǔn)備退出,再退出的時(shí)候又對(duì)列表中的項(xiàng)目進(jìn)行了更新,所以此時(shí)又發(fā)出了一條消息。
B 關(guān)閉了,我們回到了 A 頁面。但是,此時(shí),我們發(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 處于后臺(tái),B 處于前臺(tái)。此時(shí),B 調(diào)用 doBusiness1()
發(fā)送了一條消息 MSG,A 中在 onReceive()
中收到消息,并調(diào)用 loadData()
加載數(shù)據(jù)。然后,B 處理完了業(yè)務(wù),準(zhǔn)備退出的時(shí)候發(fā)現(xiàn)其他數(shù)據(jù)發(fā)生了變化,所以又發(fā)了一條消息,然后 onReceive()
中收到消息,并調(diào)用 loadData()
. 但此時(shí)發(fā)現(xiàn) loading 為 true. 所以,我們后來對(duì)數(shù)據(jù)的修改沒有體現(xiàn)到列表上面。
2、問題的原因
如果用上面的示例代碼作為例子,那么出現(xiàn)問題的原因就是當(dāng) A 處于后臺(tái)的時(shí)候。雖然調(diào)用了 loadData()
并且從網(wǎng)絡(luò)中拿到了數(shù)據(jù),但是調(diào)用 data.setValue()
方法的時(shí)候無法通知到 A 中。所以,loading = false
這一行無法被調(diào)用到。第二次發(fā)出通知的時(shí)候,一樣調(diào)用到了 loadData()
,但是因?yàn)榇藭r(shí) loading
為 true,所以并沒有執(zhí)行加載數(shù)據(jù)的操作。而當(dāng)從 B 中完全回到 A 的時(shí)候,第一次加載的數(shù)據(jù)被 A 接收到。所以,列表中的數(shù)據(jù)是第一次加載時(shí)的數(shù)據(jù),第二次加載事件丟失了。
解決這個(gè)問題的方法當(dāng)然比較簡單,可以當(dāng)接收到事件的時(shí)候使用布爾變量監(jiān)聽,然后回到頁面的時(shí)候發(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();
}
}
}
對(duì)于上面的問題,當(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ā)將會(huì)交給 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;
}
然后,會(huì)調(diào)用 considerNotify()
方法來最終將事件傳遞出去,
private void considerNotify(ObserverWrapper observer) {
// 這里會(huì)因?yàn)楫?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);
}
這里會(huì)因?yàn)楫?dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出 considerNotify()
方法,從而消息無法被傳遞出去。
3、LiveData 的通知機(jī)制
LiveData 的通知機(jī)制并不復(fù)雜,它的類主要包含在 livedata-core
包下面,總共也就 3 個(gè)類。LiveData 是一個(gè)抽象類,它有一個(gè)默認(rèn)的實(shí)現(xiàn)就是 MutableLiveData.
LiveData 主要依靠內(nèi)部的變量 mObservers
來緩存訂閱的對(duì)象和訂閱信息。其定義如下,使用了一個(gè)哈希表進(jìn)行緩存和映射,
private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
每當(dāng)我們調(diào)用一次 observe()
方法的時(shí)候就會(huì)有一個(gè)映射關(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)系中的類會(huì)先被包裝成 LifecycleBoundObserver
對(duì)象。然后使用該對(duì)象對(duì) owner 的生命周期進(jìn)行監(jiān)聽。
這的 LifecycleBoundObserver
和 ObserverWrapper
兩個(gè)類的定義如下,
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
是一個(gè)枚舉類型,定義了一些與生命周期相關(guān)的枚舉值。所以,當(dāng) Activity 或者 Fragment 的生命周期發(fā)生變化的時(shí)候會(huì)回調(diào)這個(gè)方法。從上面我們也可以看出,該方法內(nèi)部又調(diào)用了基類的 activeStateChanged()
方法,該方法主要用來更新當(dāng)前的 Observer 是否處于 Active 的狀態(tài)。我們上面無法通知也是因?yàn)樵谶@個(gè)方法中 mActive 被置為 false 造成的。
繼續(xù)看 activeStateChanged()
方法,我們可以看出在最后的幾行中,它調(diào)用了 dispatchingValue(this)
方法。所以,當(dāng) Fragment 從處于后臺(tái)切換到前臺(tái)之后,會(huì)將當(dāng)前緩存的值通知給觀察者。
那么值是如何緩存的,以及緩存了多少值呢?回到之前的 setValue()
和 dispatchingValue()
方法中,我們發(fā)現(xiàn)值是以一個(gè)單獨(dú)的變量進(jìn)行緩存的,
private volatile Object mData = NOT_SET;
因此,在我們的示例中,當(dāng)頁面從后臺(tái)切換到前臺(tái)的時(shí)候,只能將最后一次緩存的結(jié)果通知給觀察者就真相大白了。
總結(jié)
從上面的分析中,我們對(duì) LiveData 總結(jié)如下,
- 當(dāng)調(diào)用
observe()
方法的時(shí)候,我們的觀察者將會(huì)和 LifecycleOwner (Fragment 或者 Activity) 一起被包裝到一個(gè)類中,并使用哈希表建立映射關(guān)系。同時(shí),還會(huì)對(duì) Fragment 或者 Activity 的生命周期方法進(jìn)行監(jiān)聽,依次來達(dá)到監(jiān)聽觀察者是否處于 active 狀態(tài)的目的。 - 當(dāng) Fragment 或者 Activity 處于后臺(tái)的時(shí)候,其內(nèi)部的觀察者將處于非 active 狀態(tài),此時(shí)使用
setValue()
設(shè)置的值會(huì)緩存到 LiveData 中。但是這種緩存只能緩存一個(gè)值,新的值會(huì)替換舊的值。因此,當(dāng)頁面從后臺(tái)恢復(fù)到前臺(tái)的時(shí)候只有最后設(shè)置的一個(gè)值會(huì)被傳遞給觀察者。 - 2 中的當(dāng) Fragment 或者 Activity 從后臺(tái)恢復(fù)的時(shí)候進(jìn)行通知也是通過監(jiān)聽其生命周期方法實(shí)現(xiàn)的。
- 調(diào)用了
observe()
之后,F(xiàn)ragment 或者 Activity 被緩存了起來,不會(huì)造成內(nèi)存泄漏嗎?答案是不會(huì)的。因?yàn)?LiveData 可以對(duì)其生命周期進(jìn)行監(jiān)聽,當(dāng)其處于銷毀狀態(tài)的時(shí)候,該映射關(guān)系將被從緩存中移除。
以上。
(如有疑問,可以在評(píng)論中交流)
如果你喜歡這篇文章,請(qǐng)點(diǎn)贊!你也可以在以下平臺(tái)關(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
所有的文章維護(hù)在:Github, Android-notes