前言
Jetpack AAC 系列文章:
Jetpack Lifecycle 該怎么看卖氨?還肝否?
Jetpack LiveData 是時候了解一下了
Jetpack ViewModel 抽絲剝繭
前兩篇分析了Lifecycle和LiveData镊绪,本篇將著重分析ViewModel及其三者的關聯(lián)。
通過本篇,你將了解到:
1仅颇、為什么需要ViewModel ?
2碘举、ViewModel 的使用方式
3忘瓦、ViewModel 原理掘地三尺
4、Lifecycle/LiveData/ViewModel 關聯(lián)
1引颈、為什么需要ViewModel 耕皮?
配置項更改到數(shù)據(jù)恢復
Android 配置項更改常用的即是橫豎屏切換,當橫豎屏切換的時候蝙场,Activity 會重建凌停,重新走onCreate(xx)...onResume(),此時Activity 已經(jīng)是全新的實例了李丰,因此之前的Activity 關聯(lián)的ViewTree 將會重建苦锨。
除了橫豎屏切換,其它的配置項更改也會重建Activity趴泌。
問題來了:之前ViewTree 綁定的數(shù)據(jù)如何恢復呢舟舒?
傳統(tǒng)的數(shù)據(jù)恢復方法
Android onSaveInstanceState/onRestoreInstanceState 原來要這么理解 已經(jīng)分析過,此處再簡單提一下:
1嗜憔、在onSaveInstanceState 里保存ViewTree 相關的數(shù)據(jù)秃励。
2、在onRestoreInstanceState 里恢復ViewTree 相關的數(shù)據(jù)吉捶。
通過這兩個方法夺鲜,在配置項更改的時候可以將數(shù)據(jù)恢復。不過這種方式也有缺陷:
1呐舔、onSaveInstanceState 用Bundle存儲數(shù)據(jù)便于跨進程傳遞币励,因此其存儲上限受限于Binder(1M),不能用于恢復較大的數(shù)據(jù)珊拼,比如Bitmap食呻。
2、復雜的類需要實現(xiàn)Parcelable/Serializable 接口澎现。
3仅胞、onSaveInstanceState 在onStop 之后調用,調用比較頻繁剑辫。
ViewModel 能夠實現(xiàn)數(shù)據(jù)恢復功能干旧,也規(guī)避了以上問題。
UI 數(shù)據(jù)的統(tǒng)一管理
以前管理UI 數(shù)據(jù)的時候妹蔽,我們一般會定義一個Model椎眯,再定義一個Manager對其進行統(tǒng)一管理挠将,借助于ViewMode+LiveData,能夠更優(yōu)雅地管理數(shù)據(jù)盅视。
綜上捐名,ViewModel 能夠進行數(shù)據(jù)恢復以及UI 數(shù)據(jù)統(tǒng)一管理。
2闹击、ViewModel 的使用方式
橫豎屏切換數(shù)據(jù)恢復
說得ViewModel 很優(yōu)秀的樣子镶蹋,有代碼有真相,以橫豎屏切換為例赏半,看看如何使用它贺归。
定義ViewModel:
public class MoneyViewModel extends ViewModel {
private int money;
private String name = "官方ViewModel";
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
MoneyViewModel 繼承自ViewModel。
作為比對断箫,再定義一個純粹的類:
public class MyViewModel {
private int money;
private String name = "我的ViewModel";
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
兩者唯一的差別就是是否繼承自ViewModel拂酣。
xml里定義兩個TextView以及一個Button。
當點擊修改文本的時候仲义,將上面兩個TextView 修改婶熬,Activity里代碼如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
//先new 出Provider,再獲取ViewModel
moneyViewModel = new ViewModelProvider(this).get(MoneyViewModel.class);
myViewModel = new MyViewModel();
TextView tvVM = findViewById(R.id.tv_vm);
tvVM.setText(moneyViewModel.getName());
TextView tvMyVM = findViewById(R.id.tv_my_vm);
tvMyVM.setText(myViewModel.getName());
Button btnChange = findViewById(R.id.btn_change);
btnChange.setOnClickListener((v) -> {
moneyViewModel.setName("官方 ViewModel 改變");
tvMyVM.setText(moneyViewModel.getName());
myViewModel.setName("我的 ViewModel 改變");
tvVM.setText(myViewModel.getName());
});
}
點擊Button后埃撵,修改我的ViewModel和官方ViewModel赵颅,此時UI 刷新。隨后暂刘,將屏幕旋轉到橫屏饺谬,再查看UI 展示。
可以看出谣拣,豎屏切換到橫屏后募寨,官方ViewModel改變了,我的ViewModel沒有改變森缠。
顯而易見拔鹰,ViewModel 能夠在橫豎屏切換后恢復數(shù)據(jù)。
3贵涵、ViewModel 原理掘地三尺
ViewModelStore 的獲取
moneyViewModel列肢、myViewModel 同樣作為ViewModelActivity 的成員變量,ViewModelActivity 都重建了(重新New ViewModelActivity 實例)独悴,理論上來說成員變量也是重建了的,為啥moneyViewModel 可以保持數(shù)據(jù)呢锣尉?這也是我們要探究的ViewModel 原理刻炒。
從ViewModel 的創(chuàng)建開始分析:
moneyViewModel = new ViewModelProvider(this).get(MoneyViewModel.class);
ViewModelProvider 顧名思義:ViewModel 的提供者。
構造函數(shù)的形參為:ViewModelStoreOwner自沧,該接口的唯一方法:
ViewModelStore getViewModelStore();
我們看到上面?zhèn)魅肓藅his坟奥,也即是說咱們的ViewModelActivity實現(xiàn)了該接口树瞭。
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
mFactory、mViewModelStore 為ViewModelProvider 成員變量爱谁。
mFactory 為創(chuàng)建ViewModel的工廠方法晒喷,mViewModelStore 為ViewModel的存儲器,它是通過 ViewModelStoreOwner.getViewModelStore()獲取的访敌。
ViewModelActivity 繼承自AppCompatActivity凉敲,進而繼承自ComponentActivity,而ComponentActivity 實現(xiàn)了ViewModelStoreOwner 接口寺旺,實現(xiàn)方法如下:
#ComponentActivity.java
public ViewModelStore getViewModelStore() {
...
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
//為空爷抓,從mLastNonConfigurationInstances 尋找
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 直接恢復
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
//沒找到,則創(chuàng)建ViewModelStore
mViewModelStore = new ViewModelStore();
}
}
}
可以看出阻塑,ViewModelProvider. mViewModelStore 來源于ComponentActivity.mViewModelStore蓝撇,而ComponentActivity.mViewModelStore 的賦值有兩個地方:
1、從Activity.mLastNonConfigurationInstances里獲取陈莽。
2渤昌、全新創(chuàng)建,直接new ViewModelStore走搁。
ViewModel 的獲取
ViewModelProvider.get(MoneyViewModel.class)
#ViewModelProvider.java
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//先從ViewModelStore里獲取
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof ViewModelProvider.OnRequeryFactory) {
((ViewModelProvider.OnRequeryFactory) mFactory).onRequery(viewModel);
}
//viewModel 不為空独柑,說明找到了ViewModel
return (T) viewModel;
} else {
...
}
if (mFactory instanceof ViewModelProvider.KeyedFactory) {
//根據(jù)工廠創(chuàng)建ViewModel
viewModel = ((ViewModelProvider.KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
//將ViewModel 存儲到ViewModelStore 里
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
與ViewModelStore 來源類似,ViewModel 來源于兩個地方:
1朱盐、從ViewModelStore 里獲取群嗤。
2、通過反射構造新的實例兵琳。
新生成的ViewModel 將會存儲到ViewModeStore里狂秘。
而ViewModeStore 就只有一個成員變量:
private final HashMap<String, ViewModel> mMap = new HashMap<>();
此處的Map 的key 即為自定義ViewModel的全限定類名+前綴。
Activity 重建對ViewModel 的影響
由上面分析可知躯肌,ViewModelStore 被Activity 持有者春,而ViewModel 被ViewModelStore 持有。
屏幕從豎屏切換到橫屏時清女,Activity 重建了钱烟,拿到的ViewModel 卻沒有變化,我們有理由相信ViewModelStore 沒有變嫡丙,而縱觀ViewModelStore 賦值拴袭,此時的ViewModelStore 很有可能從NonConfigurationInstances里獲取的。
接著分析 NonConfigurationInstances的來龍去脈曙博。
需要注意的是拥刻,NonConfigurationInstances 在Activity.java和ComponentActivity.java 里都有定義。
#Activity.java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
重點查看mLastNonConfigurationInstances 的賦值父泳,它的調用棧如下圖:
performLaunchActivity() 在新建Activity和重建Activity 時都會調用般哼,只是在新建Activity 調用時吴汪,最后的ActivityClientRecord.lastNonConfigurationInstances =null。
重點又流轉到ActivityClientRecord蒸眠,它是怎么確定的漾橙。
#ActivityThread.java
public void handleRelaunchActivity(ActivityClientRecord tmp,
PendingTransactionActions pendingActions) {
...
//從mActivities 里獲取
//final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
ActivityClientRecord r = mActivities.get(tmp.token);
...
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}
現(xiàn)在看來有點眉目了,在重建Activity 時:
1楞卡、從mActivities(緩存)里尋找ActivityClientRecord霜运。
2、通過ActivityClientRecord 找到lastNonConfigurationInstances臀晃。
接下來看看ActivityClientRecord.lastNonConfigurationInstances 在哪賦值的觉渴。
我們知道Activity 重建時的步驟:
1、先將原來的Activity 銷毀徽惋。
2案淋、再重新新建一個Activity實例。
而Activity 銷毀時會調用
#ActivityThread.java
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
if (r != null) {
activityClass = r.activity.getClass();
...
if (getNonConfigInstance) {
try {
//從Activity 里獲取
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
...
}
}
...
}
...
synchronized (mResourcesManager) {
//移除ActivityClientRecord
mActivities.remove(token);
}
...
return r;
}
重點查看activity.retainNonConfigurationInstances():
#Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
//ComponentActivity 重寫了該方法
Object activity = onRetainNonConfigurationInstance();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
//activity = ComponentActivity.NonConfigurationInstances
nci.activity = activity;
...
return nci;
}
接著看onRetainNonConfigurationInstance 方法险绘。
#ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
ViewModelStore viewModelStore = mViewModelStore;
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
//存儲ViewModeStore
nci.viewModelStore = viewModelStore;
return nci;
}
終于惹骂,又看到了ViewModeStore帆疟。
ComponentActivity.NonConfigurationInstances 存儲了viewModelStore蜘澜,然后ComponentActivity.NonConfigurationInstances 存儲在Activity. NonConfigurationInstances.activity 里箕戳。
ViewModel 能夠恢復的真正原因
重新歸納小結一下:
1、Activity 新建時代咸,創(chuàng)建了ViewModeStore蹈丸,而ViewModelStore 里存儲了ViewModel。
2呐芥、豎屏切換為橫屏時逻杖,先將當前Activity 銷毀,此時會調用到performDestroyActivity()思瘟,該方法里將ViewModeStore 封裝在NonConfigurationInstances里荸百,而NonConfigurationInstances 最終賦值給ActivityClientRecord.lastNonConfigurationInstances里。
3滨攻、ActivityClientRecord 實例存儲在ActivityThread里的Map里(Activity 啟動時存放的)够话。
4、Activity 銷毀后光绕,重建Activity時女嘲,通過ActivityThread的Map 找到之前被銷毀的Activity關聯(lián)的ActivityClientRecord,從中取出lastNonConfigurationInstances賦值給Activity.mLastNonConfigurationInstances诞帐。
5欣尼、在Activity.onCreate()里調用new ViewModelProvider(this).get(MoneyViewModel.class) 時,發(fā)現(xiàn)此時的ViewModelStore == null景埃,于是從Activity.mLastNonConfigurationInstances里獲取媒至,此時就能夠拿到上一次的ViewModeStore,進而獲取到ViewModel谷徙。
以上步驟就是ViewMode 數(shù)據(jù)恢復的過程拒啰。
核心的地方在于:ViewModel 最終存儲在ActivityThread里,而ActivityThread一直存在(和進程生命周期一致)完慧,與Activity 生命周期毫無關聯(lián)谋旦。
這也就是為什么經(jīng)常說ViewModel里不能持有Activity 的引用,因為ViewModel 的生命周期比Activity 長屈尼。
講到這册着,你可能有疑惑了:ViewModel生命周期具體有多長呢?
ComponentActivity 里有通過Lifecycle監(jiān)聽Activity生命周期脾歧,當Activity 處在"ON_DESTROY"狀態(tài)時甲捏,有如下判斷:
#ComponentActivity.java
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
//如果配置項沒有發(fā)生變更,則認為是Activity 正常銷毀
//清除ViewModelStore
getViewModelStore().clear();
}
}
最終將ViewModel從ViewModeStore 的Map里移除鞭执。
當我們重寫ViewModel.onCleared()方法時司顿,在ViewModel 被清除時將會被調用,用于一些資源的釋放兄纺。
因此大溜,ViewModel 具體的生命周期如下:
1、當配置項沒發(fā)生變更時估脆,ViewModel 隨著Activity 銷毀而銷毀钦奋。
2、當配置項發(fā)生變更而導致Activity 重建時疙赠,ViewModel 生命周期長于Activity付材。
4、Lifecycle/LiveData/ViewModel 關聯(lián)
ViewModel 優(yōu)勢
通篇分析下來棺聊,發(fā)現(xiàn)ViewModel.java 本身很簡單伞租,系統(tǒng)為了恢復ViewModel 做了很多工作。
優(yōu)勢
1限佩、ViewModel 不限制類型葵诈,不限制大小。
2祟同、沒有onSaveInstanceState 保存/恢復數(shù)據(jù)時的缺陷作喘。
3、ViewModel 配合LiveData晕城,既可以分離UI和數(shù)據(jù)泞坦,又可以通過數(shù)據(jù)驅動UI。
4砖顷、ViewModel 通過Activity 獲取贰锁,只要拿到Activity實例就有機會拿到ViewModel赃梧,因此可以作為多個Fragment的數(shù)據(jù)共享中轉。
Lifecycle 與LiveData 關聯(lián)
LiveData 借助Lifecycle 實現(xiàn)生命周期監(jiān)聽豌熄,判別活躍與非活躍狀態(tài)等授嘀,實現(xiàn)一個有生命周期感知的數(shù)據(jù)。
ViewModel 與Lifecycle 關聯(lián)
沒啥關聯(lián)锣险,也沒有配合使用蹄皱。
ViewModel 與 LiveData 關聯(lián)
ViewModel 分離了UI 和數(shù)據(jù),想要通過數(shù)據(jù)驅動UI芯肤,得配合LiveData 使用更香巷折。
如下簡單示例:
public class LiveDataViewModel extends ViewModel {
private MutableLiveData<String> mutableLiveData;
public MutableLiveData<String> getLiveData() {
if (mutableLiveData == null) {
mutableLiveData = new MutableLiveData<>();
}
return mutableLiveData;
}
}
更多例子都在github 上,有興趣可以查看崖咨。
在分析LiveData和ViewModel時锻拘,在一開始都沒有將兩者一并提出來,就是為了讓大家能夠清晰地認識到兩者的職能邊界击蹲,因為應用場景不一樣逊拍,兩者都可以單獨使用,而對于配置項更改敏感的場景际邻,兩者結合會更加方便芯丧。
下篇將分析ViewModel,徹底厘清為啥ViewModel能夠存儲數(shù)據(jù)以及運用場合世曾。
本文基于:implementation 'androidx.appcompat:appcompat:1.4.1' & Android 10.0
您若喜歡缨恒,請點贊、關注轮听,您的鼓勵是我前進的動力
持續(xù)更新中骗露,和我一起步步為營系統(tǒng)、深入學習Android
1血巍、Android各種Context的前世今生
2萧锉、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4述寡、View Measure/Layout/Draw 真明白了
5柿隙、Android事件分發(fā)全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7鲫凶、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8禀崖、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10螟炫、Android 各種坐標徹底明了
11波附、Android Activity/Window/View 的background
12、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14掸屡、Android 存儲系列
15封寞、Java 并發(fā)系列不再疑惑
16、Java 線程池系列
17仅财、Android Jetpack 前置基礎系列
18钥星、Android Jetpack 易懂易學系列