Jetpack LiveData 是時候了解一下了

前言

Jetpack AAC 系列文章:

Jetpack Lifecycle 該怎么看梅惯?還肝否叔磷?
Jetpack LiveData 是時候了解一下了
Jetpack ViewModel 抽絲剝繭

上篇分析了Lifecycle卦羡,知道了如何優(yōu)雅地監(jiān)聽生命周期植捎,本篇將著重分析Lifecycle 的具體應(yīng)用場景之一:LiveData的原理及使用芽狗。
通過本篇文章鲤妥,你將了解到:

1、為什么需要LiveData?
2夷恍、LiveData 的使用方式
3魔眨、LiveData 的原理
4、LiveData 優(yōu)劣勢及其解決方案

1、為什么需要LiveData?

一個異步回調(diào)的例子

某個功能需要從網(wǎng)絡(luò)獲取數(shù)據(jù)并展示在頁面上遏暴,想想這個時候該怎么做呢侄刽?
很容易想到分三步:

1、請求網(wǎng)絡(luò)接口獲取數(shù)據(jù)朋凉。
2州丹、頁面調(diào)用接口并傳入回調(diào)對象。
3杂彭、數(shù)據(jù)通過回調(diào)接口通知UI 更新墓毒。

典型代碼如下:

object NetUtil {

    //接口
    lateinit var listener : InfoNotify

    fun getUserInfo(notify: InfoNotify) {
        listener = notify
        Thread {
            //模擬獲取網(wǎng)絡(luò)數(shù)據(jù)
            Thread.sleep(2000)
            //回調(diào)通知更新
            listener?.notify(100)
        }.start()
    }

    interface InfoNotify {
        fun notify(a : Int)
    }
}

編寫了一個網(wǎng)絡(luò)工具類,getUserInfo(xx) 傳入回調(diào)對象亲怠,而后在線程里拿到數(shù)據(jù)后通過回調(diào)通知界面更新:

        findViewById(R.id.original_callback).setOnClickListener((v)->{
            NetUtil.INSTANCE.getUserInfo(new NetUtil.InfoNotify() {
                @Override
                public void notify(int a) {
                    runOnUiThread(()->{
                        Toast.makeText(LiveDataActivity.this, "a=" + a, Toast.LENGTH_SHORT).show();
                    });
                }
            });
        });

這是獲取異步信息并展示的常規(guī)做法蚁鳖,但卻不夠完善,存在三個問題:

第一個問題:
當(dāng)退回到桌面后赁炎,此時網(wǎng)絡(luò)接口返回數(shù)據(jù),那么就會彈出Toast钾腺,如果我們想要在App退到后臺后不再彈出Toast徙垫,那么需要在彈Toast前判斷當(dāng)前App是否在前臺可見。

第二個問題:
假若在調(diào)用網(wǎng)絡(luò)的過程中退出LiveDataActivity放棒,當(dāng)網(wǎng)絡(luò)數(shù)據(jù)返回后再Toast姻报,因?yàn)锳ctivity 已經(jīng)不存在了,就會發(fā)生Crash间螟。規(guī)避的方式如下:

    runOnUiThread(()->{
        //如果Activity 正在銷毀或者已經(jīng)銷毀吴旋,那就沒必要Toast了
        if (!LiveDataActivity.this.isFinishing() && !LiveDataActivity.this.isDestroyed()) {
            Toast.makeText(LiveDataActivity.this, "a=" + a, Toast.LENGTH_SHORT).show();
        }
    });

第三個問題:
我們知道內(nèi)部類持有外部類引用,而new NetUtil.InfoNotify() 表示構(gòu)建了一個匿名內(nèi)部類厢破,這個內(nèi)部類對象會被NetUtil 持有荣瑟。Activity 退出時因?yàn)楸荒涿麅?nèi)部類持有,導(dǎo)致其無法釋放摩泪,造成內(nèi)存泄漏笆焰。規(guī)避方式如下:
1)在Activity onDestroy()里移除NetUtil 的InfoNotify監(jiān)聽。
2)在NetUtil 里使用弱引用包裹InfoNotify 對象见坑。

