Android 官方架構元件(二)——LiveData
參考連結:
https://developer.android.google.cn/topic/libraries/architecture/livedata https://mp.weixin.qq.com/s/ir3DBkGt5mna3RDjTpRFOQ
LiveData是google釋出的lifecycle-aware components中的一個元件,除了能實現資料和View的繫結響應之外,它最大的特點就是具備生命週期感知功能
LiveData的優點
- 能夠確保資料和UI統一
LiveData採用了觀察者模式,當資料發生變化時,主動通知被觀察者 。
- 解決記憶體洩露問題
由於LiveData會在Activity/Fragment等具有生命週期的lifecycleOwner元件呼叫onDestory的時候自動解綁,所以解決了可能存在的記憶體洩漏問題。之前我們為了避免這個問題,一般有註冊繫結的地方都要解綁(即註冊跟解綁要成對出現),而LiveData利用生命週期感知功能解決了這一問題,可以實現只需關心註冊,而解綁會根據生命週期自動進行的功能。
- 當Activity停止時不會引起崩潰
當Activity元件處於inactive非活動狀態時,它不會收到LiveData資料變化的通知。
- 不需要手動處理生命週期的變化
觀察者並不需要手動處理生命週期變化對自身的邏輯的影響,只需要關心如何處理獲取到的資料。LiveData能夠感知Activity/Fragment等元件的生命週期變化,所以就完全不需要在程式碼中告訴LiveData元件的生命週期狀態,當資料發生變化時,只在生命週期處於active下通知觀察者,而在inactive下,不會通知觀察者。
- 確保總能獲取到最新的資料
什麼意思呢?第一種情況,當觀察者處於active活動狀態。LiveData基於觀察者模式,所以當資料發生變化,觀察者能夠馬上獲取到最新變化;第二種情況,當觀察者處於inactive非活動狀態。LiveData只能生命週期active下發送資料給觀察者。舉個例子,當Activity處於後臺(inactive)時,LiveData接收到了新的資料,但這時候LiveData並不會通知該Activity,但是當該Activity重新返回前臺(active)時會繼續接收到最新的資料。一句話概括,LiveData是粘性的。
- configuration changes時,不需要額外的處理來儲存資料
我們知道,當你把資料儲存在元件中時,當configuration change(比如語言、螢幕方向變化)時,元件會被recreate,然而系統並不能保證你的資料能夠被恢復的。當我們採用LiveData儲存資料時,因為資料和元件分離了。當元件被recreate,資料還是存在LiveData中,並不會被銷燬。
- 資源共享
通過繼承LiveData類,然後將該類定義成單例模式,在該類封裝監聽一些系統屬性變化,然後通知LiveData的觀察者。
LiveData的簡單使用
LiveData註冊的觀察者的兩種方式:
// 這個方法新增的observer會受到owner生命週期的影響,在owner處於active狀態時,有資料變化,會通知,
// 當owner處於inactive狀態,不會通知,並且當owner的生命週期狀態時DESTROYED時,自動removeObserver
public void observe (LifecycleOwner owner, Observer<? super T> observer)
// 這個方法新增的observer不存在生命週期概念,只要有資料變化,LiveData都會通知,並且不會自動remove
public void observeForever (Observer<? super T> observer)
複製程式碼
LiveData釋出修改的兩種方式:
// 這個方法必須在主執行緒呼叫
protected void setValue (T value)
// 這個方式主要用於在非主執行緒呼叫
protected void postValue (T value)
複製程式碼
LiveData 官方API文件: https://developer.android.google.cn/reference/androidx/lifecycle/LiveData
舉個例子:在Activity頁面有一TextView,需要展示使用者User的資訊,User 類定義:
public class User {
public String name;
public int sex;
public User(String name, int sex) {
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
", name='" + name + '\'' +
", sex='" + sex+ '\'' +
'}';
}
}複製程式碼
常規的做法:
// 獲取User的資料後
mTvUser.setText(user.toString());複製程式碼
這樣做的一個問題,如果獲取或者修改User的來源不止一處,那麼需要在多個地方更新TextView,並且如果在多處UI用到了User,那麼也需要在多處更新。
怎麼優化這個問題呢?使用 LiveData。很多時候,LiveData與ViewModel組合使用(ViewModel後續文章會分析),讓LiveData持有User 實體,作為一個被觀察者,當User改變時,通知所有使用User的觀察者自動change。
首先構建一個UserViewModel如下:
public class UserViewModel extends ViewModel {
//宣告userLiveData
private MutableLiveData<User> userLiveData;
//獲取userLiveData
public LiveData getUserLiveData() {
if(userLiveData == null) {
userLiveData = new MutableLiveData<>();
}
return userLiveData;
}
}複製程式碼
然後註冊觀察者:
public class TestActivity extends AppCompatActivity {
private UserViewModel mModel;
private TextView mUserTextView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
mModel = ViewModelProviders.of(this).get(UserViewModel.class);
//建立用來更新ui的觀察者,重寫onChange方法,該方法會在資料發生變化時
//通過LiveData呼叫觀察者的onChange對資料變化響應
final Observer<User> userObserver = new Observer<User>() {
@Override
public void onChanged(@Nullable User user) {
LogUtil.i(TAG, user.toString());
//當收到LiveData發來的新資料時,更新
mUserTextView.setText(user.toString());
}
};
//獲取監聽User資料的LiveData
LiveData userLiveData = mModel.getUserLiveData();
//註冊User資料觀察者
userLiveData.observe(this, userObserver);
}
}複製程式碼
資料來源傳送改變的時候:
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
User user = new User("zhang san", 1)
//呼叫LiveData的setValue方法通知觀察者資料變化
mModel.getUserLiveData().setValue(user);
}
});複製程式碼
這樣使用到 User 的地方,UI 會自動更新,日誌如下:
com.example.cimua I/TestActivity : User{name='zhang san', sex=1}複製程式碼
好了,LiveData的基本使用就是這麼簡單~~~
LiveData更多用法可以看官方文件 https://developer.android.google.cn/topic/libraries/architecture/livedata
LiveData基本使用步驟總結:
- 建立LiveData
- 建立觀察者Observer
- 呼叫LiveData的observe方法將LiveData以及Observer建立起釋出-訂閱關係
- 在適當的時機呼叫LiveData的setValue或者postValue釋出新資料通知觀察者
LiveData原始碼分析
LiveData是基於觀察者模式構建的,所以,我們分析LiveData的原始碼主要可以分成兩部分:
- 訂閱:即呼叫LiveData的observe方法註冊
- 釋出:即呼叫LiveData的setValue或者postValue方法釋出資料
LiveData的訂閱流程分析
現在我們先看看observe()方法,因為observeForever()方法的實現跟observe()是類似的,我們就不看了,這裡只看observe():
// 我們已經知道,LiveData能夠感知生命週期的變化。從傳入的第一個引數是LifecycleOwner型別,我們已經
// 可以知道,原來LiveData是基於Lifecycle架構的基礎上擴充套件的,我們在前一篇Lifecyle文章中已經分析過
// Lifecyle元件了,LiveData就是封裝了特定的LifecycleObserver並將其註冊到LifecycleOwner中,用以感知
// 生命週期
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
// 如果LifecycleOwner的生命週期狀態已經是DESTROYED,例如Activity已經destroy,
// 那麼就沒必要新增觀察者,直接返回就可以了
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
// 建立繼承了GenericLifecycleObserver的LifecycleBoundObserver,並且將這個LifecycleBoundObserver
// 存進觀察者集合mObservers中統一管理
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
// 同一個observer物件,只有對應的lifecycleOwner不一樣,才可以重新新增,否則直接拋異常
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
// 呼叫observe()這個方法新增的observer,只有Activity/Fragment等生命週期元件可見時
// 才會收到資料更新的通知,為了知道什麼時候Activity/Fragment是可見的,這裡需要註冊到
// Lifecycle中感知生命週期
// 也是因為這個,observe()比observeForever()多了一個引數lifecycleOwner
owner.getLifecycle().addObserver(wrapper);
}複製程式碼
接著我們在看看observe()方法中最重要的LifecycleBoundObserver:
private abstract class ObserverWrapper {
final Observer<T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
// LiveData.this.mActiveCount表示處於active狀態的observer的數量
// 當mActiveCount大於0時,LiveData處於active狀態
// 注意區分observer的active狀態和 LiveData 的active狀態
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
// inactive -> active
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
// mActiveCount在我們修改前等於1,也就是說,LiveData從active
// 狀態變到了inactive
onInactive();
}
//如果是active狀態,通知觀察者資料變化(dispatchingValue方法在下一節釋出中分析)
if (mActive) {
// observer從inactive到active,此時客戶拿到的資料可能不是最新的,這裡需要dispatch
// 關於他的實現,我們下一節再看
dispatchingValue(this);
}
}
}
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
@NonNull final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
super(observer);
mOwner = owner;
}
// 這個方法返回的是當前是否是active狀態
@Override
boolean shouldBeActive() {
// 只有當生命週期狀態是STARTED或者RESUMED時返回true,其他情況返回false
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
// 這個方法是由Lifecycle結構中的mLifecycleRegistry所呼叫,一旦LifecycleOwner的生命週期
// 發生變化,都會呼叫到onStateChanged這個方法進行生命週期轉換的通知
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// 上面我們說過,使用LiveData只需要關心註冊,不需要關心何時解綁,這裡就告訴我們答案:
// 當生命週期狀態為DESTROYED,會自動removeObserver實現解綁,不會導致記憶體洩露。
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// 一開始建立LifecycleBoundObserver例項的時候,mActive預設為false,
// 當註冊到Lifecycle後,Lifecycle會同步生命週期狀態給我們(也就是回撥本方法),
// 不熟悉lifecycle的讀者,可以看前一篇講述Lifecycle的文章
activeStateChanged(shouldBeActive());
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}複製程式碼
到這裡,LiveData的訂閱流程就基本分析完了。
LiveData的釋出流程分析
上面我們已經說了,LiveData釋出修改有setValue已經postValue兩種方式,其中setValue只能在主執行緒呼叫,postValue則沒有這個限制。
我們從setValue的分析入手釋出流程:
@MainThread
protected void setValue(T value) {
// 判斷當前呼叫執行緒是否是主執行緒,如果不是,直接拋IllegalStateException異常
assertMainThread("setValue");
// 每次更新value,都會使mVersion + 1,ObserverWrapper也有一個欄位,叫mLastVersion
// 通過比較這兩個欄位,可以避免重複通知觀察者,還可以用於實現LiveData的粘性事件特性(後面會說到)
mVersion++;
// 將這次資料儲存在LiveData的mData變數中,mData的值永遠是最新的值
mData = value;
// 釋出
dispatchingValue(null);
}複製程式碼
接著再看看dispatchingValue方法:
// 如果引數initiator為null的話,表示要將新資料釋出通知給所有observer
// 如果引數不為null的話,表示只通知給傳入的觀察者initiator
private void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
// 在observer的回撥裡面又觸發了資料的修改
// 設定mDispatchInvalidated為true後,可以讓下面的迴圈知道
// 資料被修改了,從而開始一輪新的迭代。
//
// 比方說,dispatchingValue -> observer.onChanged -> setValue -> dispatchingValue
// 這裡return的是後面那個dispatchingValue,然後在第一個
// dispatchingValue會重新遍歷所有的observer,並呼叫他們的onChanged。
//
// 如果想避免這種情況,可以在回撥裡面使用 postValue 來更新資料
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
// 呼叫 observer.onChanged()
considerNotify(initiator);
initiator = null;
} else {
// initiator不為空,遍歷mObservers集合,試圖通知所有觀察者
for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
// 某個客戶在回撥裡面更新了資料,break後,這個for迴圈會重新開始
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}複製程式碼
看過我那篇 lifecycle 原始碼分析的讀者應該對 dispatchingValue 處理迴圈呼叫的方式很熟悉了。以這裡為例,為了防止迴圈呼叫,我們在呼叫客戶程式碼前先置位一個標誌(mDispatchingValue),結束後再設為 false。如果在回撥裡面又觸發了這個方法,可以通過 mDispatchingValue 來檢測。
檢測到迴圈呼叫後,再設定第二個標誌(mDispatchInvalidated),然後返回。返回又會回到之前的呼叫,前一個呼叫通過檢查 mDispatchInvalidated,知道資料被修改,於是開始一輪新的迭代。
接著繼續看considerNotify方法:
private void considerNotify(ObserverWrapper observer) {
// 如果observer的狀態不是active,那麼不向該observer通知,直接返回
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
// 上面的原始碼分析,我們知道每一次setValue或者postValue的呼叫都會是mVersion自增1,
// mLastVersion的作用是為了與mVersion作比較,這個比較作用主要有兩點:
// 1.如果說mLastVersion >= mVersion,證明這個觀察者已經接受過本次釋出事件通知,不需要重複通知了,直接返回
// 2.實現粘性事件。比如有一個數據(LiveData)在A頁面setValue()之後,則該資料(LiveData)中的
// 全域性mVersion+1,也就標誌著資料版本改變,然後再從A頁面開啟B頁面,在B頁面中開始訂閱該LiveData,
// 由於剛訂閱的時候內部的資料版本都是從-1開始,此時內部的資料版本就和該LiveData全域性的資料
// 版本mVersion不一致,根據上面的原理圖,B頁面開啟的時候生命週期方法一執行,則會進行notify,
// 此時又同時滿足頁面是從不可見變為可見、資料版本不一致等條件,所以一進B頁面,B頁面的訂閱就會被響應一次
if (observer.mLastVersion >= mVersion) {
return;
}
// 設定mLastVersion = mVersion,以免重複通知觀察者
observer.mLastVersion = mVersion;
// 這裡就最終呼叫了我們一開始通過observe()方法傳入的observer中的onChange()方法
// 即新資料被髮布給了observer
observer.mObserver.onChanged((T) mData);
}複製程式碼
setValue的分析就到此為止了~
在setValue的基礎上,分析postValue就比較簡單了:
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
//noinspection unchecked
setValue((T) newValue);
}
};
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
// 通過handler將mPostValueRunnable分發到主執行緒執行,其實最終執行的也是setValue方法
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}複製程式碼
LiveData的原始碼就分析這麼多了,更詳細就需要到android原始碼裡面找了。
總結
將LiveData的原始碼抽象為一張流程圖來展示,下面的其他問題都可以在這張圖中找到答案:
- LiveData為什麼能夠感知生命週期?
lifecycle-aware compents的核心就是生命週期感知,要明白LiveData為什麼能感知生命週期,就要知道Google的這套生命週期感知背後的原理是什麼,下面是我基於之前lifeycycle這套東西剛出來時候對原始碼進行的一個分析總結(現在的最新程式碼可能和之前有點出入,但是原理上基本是一樣的):
首先Activity/Fragment是LifecycleOwner(26.1.0以上的support包中Activity已經預設實現了LifecycleOwner介面),內部都會有一個LifecycleRegistry存放生命週期State、Event等。而真正核心的操作是,每個Activity/Fragment在啟動時都會自動新增進來一個Headless Fragment(無介面的Fragment),由於新增進來的Fragment與Activity的生命週期是同步的,所以當Activity執行相應生命週期方法的時候,同步的也會執行Headless Fragment的生命週期方法,由於這個這個Headless Fragment對我們開發者來說是隱藏的,它會在執行自己生命週期方法的時候更新Activity的LifecycleRegistry裡的生命週期State、Event, 並且notifyStateChanged來通知監聽Activity生命週期的觀察者。這樣就到達了生命週期感知的功能,所以其實是一個隱藏的Headless Fragment來實現了監聽者能感知到Activity的生命週期。
基於這套原理,只要LiveData註冊了對Activity/Fragment的生命週期監聽,也就擁有了感知生命週期的能力。
- LiveData為什麼可以避免記憶體洩露?
由於LiveData會在Activity/Fragment等具有生命週期的lifecycleOwner onDestory的時候自動解綁,所以解決了可能存在的記憶體洩漏問題。之前我們為了避免這個問題,一般有註冊繫結的地方都要解綁,而LiveData利用生命週期感知功能解決了這一問題。
我們可以知道,當Activity/Fragment的生命週期發生改變時,LiveData中的監聽都會被回撥,所以避免記憶體洩漏就變得十分簡單,可以看上圖,當LiveData監聽到Activity onDestory時則removeObserve,使自己與觀察者自動解綁。這樣就避免了記憶體洩漏。
- LiveData為什麼可以解決空指標異常?
我們通常在一個非同步任務回來後需要更新View,而此時頁面可能已經被回收,導致經常會出現View空異常,而LiveData由於具備生命週期感知功能,在介面可見的時候才會進行響應,如介面更新等,如果在介面不可見的時候發起notify,會等到介面可見的時候才進行響應更新。所以就很好的解決了空異常的問題。
- LiveData為什麼是粘性的?
所謂粘性,也就是說訊息在訂閱之前釋出了,訂閱之後依然可以接受到這個訊息,像EventBus實現粘性的原理是,把釋出的粘性事件暫時存在全域性的集合裡,之後當發生訂閱的那一刻,遍歷集合,將事件拿出來執行。
而LiveData之所以本身就是粘性的,結合上面的原理圖我們來分析一下,比如有一個數據(LiveData)在A頁面setValue()之後,則該資料(LiveData)中的全域性mVersion+1,也就標誌著資料版本改變,然後再從A頁面開啟B頁面,在B頁面中開始訂閱該LiveData,由於剛訂閱的時候內部的資料版本都是從-1開始,此時內部的資料版本就和該LiveData全域性的資料版本mVersion不一致,根據上面的原理圖,B頁面開啟的時候生命週期方法一執行,則會進行notify,此時又同時滿足頁面是從不可見變為可見、資料版本不一致等條件,所以一進B頁面,B頁面的訂閱就會被響應一次。這就是所謂的粘性,A頁面在發訊息的時候B頁面是還沒建立還沒訂閱該資料的,但是一進入B頁面一訂閱,之前在A中發的訊息就會被響應。