LifeCycle修肠、ViewModel、LiveData 的組合使用
前言
在文章正文開始之前户盯,我們先引入幾個問題:
在開發(fā)中嵌施,我們時常需要和Activity饲化、Fragment 的生命周期打交道,它們本身也提供了各自一套生命周期的方法吗伤,我們只需要在相應的方法里面做正確的事情即可吃靠,但是我們有沒有想過,這樣對于 Activity 和 Fragment 來說足淆,功能過于復雜了巢块,那么如何將這一塊業(yè)務邏輯從我們的視圖之中遷移到業(yè)務層呢?
當我們開發(fā)的APP 需要適配鍵盤狀態(tài)缸浦、屏幕旋轉(zhuǎn)夕冲、語言變化等 configure 改變會導致 Activity銷毀重建氮兵,這個時候 Activity 持有的數(shù)據(jù)就會全部丟失裂逐,在Activity 重建之后我們又要去重新獲取數(shù)據(jù),這期間可能會對用戶體驗造成不良影響泣栈,傳統(tǒng)的解決辦法是在 onSaveInstanceState
方法中保存卜高,在 onCreate
中恢復,這樣不僅會讓 UI 層代碼臃腫南片,而且存儲的數(shù)據(jù)還必須是可序列化的掺涛。那么有沒有辦法將這部分邏輯從 UI 層中移除呢?
現(xiàn)在比較常用的設計架構(gòu)有 MVP 和 MVVM疼进,以 MVP 為例子薪缆,通用的做法是將業(yè)務邏輯抽成一個 Presenter 接口,將 UI 邏輯抽成一個 View 接口伞广。在 Activity 中特定條件觸發(fā)的時候調(diào)用特定的 Presenter 接口方法拣帽,在 PresenterImpl 實現(xiàn)類中某個特定的條件觸發(fā)的時候調(diào)用 View 接口特定的方法,這樣來完成業(yè)務邏輯層和 UI 層之間的交互嚼锄,這種架構(gòu)設計只適用PresenterImpl 和 View 接口之間每個方法存在一對一或者少量對一的調(diào)用關(guān)系的場景减拭。我們假設一下Activity 中視圖 A 的可見性依賴于 PresenterImpl 中的布爾值 isVisible屬性,而 PresenterImpl中存在多處更改 isVisible 屬性的情況区丑,那么在這種情況下我們就需要在多個地方多次調(diào)用 View 接口中 showA()和 hiddenA()的操作了拧粪,這樣的代碼會讓人不舒服,或者我們可以通過重寫 isVisible 的設置器沧侥,但是這種無法得到保證可霎,因為這要求程序員必須顯式調(diào)用 isVisible 的設置器。那么在這種情況下我們?nèi)绾尾拍軐崿F(xiàn)在一處代碼塊中處理 isVisible 屬性改變的調(diào)用宴杀,并且不依賴于程序員的自覺性呢啥纸?
當我們的 Activity、Fragment 不可見的時候婴氮,我們是不需要也不應該刷新 UI 的斯棒。比如開啟一個倒計時任務盾致,從10倒數(shù)到0,我們期望當我們的 Actvity荣暮、Fragment 不可見的 時候我們的計時器依然是有效的庭惜,但是不會去刷新 UI 的顯示,在倒數(shù)過程中如果我們讓我們的 Activity穗酥、Fragment 變得可見的時候护赊,視圖應該正確顯示現(xiàn)在倒數(shù)到的最新數(shù)字,通過在設置視圖顯示文本的時候手動判斷當前 Activity砾跃、Fragment 的生命周期可以達到我們的期望骏啰,但是手動是不可靠的,有沒有自動的方式實現(xiàn)我們的期望呢抽高?
解決的方法
解決問題一的方案是使用實現(xiàn)了 LifecycleOwner 的 Fragment判耕、Activity,比如 V4包下的 Fragment翘骂、FragmentActivity壁熄、AppCompatActivity 之類的,lifecycleOwner 與傳統(tǒng)的 Activity碳竟、fragment 不同之處是lifecycleOwner是一個接口草丧,而不是一個實現(xiàn)類,這意味著任何類都可以實現(xiàn)這個接口莹桅,從而監(jiān)聽 Activity昌执、Fragment 的生命周期變化
解決問題二的方案是使用 ViewModel 這個組件
解決問題三的方案是使用屬性觀察者,這里是使用 LiveData诈泼,其內(nèi)部實現(xiàn)了有新值則回調(diào)通知外界的邏輯
解決問題四的方案依然是使用 LiveData懂拾,其實也就是注冊監(jiān)聽屬性變化的時候傳入一個 LifecycleOwner,然后在回調(diào)數(shù)據(jù)改變方法之前判斷當前的 Activity厂汗、Fragment 是否可見委粉,如果不可見則不進行回調(diào)。在 Activity娶桦、Fragment 重新變得可見的時候會回調(diào) LifecycleOwner 相應的方法贾节,在這些方法里面讓 LiveData 去檢查視圖最后接收到的值是否為最后一個改變的值,如果不是衷畦,則同步值到視圖觀察者即可栗涂。
三個組件的概要介紹
LifecycleOwner 是一個接口,它擁有 Activity祈争、Fragment 所有生命周期的方法斤程,在實現(xiàn)了這個接口的 Activity、Fragment 對象中,每個生命周期對應的方法都會被回調(diào)忿墅,LifecycleOwner 之所以設計成接口扁藕,是為了其它對象可以使用到,這樣其它對象就無需要求 Activity疚脐、Fragment 在特定的生命周期中調(diào)用特定的方法亿柑,比如終結(jié)方法、暫停方法棍弄,而這些要求往往可能被程序員所忽略望薄,也使 Activity、Fragment 變得臃腫復雜呼畸。
ViewModel ViewModel通常在Activity痕支、Fragment 的生命周期內(nèi)被創(chuàng)建并且與之關(guān)聯(lián)在一起直到Activity 被 finish 掉。當 Activity蛮原、Fragment 被重新創(chuàng)建的時候卧须,通過 ViewModelProvides 可以重新獲得與之關(guān)聯(lián)的 ViewModel,從而確保數(shù)據(jù)不會丟失瞬痘。ViewModel 被存儲在 ViewModelStore 類中故慈,當與之關(guān)聯(lián)的 Activity板熊、Fragment 被 finish 之后最終會調(diào)用 ViewModel 的
onCleared
方法框全,到這里之后 ViewModel 才會被釋放掉LiveData 提供一個Observer接口給客戶端,客戶端實現(xiàn)這個接口干签,并將引用傳遞給它津辩,當我們調(diào)用 LiveData 的 setValue 或 postValue 的時候,LiveData 就會去回調(diào)這個接口的方法容劳,其實也就是LiveData 自己實現(xiàn)了觀察者模式喘沿。不過 LiveData 搞定了一個讓我們長期以來頭疼的事情,那就是當 Activity竭贩、Fragment 不可見的時候 UI 不要更新的問題蚜印,其內(nèi)部其實也是監(jiān)聽了 LifecycleOwner 的可見和不可見的方法,當 Activity留量、Fragment 不可見的時候就不回調(diào) Observer 的方法窄赋,當 Activity、Fragment 變成可見的時候楼熄,就對比最新的值和最后發(fā)出去的值是否是同一個忆绰,不同的話就發(fā)送最新的值,確保 Activity可岂、Fragment 可見的時候用戶看到的一定是最新的數(shù)據(jù)错敢。
上面的介紹就是這三個組件最核心的功能,雖然看上去沒有什么特別難的地方缕粹,但是確實讓我們的 Activity稚茅、Fragment 變得更加純粹纸淮,也讓我們的業(yè)務邏輯變得更加內(nèi)聚、不依賴客戶端程序員亚享。更少的依賴萎馅、更集中的代碼會讓我們的程序變得更加穩(wěn)固、易讀虹蒋,也讓我們的程序維護起來更加便利糜芳。
在詳細介紹這三個組件之前,我們先上源碼魄衅,這份源碼來自google 的 codelabs峭竣,github 地址
LifecycleOwner 簡單使用
其實這個LifecycleOwner真的沒什么好說的,因為實在太簡單了晃虫,我覺得通過上面的說明之后皆撩,我們應該是知道這個是什么東西,實現(xiàn)原理是什么哲银。
需要注意的就是只有支持包V4下面的 Fragment扛吞、FragmentActivity、AppCompatActivity等等幾個類是實現(xiàn)了 LifecycleOwner 接口的荆责。目前我們使用的 AS 只要是3.0.0以上的滥比,新建的項目中 MainActivity 應該都是繼承自AppCompatActivity的。
舉個例子做院,我們以定位為例子盲泛,可以參考BoundLocationManager。以往我們需要在 onResume 和 onPause 中分別啟動和暫停定位器键耕,現(xiàn)在我們可以將其從 Activity寺滚、Fragment 中移除,代碼如下
static class BoundLocationListener implements LifecycleObserver {
private final Context mContext;
private LocationManager mLocationManager;
private final LocationListener mListener;
public BoundLocationListener(LifecycleOwner lifecycleOwner,
LocationListener listener, Context context) {
mContext = context;
mListener = listener;
lifecycleOwner.getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void addLocationListener() {
Location lastLocation = //獲取用戶定位
if (lastLocation != null) {
mListener.onLocationChanged(lastLocation);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
if (mLocationManager == null) {
return;
}
mLocationManager.removeUpdates(mListener);
mLocationManager = null;
}
}
通過@OnLifecycleEvent標注我們可以標注我們的方法所對應的生命周期階段屈雄,當然我們也可以直接實現(xiàn)GenericLifecycleObserver接口村视,在void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
中判斷 Event 的值,不過通過標注會讓代碼更加純粹易讀酒奶。
ViewModel 簡單剖析
如上面的簡介蚁孔,ViewModel 中保存的數(shù)據(jù)可以躲避因 configure 發(fā)生改變而丟失的問題,我們先看下如何使用 ViewModel
如工程中的ChronometerViewModel
類所示讥蟆,只需要繼承自 ViewModel
類即可勒虾,代碼如下
public class ChronometerViewModel extends ViewModel {
@Nullable
private Long mStartTime;
@Nullable
public Long getStartTime() {
return mStartTime;
}
public void setStartTime(final long startTime) {
this.mStartTime = startTime;
}
}
當然沒有這么簡單,重要的是如何獲取這個 ViewModel 的對象瘸彤,我們在ChronoActivity2
的onCreate
中通過以下的代碼獲取 ViewModel 對象
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this).get(ChronometerViewModel.class);
至于是創(chuàng)建還是重新連接已經(jīng)存在的 ViewModel修然,就靠 ViewModelProviders 與其相關(guān)的系列工廠方法去判斷,我們無需關(guān)心
當 Activity 被銷毀重建之后,系統(tǒng)會補償我們一個savedInstanceState
對象愕宋,在 Activity 中通過這個對象系統(tǒng)可以恢復FragmentManager
玻靡,這個 FragmentManager
主要就是用來尋找HolderFragment
這個對象的,通過這個對象我們就可以找到之前Activity中贝、Fragment 關(guān)聯(lián)的 ViewModelStore囤捻,進而找到對應的 ViewModel,具體分析如下:
-
ViewModelProviders.of(this)
通過靜態(tài)工廠方法創(chuàng)建一個全新的對象ViewModelProvider對象邻寿,但是這個對象里面的mViewModelStore
屬性卻不一定是全新的蝎土,通過跟蹤代碼,我們可以發(fā)現(xiàn)在HolderFragment類的內(nèi)部類HolderFragmentManager中有如下屬性
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
即對于一個 Activity绣否、Fragment 的實例或者是銷毀重建后的新實例誊涯,都只會返回相同的一個HolderFragment實例,可以通過下面的方法證明
HolderFragment holderFragmentFor(FragmentActivity activity) {
//這行代碼獲取的的 fm 就是我們上文所說的系統(tǒng)補償給意外重建的 Activity 的參數(shù)中獲取的信息恢復出來的 fm
FragmentManager fm = activity.getSupportFragmentManager();
//找到了 holder蒜撮,我們就可以找到 ViewModelStore暴构,進而找到 ViewModel
HolderFragment holder = findHolderFragment(fm);
//獲取 map 中 activity key 對應的值
holder = mNotCommittedActivityHolders.get(activity);
if (holder != null) {
return holder;
}
holder = createHolderFragment(fm);
//將新創(chuàng)建的對象作為value,activity 作為 key 保存到 map 中
mNotCommittedActivityHolders.put(activity, holder);
return holder;
}
- 我們接下去看
ChronometerViewModel chronometerViewModel = provider.get(ChronometerViewModel.class);
這個方法的實現(xiàn)段磨,通過跟進代碼取逾,我們確實發(fā)現(xiàn)我們的 ViewModel 對象是在mViewModelStore單例屬性中獲取的,代碼如下
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//獲取 ViewModel 對象
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}
//如果不存在則創(chuàng)建一個新的 ViewModel苹支,并且緩存起來
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
通過上面的代碼砾隅,我們也就可以知道ViewModel 相關(guān)工廠類的內(nèi)部邏輯了,簡單說起來就是 viewModel 被緩存起來了沐序,只要被關(guān)聯(lián)的 key 沒有變琉用,那么就可以取到與之關(guān)聯(lián)的 ViewModel 對象堕绩,也就是我們前言所說的如何將這一塊判斷邏輯從 Activity策幼、Fragment 移除出去,通過 ViewModel 相關(guān)工廠方法就可以實現(xiàn)了奴紧。
通過上面的分析我們知道這一切的關(guān)鍵在于ViewModelProvider對象的一個單例字段 mViewModelStore
特姐,嚴格意義上來說,這個不是單例黍氮,他會隨著 Activity唐含、Fragment 的銷毀而銷毀,因此也不會造成內(nèi)存泄露沫浆。當然客戶端程序員不需要顯式地調(diào)用其終結(jié)方法捷枯,因為HolderFragmentManager內(nèi)部會監(jiān)聽 LifecycleOwner 的銷毀方法,在其中移除對 ViewModel 對象的引用专执。
需要注意的是淮捆,正如上段代碼所示ViewModel viewModel = mViewModelStore.get(key);
,mViewModelStore是通過一個字符串去獲取 ViewModel的,而這個 key 的值相對具體的 ViewModel 類是不會變化的攀痊,所以就算我們在 Activity 中多次調(diào)用
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this).get(ChronometerViewModel.class);
獲取到的對象都是同一個對象桐腌,所以假設我們的 Activity 中擁有多個 Fragment,每個 Fragment 都要獲取到相同的 ViewModel對象苟径,那么這個時候可以這樣寫
//傳遞 getActivity()案站,而不是 this
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(getActivity()).get(ChronometerViewModel.class);
因為對于不同的 Fragment,它們唯一的關(guān)聯(lián)就是屬于同一個 Activity棘街,如果傳遞的是 this蟆盐,那么獲取到的 ViewModel 就不是同一個對象了,這一點在源碼 Fragment_step5中有所體現(xiàn)遭殉。
注意舱禽,由于 ViewModel 的生命周期會比 Activity 長,所以一定不要在 ViewModel 中持有 Activity 的引用恩沽,以免造成內(nèi)存泄露
LiveData 簡單剖析
LiveData感覺也沒什么好說的誊稚,我們先看一下用法,在 Activity罗心、Fragment 中我們觀察 LiveData 的變化里伯,可以看ChronoActivity3代碼如下
private void subscribe() {
final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
@Override
public void onChanged(@Nullable final Long aLong) {
String newText = ChronoActivity3.this.getResources().getString(
R.string.seconds, aLong);
((TextView) findViewById(R.id.timer_textview)).setText(newText);
Log.d("ChronoActivity3", "Updating timer");
}
};
mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}
其實就是把 lifecycleOwner 和一個callBack 類傳遞進去,我們知道 lifecyclOwner 是為了讓 LiveData 內(nèi)部判斷是否要通知 Activity渤闷、Fragment 有新值變化了疾瓮。
接下去我們看看 LiveData 怎么創(chuàng)建和傳遞值的,可以LiveDataTimerViewModel飒箭,代碼如下
public class LiveDataTimerViewModel extends ViewModel {
private static final int ONE_SECOND = 1000;
private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();
private long mInitialTime;
public LiveDataTimerViewModel() {
mInitialTime = SystemClock.elapsedRealtime();
Timer timer = new Timer();
// Update the elapsed time every second.
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
// setValue() cannot be called from a background thread so post to main thread.
mElapsedTime.postValue(newValue);
}
}, ONE_SECOND, ONE_SECOND);
}
public LiveData<Long> getElapsedTime() {
return mElapsedTime;
}
}
其實就是創(chuàng)建一個 MutableLiveData 對象狼电,然后在需要傳遞值的時候調(diào)用 postValue 就可以了。具體的 LiveData 實現(xiàn)并不復雜弦蹂,感興趣的朋友可以自行查看肩碟。
這里我們就只說幾點使用 LiveData 需要注意的事情:
- LiveData 是一個抽象類,我們無法直接創(chuàng)建凸椿,只能使用它的實現(xiàn)類 MutableLiveData來創(chuàng)建對象削祈,但是我們對外聲明還是要用 LiveData,因為它的 postValue脑漫、setValue等是 protected 訪問權(quán)限的髓抑,這樣做之后外界就無法修改我們的值,不要給外部過大的權(quán)限优幸。
- setValue 需要在主線程中調(diào)用吨拍,如果在子線程中,那么需要使用 postValue网杆。
- liveData 可以在任意線程被訂閱羹饰,但觀察者接收到變化數(shù)據(jù)的 onChanged 方法一定是在主線程中被執(zhí)行握爷,因為這是由setValue 和 postValue 內(nèi)部調(diào)用 onChanged 方法所在的線程決定的,而這兩個方法的實現(xiàn)都是在主線程严里。
三個組件組合使用
通過上面小結(jié)的內(nèi)容之后新啼,我們知道 LifecyeleOwner 和 ViewModel、LiveData 之間都是有緊密聯(lián)系的刹碾,這一點無需我們通過代碼去組合燥撞,我們唯一能夠組合的就是 LiveData 和 ViewModel,兩者結(jié)合在一起之后其實也沒有什么生成什么化學反應迷帜,也就是同時擁有 ViewModel 和 LiveData 的特性物舒,所以也沒有什么好舉例的,只要了解了這三個組件的原理之后戏锹,想怎么組合使用都不是什么問題冠胯。
通過組合這三個組件之后我們現(xiàn)在的程序就變得職責分明了,具體有以下幾點體現(xiàn)
- Activity锦针、Fragment 更加純粹荠察,只需要用來管理UI 邏輯,不需要了解業(yè)務層與生命周期之間的關(guān)系
- 業(yè)務層邏輯更加內(nèi)聚奈搜,無需依賴 UI 去做生命周期相關(guān)階段的處理悉盆,避免出錯
- 無需在 onCreate 中判斷是否要創(chuàng)建新業(yè)務數(shù)據(jù)還是從緩存中獲取,讓 UI 層代碼更加簡介
- 當一個信號量可能在多個地方改變的時候馋吗,使用 LiveData 可以只在一處代碼塊中做邏輯處理焕盟,而不需要分散代碼或者重寫 setter 方法并強制程序員調(diào)用 setter 方法,當然當某個 UI的狀態(tài)依賴的是多個信號量或者依賴的信號量是由多個信號量組成的宏粤,那么這個時候用 RxJava2會讓代碼更加純粹脚翘、集中、易讀绍哎。
給我感覺最強大的是 LifecycleOwner来农,因為現(xiàn)在我們可以直接在我們的業(yè)務類中監(jiān)聽 Activity、Fragment 的變化蛇摸,而不需要向外接再三強調(diào)一定要在 onResume 中調(diào)用開始备图,在 onPause中調(diào)用暫停,在 onDestory 中終結(jié)赶袄,這樣的代碼只需要在業(yè)務類中一次實現(xiàn),就可以處處使用抠藕。
至于說什么時候使用這三個組件饿肺,這里我簡單說一下我的看法,首先 LifecycleOwner 和 ViewModel 是一定要處處用到的盾似,具體原因上面也說了敬辣,雖然人家叫做 ViewModel雪标,但是對于我們選擇用 MVP 或者是 MVVM 并沒有什么沖突,因為ViewModel 的概念和這兩種架構(gòu)沒有沖突溉跃,所以剩下的就是 LiveData 了村刨,LiveData 的使用場景適合在一個信號量在多處改變的時候使用,當一個信號量只在一個地方改變撰茎,那么用不用 LiveData 其實差不不大嵌牺。所以我的定位是一個信號量在多處地方改變的時候