LiveData 面試題庫(kù)、解答忿等、源碼分析

引子

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ù)觀察者辰如,分別是:

  1. 數(shù)據(jù)觀察者是否活躍。
  2. 數(shù)據(jù)觀察者綁定的生命周期組件是否活躍贵试。
  3. 數(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ā)值的方式:

  1. 當(dāng)值更新時(shí),遍歷所有觀察者將最新值分發(fā)給它們蜒车。
  2. 當(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"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末檩坚,一起剝皮案震驚了整個(gè)濱河市着撩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匾委,老刑警劉巖拖叙,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赂乐,居然都是意外死亡薯鳍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)挨措,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挖滤,“玉大人,你說(shuō)我怎么就攤上這事浅役≌端桑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵觉既,是天一觀的道長(zhǎng)惧盹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瞪讼,這世上最難降的妖魔是什么钧椰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮符欠,結(jié)果婚禮上嫡霞,老公的妹妹穿的比我還像新娘。我一直安慰自己希柿,他們只是感情好诊沪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布养筒。 她就那樣靜靜地躺著,像睡著了一般端姚。 火紅的嫁衣襯著肌膚如雪闽颇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天寄锐,我揣著相機(jī)與錄音,去河邊找鬼尖啡。 笑死橄仆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衅斩。 我是一名探鬼主播盆顾,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼畏梆!你這毒婦竟也來(lái)了您宪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奠涌,失蹤者是張志新(化名)和其女友劉穎宪巨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溜畅,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捏卓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慈格。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怠晴。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浴捆,靈堂內(nèi)的尸體忽然破棺而出蒜田,到底是詐尸還是另有隱情,我是刑警寧澤选泻,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布冲粤,位于F島的核電站,受9級(jí)特大地震影響滔金,放射性物質(zhì)發(fā)生泄漏色解。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一餐茵、第九天 我趴在偏房一處隱蔽的房頂上張望科阎。 院中可真熱鬧,春花似錦忿族、人聲如沸锣笨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)错英。三九已至入撒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椭岩,已是汗流浹背茅逮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留判哥,地道東北人献雅。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像塌计,于是被迫代替她去往敵國(guó)和親挺身。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容