把 LiveData 用于事件传递那些坑
0、前言
如果不是很了解 LiveData 和 Lifecycle 的同學可以先看一下我之前的文章 基于 Android Architecture Components 的 MVVM 淺析。同時安利下自己平時用的工具庫 LiveDataUtils,還在不斷完善中,歡迎大家 star、fork、關注和批評指正。
1、為什么要把 LiveData 當作事件傳遞用
利用觀察者模式打造的事件總線的優點不必多說(當然也有很多缺點),如 EventBus 和 RxBus 用的好的話能起到很好的解耦作用,使整個程序架構更加清晰,不至于到處傳遞各種 Callback。但是他們都缺少了對 View 層(Activity、Fragment 等)的生命周期的感知能力,需要在生命周期結束時手動解除觀察者,手動管理生命周期十分繁瑣且很容易出錯。
而 Google 推出的 Lifecycle 庫就是為了解決這一問題,其中的 LiveData 就是一個具有生命周期感知能力的觀察者,如果用它來打造一個能夠感知生命周期的事件總線,豈不美哉!
2、LiveData 當作事件傳遞用的那些坑
在隨著對 LiveData 的運用和理解的逐漸深入,特別是對它的「生命周期感知能力」有了更深的理解,慢慢發現這樣用的一些坑,借此機會就跟大家分享探討一下。而且我平時也有把 LiveData 純粹當作事件傳遞來用,特別是列表操作(比如涉及 IO 的增刪操作,View 層需要知道哪個數據改動了,以及操作是否成功等,只能以事件的形式傳遞)。
2.1、postValue 數據丟失的問題
在我的前一篇文章中也提到過,大家也可以直接看源碼。postValue 只是把傳進來的數據先存到 mPendingData,然后往主線程拋一個 Runnable,在這個 Runnable 里面再調用 setValue 來把存起來的值真正設置上去,并回調觀察者們。而如果在這個 Runnable 執行前多次 postValue,其實只是改變暫存的值 mPendingData,并不會再次拋另一個 Runnable。這就會出現后設置的值把前面的值覆蓋掉的問題,會導致事件丟失。
protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;// 這里先把數據暫存起來,后來的數據會覆蓋前面的mPendingData = value;}// 這里保證只拋一個 mPostValueRunnable,#-.-if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); } 復制代碼2.2、setValue 不回調觀察者
LiveData 的生命周期感知能力就體現在這里,它不會回調處于「非激活狀態」(即 onStart 之后到 onPause 之前)的觀察者,因為這時更新 View 沒有意義,而且比較危險,它會等到觀察者激活之后再把新的值回調給他。
但是如果我傳了多個數據(假設都是用 setValue 保證不會被覆蓋),那些處于非激活狀態的觀察者是毫不知情的,他們在激活的時候只會收到最后一個數據。這對于事件傳遞來說,就表現為事件丟失,中間傳的任何數據都無法收到,那也就失去了事件傳遞的意義。
2.3、LiveData 就不是為傳遞事件準備的
從上面兩點也可以看出,LiveData (或者說它的觀察者) 在觀察者激活之前并不關心中間經歷過多少次數據變更,它只會在某個觀察者激活時,傳遞給他最新的值,中間的值都不會起作用。
當然 LiveData 的設計也不是為了傳遞事件的,它是為了反應 View 當前狀態的,View 層只需要根據當前 LiveData 的值去渲染數據就行,非激活狀態時 View 都不可見,就算更新了也沒意義。
我最開始也是覺得 LiveData 用到了觀察者模式,而且可以進行一些不同 Fragment 之間數據通訊,就想到了事件總線,現在想想當時還是 too young too naive。
3、打造一個不會丟事件的 LiveData
LiveData 的其他功能做的很完善,只是會丟事件,我們要改造就要就針對上面的問題逐個擊破。
3.1、postValue 的問題
對于 postValue 的問題,既然它最后也是調用的 setValue,丟數據是因為只拋了一次 Runable,那我們就自己每次都往主線程拋一個 Runable 就能解決這個問題 具體實現可以參考之前提到的 LiveDataUtils。
/*** LiveData 相關的工具類,簡化 LiveData 操作** @author funnywolf* @since 2019-04-22*/ public class LiveDataUtils {private static Handler sMainHandler;/*** 用 setValue 更新 MutableLiveData 的數據,如果在子線程,就切換到主線程*/public static <T> void setValue(MutableLiveData<T> mld, T d) {if (mld == null) {return;}if (Thread.currentThread() == Looper.getMainLooper().getThread()) {mld.setValue(d);} else {postSetValue(mld, d);}}/*** 向主線程的 handler 拋 SetValueRunnable*/public static <T> void postSetValue(MutableLiveData<T> mld, T d) {if (sMainHandler == null) {sMainHandler = new Handler(Looper.getMainLooper());}sMainHandler.post(SetValueRunnable.create(mld, d));}private static class SetValueRunnable<T> implements Runnable {private final MutableLiveData<T> liveData;private final T data;private SetValueRunnable(@NonNull MutableLiveData<T> liveData, T data) {this.liveData = liveData;this.data = data;}public void run() {liveData.setValue(data);}public static <T> SetValueRunnable<T> create(@NonNull MutableLiveData<T> liveData, T data) {return new SetValueRunnable<>(liveData, data);}} } 復制代碼3.2、非激活狀態的問題
其實我覺得這個問題的主要「責任」并不在 LiveData,而是在它的觀察者,「是你告訴我你非激活的呀,那我怎么給你發數據呢,我發給你,萬一你出問題了呢,那到底誰負責?」。
我們常用的觀察者其實是 LifecycleBoundObserver,在調用 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) 會自動幫我們封裝一個這樣的觀察者,而它會根據 LifecycleOwner 的生命周期呈現出「激活」和「非激活」狀態。
LiveData 默認的還有另外一種觀察者 AlwaysActiveObserver,它是我們在調用 public void observeForever(@NonNull Observer<? super T> observer) 時生成的,顧名思義它會一直處于激活狀態,LiveData 當然也不會替我們管理這樣觀察者的生命周期,我們需要在不使用時手動調用 public void removeObserver(@NonNull final Observer<? super T> observer) 移除觀察者,否則可能會內存泄漏。
// AlwaysActiveObserver boolean shouldBeActive() {return true; } 復制代碼這個 AlwaysActiveObserver 看樣子能夠解決我們的問題,他一直處于激活狀態,那所有的事件都會回調給他,但是需要我們自己管理生命周期。這不是開歷史倒車嗎?好不容易有生命周期感知了,現在又要自己手動搞?
3.3、造一個生命周期感知的還不丟事件的觀察者
手動管理生命周期是絕對不能忍的,AlwaysActiveObserver 可以解決剛才說的問題,那我們就造一個新的觀察者來管理 observeForever 和 removeObserver 的問題。既然要造,那就造個好用的,首先事件一定不能丟,要不就沒意義了;而且生命周期要觀察者自己管理,不能只是簡單的 observeForever 和 removeObserver,非激活狀態之類的也要考慮進去。
既然要管理生命周期,那就不得不用到 LifecycleOwner、Lifecycle,然后自己觀察 LifecycleOwner 的 Lifecycle。
Lifecycle 對外只給了這個接口,并不含有任何回調,我們需要用注釋里說的 OnLifecycleEvent 注解來標記相應的函數,Lifecycle 會通過反射拿到標記的函數,然后生成對應的適配器,感興趣的可以看下 Lifecycling.getCallback 函數。比如我們可以這樣用
public class Observer implements LifecycleObserver {(Lifecycle.Event.ON_START)private void onStart() {doSomethingOnStart();} } 復制代碼拿到生命周期后,我們就可以在一開始 observeForever,在 Lifecycle.Event.ON_DESTROY 時 removeObserver。
接下來就要考慮激活和非激活的狀態了,既然用了 observeForever,那每次事件都會有回調,這時候如果 Lifecycle 處于激活狀態,那可以直接把事件發出去。但如果非激活,不能直接把事件發出去,又不能丟,那我們就需要先把事件存起來,然后在 Lifecycle 變為激活狀態時再把存起來的事件發送出去。簡單畫了下流程圖。
3.4、保證 LiveData 的事件更新
3.1 也說過要自己處理 postValue,其次要保證用我們自己定義的觀察者,需要重寫 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)。
/*** 用作事件總線的 {@link MutableLiveData}** @see AsEventBus** @author funnywolf* @since 2019-05-18*/ public class EventMutableLiveData<T> extends MutableLiveData<T> {public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {LiveEventObserver.bind(this, owner, observer);}public void postValue(T value) {LiveDataUtils.setValue(this, value);} }/*** 用作事件總線的 {@link MediatorLiveData}** @see AsEventBus** @author funnywolf* @since 2019-05-18*/ public class EventMediatorLiveData<T> extends MediatorLiveData<T> {public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {LiveEventObserver.bind(this, owner, observer);}public void postValue(T value) {LiveDataUtils.setValue(this, value);} }/*** 該注解只用于 {@link androidx.lifecycle.LiveData},用于表示 LiveData 是當成事件總線用的,需要注意:* - 觀察者在非激活狀態(onStart 之后,onPause 之前)時不會產生回調,會丟失事件* - postValue 可能會被覆蓋,只能用 setValue 來更新值* - LiveData 的事件都是黏性的,不使用時手動拋出一個 null 事件,以防下次綁定時會發送存在之前的舊數據;** @see LiveDataUtils* @see LiveEventObserver* @see EventMutableLiveData* @see EventMediatorLiveData** @author funnywolf* @since 2019-05-06*/ (ElementType.FIELD) public AsEventBus { } 復制代碼4、LiveDataUtils 中其他工具簡介
這個工具包里還有其他一些我平時常用的小工具,這里簡單分享下:
- StateData 是含有狀態和錯誤信息的數據包裝類,因為 LiveData 只有一個 onChanged 回調,無法知道數據狀態,所以搞了這個類
- RxLiveData 繼承自 MutableLiveData,實現了 Disposable 和 Observer 接口,主要為了數據從 RxJava 到 LiveData 的轉換
- LiveDataBus,一個基于 LiveData 的事件總線,但是不推薦用。事件總線這玩意盡量不要用,除非是不用不行的場合,寫的時候很香,之后維護起來很麻煩
5、總結
不得不說谷歌的生命周期庫真的很強大,不僅給我們提供了現成的工具,還給予了我們方便 DIY 的能力,一個不到五百行的 LiveData 都能玩出很多花樣。以上也只是自己的一些經驗總結,難免會有不足,歡迎各位批評指正。
轉載于:https://juejin.im/post/5cdff0de5188252f5e019bea
總結
以上是生活随笔為你收集整理的把 LiveData 用于事件传递那些坑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux学习记录-7
- 下一篇: 用Windows Server Back