可以看出嚷掠,為了解決以上三個問題,需要額外多出不少代碼荞驴,而這些代碼又是重復(fù)性/代表性比較高不皆,因此我們期望有一種方式來幫我們實(shí)現(xiàn)簡單的異步/同步 通信問題,我們只需要著眼于數(shù)據(jù)熊楼,而不用管生命周期霹娄、內(nèi)存泄漏等問題。
剛好LiveData 能夠滿足需求。

2项棠、LiveData 的使用方式

簡單同步使用方式

分為三步:
第一步:構(gòu)造LiveData

public class SimpleLiveData {
    //LiveData 接收泛型參數(shù)
    private MutableLiveData<String> name;
    public MutableLiveData<String> getName() {
        if (name == null) {
            name = new MutableLiveData<>();
        }
        return name;
    }
}

LiveData 是抽象類悲雳,MutableLiveData 是其中的一個實(shí)現(xiàn)子類,上面的代碼其實(shí)就是將我們感興趣的數(shù)據(jù)包裹在MutableLiveData里香追,類型為String合瓢。
為了方便獲取MutableLiveData 實(shí)例,再將它封裝在SimpleLiveData里透典。

第二步:監(jiān)聽LiveData數(shù)據(jù)變化
有了SimpleLiveData晴楔,接下來看如何對它進(jìn)行操作:

    private void handleSingleLiveData() {
        //構(gòu)造LiveData
        simpleLiveData = new SimpleLiveData();
        //獲取LiveData實(shí)例
        simpleLiveData.getName().observe(this, (data)-> {
            //監(jiān)聽LiveData,此處的data參數(shù)類型即是為setValue(name)時name 的類型-->String
            Toast.makeText(LiveDataActivity.this, "singleLiveData name:" + data, Toast.LENGTH_SHORT).show();
        });
    }

第三步:主動變更LiveData數(shù)據(jù)
既然有觀察者監(jiān)聽峭咒,那么勢必需要有主動發(fā)起通知的地方税弃。

        findViewById(R.id.btn_change_name).setOnClickListener((v)->{
            int a = (int)(Math.random() * 10);
            //獲取LiveData實(shí)例,更新LiveData
            simpleLiveData.getName().setValue("singleName:" + a);
        });

很簡單凑队,調(diào)用LiveData.setValue(xx)即可则果,LiveData數(shù)據(jù)發(fā)生變更后,就會通知第二步的觀察者漩氨,觀察者刷新UI(Toast)西壮。

簡單異步使用方式

你可能已經(jīng)發(fā)現(xiàn)了,上面的數(shù)據(jù)變更是在主線程發(fā)起的叫惊,我們實(shí)際場景更多的是在子線程發(fā)起的款青,模擬子線程發(fā)起數(shù)據(jù)變更:

        findViewById(R.id.btn_change_name).setOnClickListener((v)->{
            new Thread(()->{
                int a = (int)(Math.random() * 10);
                //獲取LiveData實(shí)例,更新LiveData
                simpleLiveData.getName().postValue("singleName:" + a);
            }).start();
        });

開啟線程霍狰,在線程里更新LiveData抡草,此時調(diào)用的方法是postValue(xx)。

需要注意的是:許多文章在分析LiveData時蔗坯,習(xí)慣性和ViewModel混在一起講解康震,造成初學(xué)者理解上的困難,實(shí)際上兩者是不同的東西宾濒,都可以單獨(dú)使用签杈。分別將兩者分析后,再結(jié)合一起使用就會比較清楚來龍去脈鼎兽。

3答姥、LiveData 的原理

通過比對傳統(tǒng)的回調(diào)和LiveData,發(fā)現(xiàn)LiveData 使用簡潔谚咬,沒有傳統(tǒng)回調(diào)的那幾個缺點(diǎn)鹦付,接下來我們帶著問題分析它是如何做到規(guī)避那幾個缺點(diǎn)的。

添加觀察者

