感知生命週期的資料 -- LiveData

張天氣Up發表於2020-09-13

感知生命週期的資料 -- LiveData

零. 前言

上篇文章《萬物基於Lifecycle》 介紹了整個Lifecycle體系的基石,今天這篇文章我們們來看看Jetpack給我們帶來的活著的資料——LiveData

大綱

  • LiveData 是什麼?
  • 為什麼要用LiveData?
  • How to use LiveData?
  • LiveData的生命感知能力從何而來,是如何與Lifecycle結合的?

一. LiveData 是什麼?

​ LiveData 簡單來說,就是普通資料物件的一個包裝類,這個包裝類中幫助你主動管理了資料的版本號,基於觀察者模式,讓普通的資料物件能夠感知所屬宿主(Activity、Fragment)的生命週期。這種感知能力就能夠保證只有宿主活躍(Resumed、Started)時,資料的觀察者才能受到資料變化的訊息。

上面這短短的一段話,卻有著重大的意義,舉一個case:

有一個頁面需要載入一個列表,我們需要在後臺執行緒去伺服器請求對應的資料,請求資料成功並經過解析後post訊息通知UI,UI再渲染請求來的資料。等等!假若此時的網路很慢,而剛好使用者此時按home鍵退出了應用,那麼在此時UI是不應該被渲染的,而是應該等使用者重新進入應用時才開始渲染。

從下面的演示程式碼就詮釋了上面的說所的case

class MainActivity extends AppcompactActivity{
    public void onCreate(Bundle bundle){
  
     Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
             //無論頁面可見不可見,都會去執行頁面重新整理,IO。更有甚者彈出對話方塊
        }
      };
     //1.無論當前頁面是否可見,這條訊息都會被分發。----消耗資源
     //2.無論當前宿主是否還存活,這條訊息都會被分發。---記憶體洩漏
    handler.sendMessage(msg)

    
    liveData.observer(this,new Observer<User>){
         void onChanged(User user){
           
         }
     }
    //1.減少資源佔用---          頁面不可見時不會派發訊息
    //2.確保頁面始終保持最新狀態---頁面可見時,會立刻派發最新的一條訊息給所有觀察者--保證頁面最新狀態
    //3.不再需要手動處理生命週期---避免NPE
    //4.可以打造一款不用反註冊,不會記憶體洩漏的訊息匯流排---取代eventbus
    liveData.postValue(data);
  }
}

有人說,我可以在處理訊息時,根據當前頁面時是否可見來具體處理對應邏輯。是的,沒錯,確實可以這樣,就像下面這樣。

 Handler handler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
         if (isActivityValid()) {
         	// updateUI...
         } else {
         	// dosomething...
         }
    }
  };

我再拿上面的例子說一下這種問題:

  1. 需要自行判斷宿主活躍狀態,防止生命週期越界。
  2. 如果Activity不可見,此時不更新UI,那麼就需要複寫onResume方法去更新UI,手工管理生命週期,增加了程式碼的複雜性。

二. 為什麼要使用LiveData?

上面的例子已經很好地詮釋了LiveData的強大,當然不僅限於此,它的優勢如下:

  1. 確保介面符合資料狀態

    LiveData 遵循觀察者模式。當生命週期狀態發生變化時,LiveData 會通知 Observer 物件。觀察者可以在onChanged事件時更新介面,而不是在每次資料發生更改時立即更新介面。

  2. 不會發生記憶體洩漏

    觀察者會繫結到 Lifecycle 物件,並在其關聯的生命週期遭到銷燬後進行自我清理。

  3. 不會因 Activity 停止而導致崩潰

    如果觀察者的生命週期處於非活躍狀態(如返回棧中的 Activity),則它不會接收任何 LiveData 事件。

  4. 不再需要手動處理生命週期

    介面元件只是觀察相關資料,不會停止或恢復觀察。LiveData 將自動管理所有這些操作,因為它在觀察時可以感知相關的生命週期狀態變化。

  5. 資料始終保持最新狀態

    如果生命週期變為非活躍狀態,它會在再次變為活躍狀態時接收最新的資料。例如,曾經在後臺的 Activity 會在返回前臺後立即接收最新的資料。

  6. 適當的配置更改

    如果由於配置更改(如裝置旋轉)而重新建立了 Activity 或 Fragment,它會立即接收最新的可用資料。

  7. 共享資源

    可以使用單一例項模式擴充套件 LiveData 物件以封裝系統服務,以便在應用中共享它們。LiveData 物件連線到系統服務一次,然後需要相應資源的任何觀察者只需觀察 LiveData 物件

  8. 支援黏性事件的分發
    即先傳送一條資料,後註冊一個觀察者,預設是能夠收到之前傳送的那條資料的

三. How to use LiveData ?

step1: 新增依賴:

step2: 在ViewModel中建立 LiveData 例項 (ViewModel元件會在下期講到,將LiveData儲存至viewModel中,是為了符合MVVM架構思想,V層僅負責展示,而VM層負責資料邏輯)

