感知生命周期的數據 — LiveData

感知生命周期的數據 — 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, 即一對多統一觀察,一個經典的場景是:在向伺服器請求數據時,優先展示本地資料庫的數據,然後由請求的響應決定是否要更新資料庫,如下圖所示:

    // 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的實現機制

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 才會觸發消息的分發:

五. 總結

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

Tags:
Exit mobile version