#LiveData.java
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        //該方法調(diào)用者必須在主線程
        assertMainThread("observe");
        //如果處在DESTROYED 狀態(tài)择卦,則沒必要添加觀察者
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        //包裝觀察者
        LiveData.LifecycleBoundObserver wrapper = new LiveData.LifecycleBoundObserver(owner, observer);
        //將包裝結(jié)果添加到Map里
        LiveData.ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        ...
        //監(jiān)聽生命周期
        owner.getLifecycle().addObserver(wrapper);
    }

重點(diǎn)看看LifecycleBoundObserver:

#LiveData.java
    class LifecycleBoundObserver extends LiveData.ObserverWrapper implements LifecycleEventObserver {
        @NonNull
        final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }

        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }
        ...
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                                   @NonNull Lifecycle.Event event) {
            Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
            //=====重要1
            if (currentState == DESTROYED) {
                //移除觀察者
                removeObserver(mObserver);
                //不再分發(fā)
                return;
            }
            Lifecycle.State prevState = null;
            while (prevState != currentState) {
                  prevState = currentState;
                  //通知觀察者
                  activeStateChanged(shouldBeActive());
                  currentState = mOwner.getLifecycle().getCurrentState();
                }
        }
        ...
      }

onStateChanged() 是LifecycleEventObserver 接口里定義的方法敲长,而LifecycleEventObserver 繼承自LifecycleObserver郎嫁。
當(dāng)宿主(Activity/Fragment) 生命周期發(fā)生改變時會調(diào)用onStateChanged()。

我們注意到注釋里的:"重要1"

removeObserver(mObserver)

目的是將之前添加的觀察者從Map 里移除祈噪。
當(dāng)宿主(Activity/Fragment) 處在DESTROYED 狀態(tài)時泽铛,移除LiveData的監(jiān)聽,避免內(nèi)存泄漏辑鲤。
這就解決了第三個問題:內(nèi)存泄漏問題盔腔。

shouldBeActive()用來判斷當(dāng)前宿主是否是活躍狀態(tài),此處定義的活躍狀態(tài)為:宿主的狀態(tài)要>="STARTED"狀態(tài)月褥,而該狀態(tài)區(qū)間為:Activity.onStart() 之后且Activity.onPause()之前弛随。

當(dāng)宿主處于活躍狀態(tài)時,才會繼續(xù)通知UI 數(shù)據(jù)變更了宁赤,進(jìn)而刷新UI舀透,若是處于非活躍狀態(tài),比如App 失去焦點(diǎn)(onPause()被調(diào)用)决左,那么將不會刷新UI 愕够。

通知觀察者

觀察者接收數(shù)據(jù)的通知有兩個來源:

1、宿主的生命周期發(fā)生變化佛猛。
2链烈、通過調(diào)用setValue()/postValue() 觸發(fā)。

上面分析的是第1種情況挚躯,接下來分析第2種場景。

LiveData.setValue() 調(diào)用棧

先看方法實(shí)現(xiàn):

#LiveData.java
    protected void setValue(T value) {
        //必須在主線程調(diào)用
        assertMainThread("setValue");
        //版本增加
        mVersion++;
        //暫存值
        mData = value;
        //分發(fā)到觀察者
        dispatchingValue(null);
    }

再看dispatchingValue(xx)

#LiveData.java
    void dispatchingValue(@Nullable LiveData.ObserverWrapper initiator) {
        ...
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                //精準(zhǔn)通知
                considerNotify(initiator);
                initiator = null;
            } else {
                //遍歷調(diào)用所有觀察者
                for (Iterator<Map.Entry<Observer<? super T>, LiveData.ObserverWrapper>> iterator =
                     mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

通過搜索發(fā)現(xiàn)擦秽,dispatchingValue(xx) 被兩個地方調(diào)用码荔,其實(shí)就是上面所說的:觀察者接收數(shù)據(jù)的通知有兩個來源。

當(dāng)主動調(diào)用setValue(xx)/postValue(xx)時感挥,因?yàn)闆]有指定分發(fā)給哪個觀察者缩搅,因此會遍歷通知所有觀察者。
而當(dāng)生命周期發(fā)生變化時触幼,因?yàn)槊總€觀察者都綁定了Lifecycle硼瓣,因此都獨(dú)立處理了數(shù)據(jù)分發(fā)。


image.png