public class ViewModelTest extends AndroidViewModel {
	public final  MutableLiveData<String> name = new MutableLiveData<>();
	...
}

step3: 在Activity或Fragment中對LiveData進行新增觀察者進行監聽

public class ActivityTest extends AppCompatActivity {
	...
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        viewModel.name.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String name) {
                // dosomething...
            }
        });
    }
}

我們可以根據LiveData值的變化來做對應的事情,且不用擔心生命週期越界的問題。

LiveData核心方法

方法名 作用
observe(LifecycleOwner owner,Observer observer) 註冊和宿主生命週期關聯的觀察者
observeForever(Observer observer) 註冊觀察者,不會反註冊,需自行維護
setValue(T data) 傳送資料,沒有活躍的觀察者時不分發。只能在主執行緒。
postValue(T data) 和setValue一樣。不受執行緒環境限制,
onActive 當且僅當有一個活躍的觀察者時會觸發
inActive 不存在活躍的觀察者時會觸發

LiveData的衍生類及功能

  • MutableLiveData

    該類十分簡單,主要開放了LiveData的發訊息介面

    public class MutableLiveData<T> extends LiveData<T> {
          @Override
          public void postValue(T value) {
            super.postValue(value);
        }
    
        @Override
        public void setValue(T value) {
            super.setValue(value);
        }
    }
    

    設計初衷:考慮單一開閉原則,LiveData只能接受訊息,避免拿到LiveData物件既能發訊息也能收訊息的混亂使用。

  • MediatorLiveData

    合併多個LiveData, 即一對多統一觀察,一個經典的場景是:在向伺服器請求資料時,優先展示本地資料庫的資料,然後由請求的響應決定是否要更新資料庫,如下圖所示:

    img

    // ResultType: Type for the Resource data.
    // RequestType: Type for the API response.
    public abstract class NetworkBoundResource<ResultType, RequestType> {
        // MediatorLiveData 資料組合者
        private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
        private Executor executor;
        @MainThread
        protected NetworkBoundResource(Executor mExecutor) {
            this.executor = mExecutor;
            // 首先初始化一個Loading的status 空result
            result.setValue(Resource.loading(null));
            // 然後從資料庫中獲取持久化資料
            LiveData<ResultType> dbSource = loadFromDb();
            // 資料組合者監聽資料庫中的資料
            result.addSource(dbSource, data -> {
                // dbSource第一次回撥,用來判斷資料有效期,此時取消監聽
                result.removeSource(dbSource);
                // 業務自行定義是否需要fetch最新的資料
                if (shouldFetch(data)) {
                    fetchFromNetwork(dbSource);
                } else {
                    // 資料有效,重新觀察一次,觀察者會立馬收到一次回撥(LiveData粘性事件機制)
                    result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
                }
            });
        }
        
        private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
            LiveData<ApiResponse<RequestType>> apiResponse = createCall();
            // 這裡資料雖無效,但是可以先給UI展示
            result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
            result.addSource(apiResponse, response -> {
                result.removeSource(apiResponse);
                result.removeSource(dbSource);
    
                if (response != null) {
                    if (response.isSuccessful()) {
                        executor.execute(() -> {
                            saveCallResult(processResponse(response));
                            executor.execute(() ->
                                 // 這裡我們拿到的最新的資料需要主動通知監聽者,以拿到從服務端拿到的最新資料
                                    result.addSource(loadFromDb(),
                                            newData -> setValue(Resource.success(newData)))
                            );
                        });
                    } else {
                        onFetchFailed();
                        result.addSource(dbSource,
                                newData -> setValue(Resource.error(response.errorMessage, newData)));
                    }
                } else {
                    result.addSource(dbSource,
                            newData -> setValue(Resource.error("Request failed, server didn't 		response", newData)));
                }
            });
        }
    }
    

    上面的例子MediatorLiveData同時監聽了資料庫中的LiveData和服務端的LiveData

  • Transformations

    這是一個資料轉化工具類,共兩個主要方法:

    1. 靜態轉化 -- map(@NonNull LiveData source, @NonNull final Function<X, Y> mapFunction)

      MutableLiveData<Integer> data = new MutableLiveData<>();
      
      //資料轉換
      LiveData<String> transformData = Transformations.map(data, input ->   String.valueOf(input));
      //使用轉換後生成的transformData去觀察資料
      transformData.observe( this, output -> {
      
      });
      
      //使用原始的livedata傳送資料
      data.setValue(10);
      
    2. 動態轉化 -- LiveData switchMap(
      @NonNull LiveData source,
      @NonNull final Function<X, LiveData> switchMapFunction)

     MutableLiveData userIdLiveData = ...;
     // 使用者資料和使用者id緊密相關,當我們改變userId的liveData的同時還會主動通知userLiveData更新
    LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
         repository.getUserById(id));
    
     void setUserId(String userId) {
          this.userIdLiveData.setValue(userId);
     }
    
    // 不要像下面這麼做
    private LiveData getUserLiveData(String userId) {
        // DON'T DO THIS
        return repository.getUserById(userId);
    }
    
    

