系列文章
【背上Jetpack】Jetpack 主要組件的依賴及傳遞關(guān)系
【背上Jetpack】AdroidX下使用Activity和Fragment的變化
【背上Jetpack之Fragment】你真的會用Fragment嗎防泵?Fragment常見問題以及androidx下Fragment的使用新姿勢
【背上Jetpack之Fragment】從源碼角度看 Fragment 生命周期 AndroidX Fragment1.2.2源碼分析
【背上Jetpack之OnBackPressedDispatcher】Fragment 返回棧預(yù)備篇
【背上Jetpack之Fragment】從源碼的角度看Fragment 返回棧 附多返回棧demo
【背上Jetpack】絕不丟失的狀態(tài) androidx SaveState ViewModel-SaveState 分析
前言
Android 開發(fā)時友雳,我們使用 activity 和 fragment 作為視圖控制器虑啤, 可能還會使用有一些類可以存儲和提供 UI 數(shù)據(jù)(例如MVP中的
Presenter
)
但是 當(dāng)配置更改時(如旋轉(zhuǎn)屏幕)洗鸵,activity 會重建,但對于 UI 數(shù)據(jù)的持有者呢椿息?
- 開發(fā)者需要重新保存相關(guān)的信息并傳遞給重建的 activity 涛菠,否則開發(fā)者必須再次獲取數(shù)據(jù)(通過網(wǎng)絡(luò)請求或本地數(shù)據(jù)庫)
- 由于 UI 數(shù)據(jù)的持有者的生命周期可能比 activity 長铺然,因此開發(fā)者還需要避免出現(xiàn)內(nèi)存泄漏的問題
如何解決上述問題?ViewModel
本文重點介紹 ViewModel 的職責(zé)(what)以及重點功能的實現(xiàn)原理(how)且轨,即使您不使用 Jetpack MVVM
架構(gòu)浮声,也要了解一下 ViewModel
ViewModel 的原理部分要求您了解 activity 的啟動流程,這部分內(nèi)容網(wǎng)上文章很多旋奢,本文不再贅述
ViewModel 的職責(zé)
我先上個 視頻 泳挥,這個小姐姐表述的比文字更形象
ViewModel
主要用于存儲 UI 數(shù)據(jù)以及生命周期感知的數(shù)據(jù)
ViewModel
的生命周期 ,圖片來自 官方文檔
作為數(shù)據(jù)持有者
ViewModel
能夠?qū)崟r進(jìn)行配置更改黄绩。 這意味著即使在手機旋轉(zhuǎn)后銷毀并重新創(chuàng)建 activity 之后羡洁,您仍然擁有相同的 ViewModel
和相同的數(shù)據(jù)。 因此:
- 您無需擔(dān)心 UI 數(shù)據(jù)持有者的生命周期爽丹。
ViewModel
將由工廠自動創(chuàng)建筑煮,您無需自行創(chuàng)建和銷毀 - 數(shù)據(jù)將始終更新,旋轉(zhuǎn)手機后粤蝎,您將獲得與以前相同的數(shù)據(jù)真仲。 因此,您無需手動將數(shù)據(jù)傳遞給新的 activity 實例或再次調(diào)用網(wǎng)絡(luò)或數(shù)據(jù)庫來獲取數(shù)據(jù)初澎。
Fragment 間共享數(shù)據(jù)
一個 activity 中的兩個或更多 fragment 需要相互通信是很常見的秸应。例如您有一個片段,用戶在其中從列表中選擇一個 item碑宴,另一個片段顯示了所選 item 的內(nèi)容软啼。 傳統(tǒng)做法兩個 fragment 都需要定義一些接口,并且宿主 activity 必須將兩者綁定在一起延柠。 此外祸挪,兩個 fragment 都必須處理另一個 fragment 尚未創(chuàng)建或不可見的情況。
可以通過使用 ViewModel
對象解決此問題贞间。 這些 fragment 可以使用 activity 范圍內(nèi)共享一個 ViewModel
來處理此通信贿条,如以下示例代碼所示:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), { item ->
// Update the UI.
});
}
}
由于 兩個 fragment 使用的都是 activity 范圍的
ViewModel
(ViewModelProvider
構(gòu)造器傳入的 activity ),因此它們獲得了相同的 ViewModel 實例增热,自然其持有的數(shù)據(jù)也是相同的整以,這也 保證了數(shù)據(jù)的一致性
這種方法具有以下優(yōu)點:
宿主 activity 無需執(zhí)行任何操作,也無需了解此通信峻仇。
除
SharedViewModel
外公黑,fragment 不需要彼此了解。 如果其中一個 fragment 消失了,則另一個繼續(xù)照常工作帆调。每個 fragment 都有其自己的生命周期奠骄,并且不受另一個 fragment 的生命周期影響。 如果一個 fragment 替換了另一個 fragment番刊,則 UI 可以繼續(xù)正常工作而不會出現(xiàn)任何問題。
代替 Loader
CursorLoader
這樣的 Loader 類經(jīng)常用于使應(yīng)用程序 UI 中的數(shù)據(jù)與數(shù)據(jù)庫保持同步影锈。您可以使用 ViewModel
和其他一些類來替換 Loader芹务。 使用 ViewModel
可將視圖控制器與數(shù)據(jù)加載操作分開,這意味著您在類之間的強引用較少鸭廷。
在使用 Loader 的一種常見方法中枣抱,應(yīng)用程序可能會使用 CursorLoader
來觀察數(shù)據(jù)庫的內(nèi)容。 當(dāng)數(shù)據(jù)庫中的值更改時辆床,加載程序會自動觸發(fā)數(shù)據(jù)的重新加載并更新 UI
圖片來自 官方文檔
ViewModel
與 Room
和 LiveData
一起使用以替換 Loader佳晶。 ViewModel
確保數(shù)據(jù)在設(shè)備配置更改后仍然存在。 當(dāng)數(shù)據(jù)庫發(fā)生更改時讼载,Room
會通知 LiveData
轿秧,然后 LiveData
會使用修改后的數(shù)據(jù)更新 UI
圖片來自 官方文檔
總結(jié)
- ViewModel 可作為 UI 數(shù)據(jù)的持有者,在 activity/fragment 重建時 ViewModel 中的數(shù)據(jù)不受影響咨堤,同時可以避免內(nèi)存泄漏
- 可以通過 ViewModel 來進(jìn)行 activity 和 fragment 菇篡,fragment 和 fragment 之間的通信,無需關(guān)心通信的對方是否存在一喘,使用 application 范圍的 ViewModel 可以進(jìn)行全局通信
- 可以代替 Loader
ViewModel 源碼分析
分析源碼時我們可以不計較細(xì)枝末節(jié)驱还,只分析主要的邏輯即可。因此我們來思考幾個問題凸克,并從源碼中尋找答案
如何做到 activity 重建后
ViewModel
仍然存在议蟆?如何做到 fragment 重建后
ViewModel
仍然存在?如何控制作用域萎战?(即保證相同作用域獲取的
ViewModel
實例相同)如何避免內(nèi)存泄漏咐容?
維持我們一貫的風(fēng)格,我們先來大膽地猜一猜
對于問題1 :activity 有著 saveInstanceState
機制撞鹉,因此可能通過該機制來處理(事實證明不是)
對于問題2:可能 fragment 通過 宿主 activity 或 父 fragment 的幫助來確保 ViewModel
實例在重建后仍然存在
對于問題3:實現(xiàn)一個類似單例的效果疟丙,相同作用域獲取的對象是相同的
對于問題4:避免 ViewModel
持有 view 或 context 的引用
首先我們要先了解一下 ViewModel
的結(jié)構(gòu)
ViewModel
:抽象類,主要有 clear 方法鸟雏,它是 final 級享郊,不可修改,clear 方法中包含 onClear 鉤子孝鹊,開發(fā)者可重寫 onClear 方法來自定義數(shù)據(jù)的清空ViewModelStore
:內(nèi)部維護(hù)一個 HashMap 以管理ViewModel
ViewModelStoreOwner
:接口炊琉,ViewModelStore
的作用域,實現(xiàn)類為ComponentActivity
和Fragment
,此外還有FragmentActivity.HostCallbacks
ViewModelProvider
:用于創(chuàng)建ViewModel
苔咪,其構(gòu)造方法有兩個參數(shù)锰悼,第一個參數(shù)傳入ViewModelStoreOwner
,確定了ViewModelStore
的作用域团赏,第二個參數(shù)為ViewModelProvider.Factory
箕般,用于初始化ViewModel
對象,默認(rèn)為getDefaultViewModelProviderFactory()
方法獲取的 factory
簡單來說 ViewModelStoreOwner 持有 ViewModelStore 持有 ViewModel
1. 如何做到 activity 重建后 ViewModel 仍然存在舔清?
在 【背上Jetpack】絕不丟失的狀態(tài) androidx SaveState ViewModel-SaveState 分析 中我們提到了 androidx.core.app.ComponentActivity 的引入并探討了其作為中間層的作用
我們已經(jīng)講過 SavedStateRegistryOwner
和 OnBackPressedDispatcherOwner
這兩種角色丝里,而今天我們來聊一下
ViewModelStoreOwner
和 HasDefaultViewModelProviderFactory
。其中前者代表著 ViewModelStore
的作用域体谒,后者來標(biāo)記 ViewModelStoreOwner
擁有默認(rèn)的 ViewModelProvider.Factory
那么 ViewModel
的邏輯肯定就在該類了
ComponentActivity
實現(xiàn)了 ViewModelStoreOwner
接口杯聚,意味著需要重寫 getViewModelStore()
方法,該方法為 ComponentActivity
的 mViewModelStore
變量賦值抒痒。activity 重建后 ViewModel 仍然存在幌绍,只要保證 activity 重建后 mViewModelStore 變量值不變即可
順著這個思路,我們來看一下 getViewModelStore()
的實現(xiàn)
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
//核心故响,在該位置重置 mViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
即 mViewModelStore
的值由 getLastNonConfigurationInstance()
返回的 NonConfigurationInstances
對象中的 viewModelStore
賦值傀广,如果此時還為空才去 new ViewModelStore 對象。因此我們只需找到
getLastNonConfigurationInstance
中的 NonConfigurationInstances
在哪里保存的即可
getLastNonConfigurationInstance
為平臺 activity 中的方法被去,返回 mLastNonConfigurationInstances.activity
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
那么我們看一下 mLastNonConfigurationInstances
的賦值位置
//省略其他參數(shù)
final void attach(NonConfigurationInstances lastNonConfigurationInstances){
mLastNonConfigurationInstances = lastNonConfigurationInstances;
//...
}
了解過 activity 的啟動流程的小伙伴肯定知道主儡,這個 attach 方法是 ActivityThread
中的 performLaunchActivity
調(diào)用的
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//省略其他參數(shù)
activity.attach(r.lastNonConfigurationInstances);
r.lastNonConfigurationInstances = null;
//...
}
深入追蹤源碼我們整理一下調(diào)用流程
由于 ActivityThread
中的 ActivityClientRecord
不受 activity 重建的影響,所以 activity 重建時 mLastNonConfigurationInstances
能夠得到上一次的值惨缆,使得 ViewModelStore
值不變 糜值,問題1就解決了
2. 如何做到 fragment 重建后 ViewModel 仍然存在?
對于問題2坯墨,有了上面的思路我們可以認(rèn)定 fragment 重建后其內(nèi)部的 getViewModelStore()
方法返回的對象是相同的寂汇。
// Fragment.java
public ViewModelStore getViewModelStore() {
return mFragmentManager.getViewModelStore(this);
}
可以看到 getViewModelStore()
內(nèi)部調(diào)用的是 mFragmentManager
(普通fragment 對應(yīng) activity 中的 FragmentManager
,子 fragment 則對應(yīng)父 fragment 的 childFragmentManager
)的 getViewModelStore()
方法
// FragmentManager.java
private FragmentManagerViewModel mNonConfig;
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
而 FragmentManager 中的 getViewModelStore 使用的是 mNonConfig 捣染,mNonConfig 竟然是個 ViewModel骄瓣!
// FragmentManagerViewModel.java
private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
FragmentManagerViewModel
管理著內(nèi)部的 ViewModelStore
和 child 的 FragmentManagerViewModel
。因此保證 mNonConfig 值不變即能確保 fragment 中的 getViewModelStore()
不變耍攘。那么看看 mNonConfig 賦值的位置
// FragmentManager.java
void attachController(@NonNull FragmentHostCallback<?> host, @NonNull FragmentContainer container, @Nullable final Fragment parent) {
//...
if (parent != null) {
// 嵌套 fragment 的情況榕栏,有父 fragment
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
// host 是 FragmentActivity.HostCallbacks
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}
// FragmentManagerViewModel.java
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}
我們先看 fragment 的直接宿主是 activity (即沒有嵌套)的情況,mNonConfig 由FragmentManagerViewModel.getInstance(viewModelStore)
賦值蕾各,而 getInstance 中使用的是 ViewModelProvider
獲取 ViewModel
扒磁,根據(jù)我們上面的分析,只要保證作用域(viewModelStore)相同式曲,即可獲取相同的 ViewModel
實例妨托,因此我們需要看一下 host 的 getViewModelStore 方法缸榛。經(jīng)過一番尋找,host 是 FragmentActivity.HostCallbacks
// FragmentActivity.java 內(nèi)部類
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements ViewModelStoreOwner, OnBackPressedDispatcherOwner {
public ViewModelStore getViewModelStore() {
// 宿主 activity 的 getViewModelStore
return FragmentActivity.this.getViewModelStore();
}
}
host 的 getViewModelStore 方法返回的是宿主 activity 的 getViewModelStore()
兰伤,而 activity 重建后其內(nèi)部的 mViewModelStore
是不變的内颗,因此即使 activity 重建,其內(nèi)部的 FragmentManager 對象變化敦腔,但 FragmentManager 內(nèi)部的 FragmentManagerViewModel 的實例(mNonConfig
)不變均澳,mNonConfig.getViewModelStore 不變,fragment 的 getViewModelStore()
亦不變符衔,fragment 重建后其內(nèi)部的 ViewModel
仍然存在
對于嵌套 fragment 负懦,mNonConfig 通過 parent.mFragmentManager.getChildNonConfig(parent) 獲取
// FragmentManager.java
private FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
return mNonConfig.getChildNonConfig(f);
}
上文提到 FragmentManagerViewModel
管理著 mChildNonConfigs Map,因此子 fragment 重置后其內(nèi)部的 mNonConfig 對象也是相同的
至此問題 2 就解決了
3. 如何控制作用域柏腻?
對于問題3,我們知道 ViewModelStoreOwner
代表著作用域系吭,其內(nèi)部唯一的方法返回 ViewModelStore
對象五嫂,也即不同的作用域?qū)?yīng)不同的 ViewModelStore
,而 ViewModelStore
內(nèi)部維護(hù)著 ViewModel
的 HashMap 肯尺,因此只要保證相同作用域的 ViewModelStore
對象相同就能保證相同作用域獲取到相同的 ViewModel
對象沃缘,而問題1我們已經(jīng)解釋了重建時如何保證 ViewModelStore
對象不變。
因此問題3也解決了则吟。
4. 如何避免內(nèi)存泄漏槐臀?
對于問題4,由于 ViewModel
的設(shè)計氓仲,使得 activity/fragment 依賴它水慨,而 ViewModel
不依賴視圖控制器。因此只要不讓 ViewModel
持有 context 或 view 的引用敬扛,就不會造成內(nèi)存泄漏
總結(jié)
簡單的總結(jié)一下:
activity 重建后 mViewModelStore 通過 ActivityThread 的一系列方法能夠保持不變晰洒,從而當(dāng) activity 重建時 ViewModel 中的數(shù)據(jù)不受影響
通過宿主 activity 范圍內(nèi)共享的 FragmentManagerViewModel 來存儲 fragment 的 ViewModelStore 和子fragment 的 FragmentManagerViewModel ,而 activity 重建后 FragmentManagerViewModel 中的數(shù)據(jù)不受影響啥箭,因此 fragment 內(nèi)部的 ViewModel 的數(shù)據(jù)也不受影響
通過同一 ViewModelStoreOwner 獲取的 ViewModelStore 相同谍珊,從而保證同一作用域通過 ViewModelProvider 獲取的ViewModel 對象是相同的
通過單向依賴(視圖控制器持有 ViewModel )來解決內(nèi)存泄漏的問題
ViewModel 和 onSaveInstanceState
ViewModel
和 onSaveInstanceState
的功能有些類似,但它們也有很多差異
從存儲位置上來說急侥,ViewModel
是在內(nèi)存中砌滞,因此其讀寫速度更快,但當(dāng)進(jìn)程被系統(tǒng)殺死后坏怪,ViewModel
中的數(shù)據(jù)也不存在了贝润。從數(shù)據(jù)存儲的類型上來看,ViewModel
適合存儲相對較重的數(shù)據(jù)陕悬,例如網(wǎng)絡(luò)請求到的 list 數(shù)據(jù)题暖,而 onSaveInstanceState
適合存儲輕量可序列化的數(shù)據(jù)
那么我們該如何使用呢?可以使用 viewmodel-savedstate
庫,詳情參考 【背上Jetpack】絕不丟失的狀態(tài) androidx SaveState ViewModel-SaveState 分析
關(guān)于我
我是 Fly_with24