踩坑之路:LiveData之粘性事件

前言

何為粘性事件?
即發(fā)射的事件如果早于注冊山叮,那么注冊之后依然可以接收到的事件稱為粘性事件

背景

最近接手了一個公司的項目,采用了目前比較新的技術(shù):LiveData+ViewModel的事件通知框架。該框架擁有大量的優(yōu)點包括但不僅限于以下:1.實時感知生命周期柬姚。2.無需手動回收,解綁,即不會出現(xiàn)內(nèi)存泄漏的情況庄涡。3.數(shù)據(jù)變化可進行實時通知 等等......
本人對于這個框架也只是一知半解量承,奈何項目比較緊急,接手以后馬上就要開始干活穴店,所以只能是邊干活邊了解內(nèi)部實現(xiàn)原理撕捍。
結(jié)果項目中就碰到了一個讓我頭疼了整整一天的問題。那么到底是啥呢泣洞,請接著往下看↓

起因

由于公司代碼不便放到網(wǎng)上忧风,所以本人便以demo代碼代替(主要邏輯一致)

  public class CustomViewModel extends ViewModel {
    MutableLiveData<Integer> mLiveData;
    private int mPostedValue = 10;
    public MutableLiveData<Integer> getLiveData(){
        if(mLiveData == null){
            mLiveData = new MutableLiveData<>();
        }
        loadData();
        return mLiveData;
    }

    private void loadData() {
        new Thread(){
            @Override
            public void run() {
                SystemClock.sleep(2000);
                mLiveData.postValue(mPostedValue);
                mPostedValue = mPostedValue * 2;
            }
        }.start();
    }
}

這個就是ViewModel的代碼,用于進行網(wǎng)絡(luò)請求的操作球凰,返回數(shù)據(jù)以后通過LiveData實時刷新

public class MainActivity extends AppCompatActivity {
    private CustomViewModel mViewModel;
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
        final MutableLiveData<Integer> liveData = mViewModel.getLiveData();
        liveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e(TAG,"參數(shù)返回: " + integer);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                MutableLiveData<Integer> liveData = mViewModel.getLiveData();
                liveData.observe(MainActivity.this, new Observer<Integer>() {
                    @Override
                    public void onChanged(Integer integer) {
                        Log.e(TAG,"參數(shù)返回: " + integer);
                    }
                });
            }
        },5000);
    }

}

此為activity里面的代碼狮腿,onCreate會率先訂閱一個LiveData的事件,然后觀察網(wǎng)絡(luò)請求回調(diào)呕诉。onResume里面的代碼模仿的就是點擊事件再次進行網(wǎng)絡(luò)請求缘厢。

現(xiàn)象

打印結(jié)果如下

2019-09-14 18:48:23.710 6745-6745/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 10
2019-09-14 18:48:26.720 6745-6745/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 10
2019-09-14 18:48:28.721 6745-6745/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 20
2019-09-14 18:48:28.721 6745-6745/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 20

首先看到這個結(jié)果,肯定不是我想要的結(jié)果甩挫。下面的20打印了兩次贴硫,不過這個現(xiàn)象倒是一眼就能看出來,畢竟onCreate的時候已經(jīng)訂閱過一次伊者,你發(fā)射的第二次數(shù)據(jù)自然就會有兩個觀察者可以監(jiān)聽到英遭。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
        final MutableLiveData<Integer> liveData = mViewModel.getLiveData();
        liveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e(TAG,"參數(shù)返回: " + integer);
                //解除觀察者
                liveData.removeObserver(this);
            }
        });
    }

所以在onCreate的注冊方法里面加上了解除注冊的操作拖刃。嗯,完美了贪绘,打印下看看

2019-09-14 18:54:17.465 6973-6973/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 10
2019-09-14 18:54:20.483 6973-6973/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 10
2019-09-14 18:54:22.483 6973-6973/com.netease.livedatademo E/MainActivity: 參數(shù)返回: 20

事情總是不能如人所愿兑牡。。税灌。發(fā)現(xiàn)這時候10依然多打了一次均函,那么問題來了,這個10到底是哪里來的菱涤。(我在公司代碼里面全局搜索了一遍苞也,發(fā)射數(shù)據(jù)的地方就這么一個,于是就很費解到底怎么回事)

既然事情結(jié)果是這樣了粘秆,肯定不能就這么下去如迟,于是開始了源碼探究,既然onCreate里面的注冊已經(jīng)解除攻走,那么基本沒什么必要在往下看下去了殷勘,所以打算從onResume調(diào)用的注冊方法開始下手

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            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);
    }

LiveData在調(diào)用Observer以后最后會執(zhí)行addObserver方法,而此處getLifeCycle獲取的對象是LifecycleRegistry對象(原因不過多追究昔搂,不是本文重點)

    @Override
    public void addObserver(@NonNull LifecycleObserver observer) {
        ...代碼省略...
        while ((statefulObserver.mState.compareTo(targetState) < 0
                && mObserverMap.contains(observer))) {
            pushParentState(statefulObserver.mState);
            statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
            popParentState();
            // mState / subling may have been changed recalculate
            targetState = calculateTargetState(observer);
        }
        ...代碼省略...
    }

此處會進入while循環(huán)玲销,然后調(diào)用dispatchEvent方法,所以我們接著往下看

    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);
            mLifecycleObserver.onStateChanged(owner, event);
            mState = newState;
        }
    }