四. LiveData的實現機制

livedata2

LiveData註冊觀察者觸發訊息分發流程:

  1. observe 註冊時,可以主動跟宿主生命週期繫結,不用反註冊:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        //1. 首先來個斷言,這個方法只能在主執行緒呼叫,observeForever也是。
        assertMainThread("observe");
        //2.其次把註冊進來的observer包裝成 一個具有生命周邊邊界的觀察者
        //它能監聽宿主被銷燬的事件,從而主動的把自己反註冊,避免記憶體洩漏
        //此時觀察者是否處於活躍狀態就等於宿主是否可見
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        //3.接著會判斷該觀察是否已經註冊過了,如果是則拋異常,所以要注意,不允許重複註冊
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        //4.這一步才是關鍵
        //利用Lifecycle,把觀察者註冊到進去,才能監聽到宿主生命週期狀態的變化,對不對?
        //根據Lifecycle文章中的分析,一旦一個新的觀察者被新增,Lifecycle也會同步它的狀態和宿主一致對不對?此時會觸發觀察者的onStateChanged方法
        owner.getLifecycle().addObserver(wrapper);
}
  1. LifecycleBoundObserver 監聽宿主的生命週期(這裡我們還記得之前在Lifecycle解析中提到addObserver時也會對observer進行包裝,這時一樣的),並且宿主不可見時不分發任何資料:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
        }

        @Override
        boolean shouldBeActive() {
        //使用observer方法註冊的觀察者都會被包裝成LifecycleBoundObserver
        //觀察者是否活躍就等於宿主 的狀態是否大於等於STARTED,
        //如果頁面當前不可見,你傳送了一條訊息,此時是不會被分發的,可以避免後臺任務搶佔資源,當頁面恢復可見才會分發。
        //注意:如果使用observerForever註冊的觀察者,
        //會被包裝成AlwaysActiveObserver,它的shouldBeActive一致返回true.即便在頁面不可見也能收到資料
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
                //在這裡如果監聽到宿主被銷燬了,則主動地把自己從livedata的觀察者中移除掉
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            //否則說明宿主的狀態發生了變化,此時會判斷宿主是否處於活躍狀態
            activeStateChanged(shouldBeActive());
        }
    }
  1. ObserverWrapper 狀態變更後,如果觀察者處於活躍狀態會觸發資料的分發流程:
abstract class ObserverWrapper{
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION//這裡就等於-1,沒有主動和LiveData的mVersion對齊,為黏性事件埋下了伏筆
        
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            //更改觀察者的狀態
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            //如果此時有且只有一個活躍的觀察者則觸發onActive
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                onActive();
            }
            //沒有任何一個活躍的觀察者則觸發onInactive
            //利用這個方法被觸發的時機,可以做很多事,比如懶載入,資源釋放等
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            //如果此時觀察者處於活躍狀態,下面就開始分發資料了
            //請注意,這裡傳遞了this = observer
            if (mActive) {
                dispatchingValue(this);
            }
        }
} 
  1. dispatchingValue 資料分發流程控制:
void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
            //如果傳遞的觀察者不為空,則把資料分發給他自己。這個流程是新註冊觀察者的時候會被觸發
                considerNotify(initiator);
                initiator = null;
            } else {
                //否則遍歷集合中所有已註冊的的觀察者,逐個呼叫considerNotify,分發資料
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> 		                       iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
  1. considerNotify 資料真正分發的地方,需要滿足三個套件:
private void considerNotify(ObserverWrapper observer) {
        //觀察者當前狀態不活躍不分發
        if (!observer.mActive) {
            return;
        }
        //觀察者所在宿主是否處於活躍狀態,否則不分發,並且更改觀察者的狀態為false
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //此處判斷觀察者接收訊息的次數是否大於等於 傳送訊息的次數
        //但是observer被建立之初verison=-1 
        //如果此時LiveData已經傳送過資料了。這裡就不滿足了,就出現黏性事件了,後註冊的觀察者收到了前面傳送的訊息。
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //每分發一次訊息,則把觀察者和LiveData的version對齊,防止重複傳送
        observer.mLastVersion = mVersion;
        //最後的資料傳遞
        observer.mObserver.onChanged((T) mData);
    }

普通訊息分發流程。即呼叫 postValue,setValue 才會觸發訊息的分發:

img

五. 總結

Android開發大部分主要的工作就是從伺服器獲取資料,將其轉為渲染為UI展示給使用者。本質上我們所做的邏輯都是“資料驅動”,所有的View都是資料的一種狀態,資料對映的過程中我們需要去考慮生命週期的限制--即資料的活躍性。LiveData結合Lifecycle幫我們規範了這個流程,完美詮釋了observer、lifecycle-aware、data holder 這個鐵三角,開發者在遵循這個開發流程的過程中,便完成了UI -> ViewModel -> Data的單項依賴。

相關文章