如圖所示置谦,最后都會調(diào)用到considerNotify(xx):

#LiveData.java
    private void considerNotify(LiveData.ObserverWrapper observer) {
        //非活躍狀態(tài)堂鲤,直接返回
        if (!observer.mActive) {
            return;
        }
        //此處再額外判斷是為了防止observer.mActive 沒有及時被賦值(也就是Lifecycle 沒有及時通知到)
        //因此逮光,這里會主動去拿一次狀態(tài)坟奥,若是非活躍狀態(tài)屎暇,就返回履植。
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //如果LiveData數(shù)據(jù)版本<= 觀察者的數(shù)據(jù)版本盼樟,則直接返回
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //更新觀察者版本
        observer.mLastVersion = mVersion;
        //最終通知觀察者
        observer.mObserver.onChanged((T) mData);
    }

可以看出申屹,不論數(shù)據(jù)通知來源于哪丛晌,最后都只會在活躍狀態(tài)時才會通知觀察者镜廉。
這就解決了最開始的第一個、第二個問題寓涨。

不區(qū)分活躍/非活躍

當(dāng)然啦盯串,是否活躍都是通過調(diào)用ObserverWrapper 里的方法來進(jìn)行判斷的,因此若是想要不區(qū)分是否活躍都能收到數(shù)據(jù)變更戒良,則可在添加觀察者時体捏,調(diào)用如下方法:

  simpleLiveData.getName().observeForever(s -> {
            Toast.makeText(LiveDataActivity.this, "singleLiveData name:" + s, Toast.LENGTH_SHORT).show();
        });

該方法調(diào)用時沒有傳入LifecycleOwner 實(shí)例,因此此時的Observer沒有和Lifecycle進(jìn)行關(guān)聯(lián)蔬墩,當(dāng)然就沒有所謂的活躍與非活躍的劃分了译打。
更直觀的是Observer的命名:AlwaysActiveObserver(永遠(yuǎn)活躍)。
綁定Lifecycle Observer的命名:LifecycleBoundObserver (有限制)拇颅。

LiveData.postValue() 調(diào)用棧

#LiveData.java
    protected void postValue(T value) {
        boolean postTask;
        //子線程奏司、主線程都需要修改mPendingData,因此需要加鎖
        synchronized (mDataLock) {
            //mPendingData 是否還在排隊(duì)等待發(fā)送出去
            //mPendingData == NOT_SET 表示當(dāng)前沒有排隊(duì)
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            //說明上次的Runnable 還沒執(zhí)行
            //直接返回樟插,不需要切換到主線程執(zhí)行
            return;
        }
        //切換到主線程執(zhí)行Runnable
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

這里有個明顯的特點(diǎn):

當(dāng)調(diào)用postValue(xx)比較快時韵洋,數(shù)據(jù)都會更新為最新的存儲到mPendingData里,若是上條數(shù)據(jù)變更沒有發(fā)送出去黄锤,那么將不會再執(zhí)行新的Runnable了搪缨。
因此觀察者有可能不會收到全部的數(shù)據(jù)變更,而是只保證收到最新的更新鸵熟。

切換到主線程執(zhí)行Runnable:

#LiveData.java
    private final Runnable mPostValueRunnable = new Runnable() {
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                //重置狀態(tài)
                mPendingData = NOT_SET;
            }
            //發(fā)送數(shù)據(jù)變更
            setValue((T) newValue);
        }
    };

postValue(xx)作用:

將數(shù)據(jù)存儲到臨時變量里副编,并切換到主線程執(zhí)行setValue(xx),將數(shù)據(jù)變更分發(fā)出去流强。

image.png

4痹届、LiveData 優(yōu)劣勢及其解決方案

優(yōu)勢

通過原理部分的分析,你可能已經(jīng)察覺到了:LiveData 比較簡單打月,上手也比較快队腐。
其優(yōu)勢比較明顯:

a、生命周期感知:

借助Lifecycle 能夠感知生命周期各個階段的狀態(tài)奏篙,進(jìn)而能夠?qū)Σ煌纳芷跔顟B(tài)做相應(yīng)的處理柴淘。

