感知生命週期的資料 -- 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 LiveDatasource,
@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的單項依賴。