然后會調(diào)用LifeCycleBoundObserver的onStateChanged方法

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
       
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }
    }
    private abstract class ObserverWrapper {
      
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            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);
            }
        }
    }

   void dispatchingValue(@Nullable ObserverWrapper initiator) {
       ...代碼省略
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                //會執(zhí)行此方法
                considerNotify(initiator);
                initiator = null;
            } 
        ...代碼省略
    }

      private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

最終會判斷mLastVersion是否比mVersion大摘符,如果小于mVersion,那么會調(diào)用onChanged方法贤斜,即我們在MainActivity里面注冊的事件。那么mLastVersion和mVersion是什么逛裤,接著往下看:

    private int mVersion = START_VERSION;
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

我們發(fā)現(xiàn)mVersion初始值是-1瘩绒,這個mVersion是屬于LiveData的,然后只在setValue(postValue最終也會調(diào)用setValue)的時候會自增1,那么mLastVersion呢带族?

 int mLastVersion = START_VERSION;

發(fā)現(xiàn)mLastVersion也是初始值為-1锁荔,而這個mLastVersion是屬于ObserverWrapper的,而賦值的地方只有在比較完才會賦值.
那么仔細回想下,我們其實在onCreate那里注冊的觀察者信息其實有過一次setValue的操作了炉菲,又因為我們其實用的是同一個LiveData堕战,所以mVersion最后是會自增1的,又因為ObserverWrapper在每次注冊的時候都會重新new拍霜,所以mLastVersion每次都是-1開始嘱丢。
那么真相大白了,只要之前有發(fā)射過一次數(shù)據(jù)祠饺,那么后面注冊的觀察者都會接收到之前發(fā)射過的數(shù)據(jù)越驻,而且看樣子這個Version值不可以輕易改變,也就是說谷歌不提供API讓我們?nèi)∠粽承允录?這算是一個比較大的缺點了)

解決方案

問題來了,既然已經(jīng)知道了原因缀旁,那么怎么解決呢记劈?不可能放著不管的。方案有三并巍,容我一一道來


1.既然每次注冊時ObserverWrapper是不一樣的目木,那么只要我們的LiveData也不一樣不就可以輕松解決了?

2.不要多次注冊:onCreate里面的注冊以后懊渡,onResume里面就不要注冊了刽射,然后通過判斷條件的不同寫兩個不同的處理方式。

3.既然無法直接修改mVersion值剃执,和mLastVersion值誓禁。那么我們可以直接重寫LiveData類。然后重寫Observer接口肾档,通過重寫的Observer類的onChange方法進行攔截摹恰。那么怎么攔截?
在LiveData的Observe方法里面將傳入的Observer對象裝飾到我們自己的Observer類里面怒见,然后調(diào)用super.Observe的時候?qū)⑽覀冏约旱腛bserver方法傳入俗慈,然后就可以進行自定義攔截。代碼如下:

public class BaseLiveData<T> extends MutableLiveData<T> {
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer,boolean isSticky) {
        if(isSticky){
            super.observe(owner, observer);
        } else {
            super.observe(owner,new CustomObserver<T>(observer));
        }
    }

    @Override
    public void setValue(T value) {

        super.setValue(value);
    }

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

    class CustomObserver<T> implements Observer<T> {
        private Observer<? super T> mObserver;

        public CustomObserver(Observer<? super T> observer) {
            mObserver = observer;
        }

        @Override
        public void onChanged(T t) {
            //此處做攔截操作
            
            mObserver.onChanged(t);
        }
    }
}

總結(jié)

總的來說解決方案不算復(fù)雜速种,前面兩種更是沒有什么難度姜盈。只不過谷歌不提供API讓我們可以解除LiveData的粘性事件確實有點霸道。所以就這點來說遠沒有EventBust來的靈活配阵,當(dāng)然LiveData生命感知的能力確實是EventBus無法比擬的。只能說各取所需吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末示血,一起剝皮案震驚了整個濱河市棋傍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌难审,老刑警劉巖瘫拣,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異告喊,居然都是意外死亡麸拄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門黔姜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拢切,“玉大人,你說我怎么就攤上這事秆吵』匆” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長主穗。 經(jīng)常有香客問我泻拦,道長,這世上最難降的妖魔是什么忽媒? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任争拐,我火速辦了婚禮,結(jié)果婚禮上晦雨,老公的妹妹穿的比我還像新娘陆错。我一直安慰自己,他們只是感情好金赦,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布音瓷。 她就那樣靜靜地躺著,像睡著了一般夹抗。 火紅的嫁衣襯著肌膚如雪绳慎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天漠烧,我揣著相機與錄音杏愤,去河邊找鬼。 笑死已脓,一個胖子當(dāng)著我的面吹牛珊楼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播度液,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼厕宗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堕担?” 一聲冷哼從身側(cè)響起已慢,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霹购,沒想到半個月后佑惠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡齐疙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年膜楷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贞奋。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡赌厅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忆矛,到底是詐尸還是另有隱情察蹲,我是刑警寧澤请垛,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站洽议,受9級特大地震影響宗收,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亚兄,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一混稽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧审胚,春花似錦匈勋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至菲嘴,卻和暖如春饿自,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背龄坪。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工昭雌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人健田。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓烛卧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妓局。 傳聞我的和親對象是個殘疾皇子总放,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

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