正因?yàn)榭梢愿兄芷冢裕?/p>

  • 可以在活躍狀態(tài)時再更新UI 秘通。
  • UI 保持最新數(shù)據(jù)(從非活躍到活躍狀態(tài)總能收到最新數(shù)據(jù))为严。
  • 觀察者無需手動移除,不會有內(nèi)存泄漏肺稀。
  • Activity/Fragment 不存活不會更新UI梗脾,避免了Crash。
  • 粘性事件設(shè)計方式盹靴,新的觀察者無需再次主動獲取最新數(shù)據(jù)炸茧。

還有個額外的特點(diǎn):稍微改造一下瑞妇,LiveData 可以當(dāng)做組件之間消息傳遞使用。

b梭冠、數(shù)據(jù)實(shí)時同步

在主線程調(diào)用時:LiveData.setValue(xx)能夠直接將數(shù)據(jù)通知到觀察者辕狰。
在子線程調(diào)用時:LiveData.postValue(xx)將數(shù)據(jù)暫存,并且切換到主線程調(diào)用setValue(xx)控漠,將暫存數(shù)據(jù)發(fā)出去蔓倍。
因此,從數(shù)據(jù)變更--->發(fā)送通知--->觀察者接收數(shù)據(jù) 這幾個步驟沒有明顯地耗時盐捷,UI 能夠?qū)崟r監(jiān)聽到數(shù)據(jù)的變化偶翅。

劣勢

a、postValue(xx) 數(shù)據(jù)丟失

postValue(xx)每次調(diào)用時將數(shù)據(jù)存儲在mPendingData 變量里碉渡,因此后面的數(shù)據(jù)會覆蓋前面的數(shù)據(jù)聚谁。LiveData 確保UI 能夠拿到最新的數(shù)據(jù),而此過程中的數(shù)據(jù)變化過程可能會丟失滞诺。
問題的原因是:不是每一次數(shù)據(jù)變更都會post到主線程執(zhí)行形导。
因此想要每次都通知,則需要重新包裝一下LiveData习霹,如下:

public class LiveDataPostUtil {
    private static Handler handler;
    public static <T> void postValue(MutableLiveData<T> liveData, T data) {
        if (liveData == null || data == null)
            return;
        if (handler == null) {
            handler = new Handler(Looper.getMainLooper());
        }
        handler.post(new CustomRunnable<>(liveData, data));
    }

    static class CustomRunnable<T> implements Runnable{
        private MutableLiveData<T> liveData;
        private T data;

        public CustomRunnable(MutableLiveData<T> liveData, T data) {
            this.liveData = liveData;
            this.data = data;
        }

        @Override
        public void run() {
            liveData.setValue(data);
        }
    }
}

b朵耕、粘性事件

相信大家看到過一些博客的分析也知道了LiveData 粘性事件問題。
粘性事件:

數(shù)據(jù)變更發(fā)生后淋叶,才注冊的觀察者阎曹,此時觀察者還能收到變更通知。

來看看什么場景下會有這種現(xiàn)象煞檩。
定義全局持有LiveData 的單例:

public class GlobalLiveData {
    private static class Inner {
        static GlobalLiveData ins = new GlobalLiveData();
    }

    public static GlobalLiveData getInstance() {
        return Inner.ins;
    }

    private SimpleLiveData simpleLiveData;
    private GlobalLiveData() {
        simpleLiveData = new SimpleLiveData();
    }

    public SimpleLiveData getSimpleLiveData() {
        return simpleLiveData;
    }
}

在Activity.onCreate()里監(jiān)聽數(shù)據(jù)變化:

       GlobalLiveData.getInstance().getSimpleLiveData().getName().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Toast.makeText(LiveDataActivity.this, "global name:" + s, Toast.LENGTH_SHORT).show();
            }
        });

然后點(diǎn)擊按鈕發(fā)送數(shù)據(jù)變更:

    findViewById(R.id.btn_change_name).setOnClickListener((v)->{
     GlobalLiveData.getInstance().getSimpleLiveData().getName().setValue("from global");
    });

數(shù)據(jù)變更發(fā)出去后处嫌,觀察者收到通知并Toast,此時一切正常形娇。

