感知生命周期的數據 — 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...
}
}
};
我再拿上面的例子說一下這種問題:
- 需要自行判斷宿主活躍狀態,防止生命周期越界。
- 如果Activity不可見,此時不更新UI,那麼就需要複寫onResume方法去更新UI,手工管理生命周期,增加了程式碼的複雜性。
二. 為什麼要使用LiveData?
上面的例子已經很好地詮釋了LiveData的強大,當然不僅限於此,它的優勢如下:
-
確保介面符合數據狀態
LiveData 遵循觀察者模式。當生命周期狀態發生變化時,LiveData 會通知
Observer
對象。觀察者可以在onChanged事件時更新介面,而不是在每次數據發生更改時立即更新介面。 -
不會發生記憶體泄漏
觀察者會綁定到
Lifecycle
對象,並在其關聯的生命周期遭到銷毀後進行自我清理。 -
不會因 Activity 停止而導致崩潰
如果觀察者的生命周期處於非活躍狀態(如返回棧中的 Activity),則它不會接收任何 LiveData 事件。
-
不再需要手動處理生命周期
介面組件只是觀察相關數據,不會停止或恢復觀察。LiveData 將自動管理所有這些操作,因為它在觀察時可以感知相關的生命周期狀態變化。
-
數據始終保持最新狀態
如果生命周期變為非活躍狀態,它會在再次變為活躍狀態時接收最新的數據。例如,曾經在後台的 Activity 會在返回前台後立即接收最新的數據。
-
適當的配置更改
如果由於配置更改(如設備旋轉)而重新創建了 Activity 或 Fragment,它會立即接收最新的可用數據。
-
共享資源
可以使用單一實例模式擴展
LiveData
對象以封裝系統服務,以便在應用中共享它們。LiveData
對象連接到系統服務一次,然後需要相應資源的任何觀察者只需觀察LiveData
對象 -
支援黏性事件的分發
即先發送一條數據,後註冊一個觀察者,默認是能夠收到之前發送的那條數據的
三. 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
這是一個數據轉化工具類,共兩個主要方法:
-
靜態轉化 — 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);
-
動態轉化 — 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註冊觀察者觸發消息分發流程:
- 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);
}
- 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());
}
}
- 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);
}
}
}
- 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;
}
- 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的單項依賴。