當(dāng)Activity 關(guān)閉并重新打開時,此時發(fā)現(xiàn)還有Toast 彈出筹误。

粘性事件現(xiàn)象發(fā)生了桐早。
明明是全新注冊的觀察者,而且此時沒有新的數(shù)據(jù)變更厨剪,卻依然收到之前的數(shù)據(jù)哄酝。
這和LiveData 的實(shí)現(xiàn)有關(guān),看看核心源碼實(shí)現(xiàn):

#LiveData.java
    private void considerNotify(LiveData.ObserverWrapper observer) {
        //mVersion 為LiveData 當(dāng)前數(shù)據(jù)版本祷膳,當(dāng)setValue/postValue 發(fā)生時陶衅,mVersion++
        //通過比對LiveData 當(dāng)前數(shù)據(jù)版本與觀察者的數(shù)據(jù)版本,若是發(fā)現(xiàn)LiveData 當(dāng)前數(shù)據(jù)版本 更大
        //說明是之前沒有通知過觀察者直晨,因此需要通知搀军,反之則不通知膨俐。
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //將觀察者數(shù)據(jù)版本保持與LiveData 版本一致,表明該觀察者消費(fèi)了最新的數(shù)據(jù)罩句。
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

再回溯一下流程:

1焚刺、初始時LiveData.mVersion= -1,ObserverWrapper.mLastVersion = -1门烂,因此初次進(jìn)入Activity時沒有數(shù)據(jù)通知乳愉。
2、當(dāng)點(diǎn)擊按鈕后(LiveData.setValue())屯远,此時LiveData.mVersion = 0蔓姚;因?yàn)長iveData.mVersion>ObserverWrapper.mLastVersion,因此觀察者能夠收到通知慨丐。
3坡脐、當(dāng)退出Activity 再進(jìn)來后,因?yàn)镺bserverWrapper 是全新new 出來的咖气,ObserverWrapper.mLastVersion = -1挨措,而LiveData.mVersion =0,還是大于ObserverWrapper.mLastVersion崩溪,因此依然能夠收到通知浅役。

要解決這個問題,很直觀的想法是從version字段出發(fā)伶唯,而LiveData觉既、ObserverWrapper 并沒有對外暴露方法來修改version,此時我們想到了反射乳幸。

通過反射修改ObserverWrapper.mLastVersion 的值瞪讼,使得在第一次注冊時候保持與LiveData.mVersion 值一致。

這也是很多博客的主流解決方法粹断,因?yàn)橐瓷銶ap符欠,進(jìn)而反射里面的Observer拿出version,步驟有點(diǎn)多瓶埋,這里提供一種方案希柿,只需要拿到LiveData.mVersion即可,剛好LiveData提供了方法:

    int getVersion() {
        return mVersion;
    }

因此我們只需要調(diào)用這個反射方法即可:

public class EasyLiveData<T> extends LiveData<T> {

    @Override
    public void observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) {
        super.observe(owner, new EasyObserver<>(observer));
    }

    @Override
    public void observeForever(@NonNull @NotNull Observer<? super T> observer) {
        super.observeForever(new EasyObserver<>(observer));
    }

    @Override
    protected void setValue(T value) {
        super.setValue(value);
    }

    @Override
    protected void postValue(T value) {
        super.postValue(value);
    }

    class EasyObserver<T> implements Observer<T>{
        private Observer observer;
        private boolean shouldConsumeFirstNotify;
        public EasyObserver(Observer observer) {
            this.observer = observer;
            shouldConsumeFirstNotify = isNewLiveData(EasyLiveData.this);
        }

        @Override
        public void onChanged(T t) {
            //第一次進(jìn)來养筒,沒有發(fā)生過數(shù)據(jù)變更曾撤,則后續(xù)的變更直接通知。
            if (shouldConsumeFirstNotify) {
                observer.onChanged(t);
            } else {
                //若是LiveData 之前就有數(shù)據(jù)變更晕粪,那么這一次的變更不處理
                shouldConsumeFirstNotify = true;   
            }
        }

        private boolean isNewLiveData(LiveData liveData) {
            Class ldClass = LiveData.class;
            try {
                Method method = ldClass.getDeclaredMethod("getVersion");
                method.setAccessible(true);
                //獲取版本
                int version = (int)method.invoke(liveData);
                //版本為-1挤悉,說明是初始狀態(tài),LiveData 還未發(fā)生過數(shù)據(jù)變更巫湘。
                return version == -1;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
    }
}

如若不想要粘性事件装悲,則使用上述的EasyLiveData 即可昏鹃。
粘性事件/非粘性事件 對比如下:


粘性事件.gif
非粘性事件.gif

可以看出,再次進(jìn)入Activity時衅斩,并沒有彈出Toast盆顾。

優(yōu)劣勢辯證看

LiveData 優(yōu)勢很明顯,當(dāng)然劣勢也比較突出畏梆,雖然說是劣勢您宪,換個角度看就是仁者見仁智者見智:

個人猜測LiveData 設(shè)計的側(cè)重點(diǎn)就不是在消息通知上,而是為了讓UI 能夠感知到最新數(shù)據(jù)奠涌,并且無需再次請求數(shù)據(jù)宪巨。
當(dāng)然,為了使得LiveData 更加契合我們的應(yīng)用場景溜畅,可以按上述方法進(jìn)行適當(dāng)改造捏卓。

如果你是用Java 開發(fā),那么LiveData 是把利刃慈格,如果你用Kotlin怠晴,可以考慮用Flow。

下篇將分析ViewModel浴捆,徹底厘清為啥ViewModel能夠存儲數(shù)據(jù)以及運(yùn)用場合蒜田。

本文基于:implementation 'androidx.appcompat:appcompat:1.4.1'

LiveData 演示&工具

您若喜歡,請點(diǎn)贊选泻、關(guān)注冲粤,您的鼓勵是我前進(jìn)的動力

持續(xù)更新中,和我一起步步為營系統(tǒng)页眯、深入學(xué)習(xí)Android

1梯捕、Android各種Context的前世今生
2、Android DecorView 必知必會
3窝撵、Window/WindowManager 不可不知之事
4傀顾、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6碌奉、Android invalidate/postInvalidate/requestLayout 徹底厘清
7短曾、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9道批、Android 鍵盤一招搞定
10错英、Android 各種坐標(biāo)徹底明了
11入撒、Android Activity/Window/View 的background
12隆豹、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14茅逮、Android 存儲系列
15璃赡、Java 并發(fā)系列不再疑惑
16判哥、Java 線程池系列
17、Android Jetpack 前置基礎(chǔ)系列
18碉考、Android Jetpack 易懂易學(xué)系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塌计,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侯谁,更是在濱河造成了極大的恐慌锌仅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墙贱,死亡現(xiàn)場離奇詭異热芹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惨撇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門伊脓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魁衙,你說我怎么就攤上這事报腔。” “怎么了剖淀?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵纯蛾,是天一觀的道長。 經(jīng)常有香客問我祷蝌,道長茅撞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任巨朦,我火速辦了婚禮米丘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糊啡。我一直安慰自己拄查,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布棚蓄。 她就那樣靜靜地躺著堕扶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梭依。 梳的紋絲不亂的頭發(fā)上稍算,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音役拴,去河邊找鬼糊探。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的科平。 我是一名探鬼主播褥紫,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞪慧!你這毒婦竟也來了髓考?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤弃酌,失蹤者是張志新(化名)和其女友劉穎氨菇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妓湘,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡门驾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了多柑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奶是。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竣灌,靈堂內(nèi)的尸體忽然破棺而出聂沙,到底是詐尸還是另有隱情,我是刑警寧澤初嘹,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布及汉,位于F島的核電站,受9級特大地震影響屯烦,放射性物質(zhì)發(fā)生泄漏坷随。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一驻龟、第九天 我趴在偏房一處隱蔽的房頂上張望温眉。 院中可真熱鬧,春花似錦翁狐、人聲如沸类溢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闯冷。三九已至,卻和暖如春懈词,著一層夾襖步出監(jiān)牢的瞬間蛇耀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工坎弯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纺涤,地道東北人躁倒。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像洒琢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子褐桌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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