前言
Jetpack AAC 系列文章:
Jetpack Lifecycle 该怎么看?还肝否?
Jetpack LiveData 是时候了解一下了
Jetpack ViewModel 抽丝剥茧
前两篇分析了Lifecycle和LiveData,本篇将着重分析ViewModel及其三者的关联。
通过本篇,你将了解到:
1、为什么需要ViewModel ?
2、ViewModel 的使用方式
3、ViewModel 原理掘地三尺
4、Lifecycle/LiveData/ViewModel 关联
1、为什么需要ViewModel ?
配置项更改到数据恢复
Android 配置项更改常用的即是横竖屏切换,当横竖屏切换的时候,Activity 会重建,重新走onCreate(xx)...onResume(),此时Activity 已经是全新的实例了,因此之前的Activity 关联的ViewTree 将会重建。
除了横竖屏切换,其它的配置项更改也会重建Activity。
问题来了:之前ViewTree 绑定的数据如何恢复呢?
传统的数据恢复方法
Android onSaveInstanceState/onRestoreInstanceState 原来要这么理解 已经分析过,此处再简单提一下:
1、在onSaveInstanceState 里保存ViewTree 相关的数据。
2、在onRestoreInstanceState 里恢复ViewTree 相关的数据。
通过这两个方法,在配置项更改的时候可以将数据恢复。不过这种方式也有缺陷:
1、onSaveInstanceState 用Bundle存储数据便于跨进程传递,因此其存储上限受限于Binder(1M),不能用于恢复较大的数据,比如Bitmap。
2、复杂的类需要实现Parcelable/Serializable 接口。
3、onSaveInstanceState 在onStop 之后调用,调用比较频繁。
ViewModel 能够实现数据恢复功能,也规避了以上问题。
UI 数据的统一管理
以前管理UI 数据的时候,我们一般会定义一个Model,再定义一个Manager对其进行统一管理,借助于ViewMode+LiveData,能够更优雅地管理数据。
综上,ViewModel 能够进行数据恢复以及UI 数据统一管理。
2、ViewModel 的使用方式
横竖屏切换数据恢复
说得ViewModel 很优秀的样子,有代码有真相,以横竖屏切换为例,看看如何使用它。
定义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 能够在横竖屏切换后恢复数据。
3、ViewModel 原理掘地三尺
ViewModelStore 的获取
moneyViewModel、myViewModel 同样作为ViewModelActivity 的成员变量,ViewModelActivity 都重建了(重新New ViewModelActivity 实例),理论上来说成员变量也是重建了的,为啥moneyViewModel 可以保持数据呢?这也是我们要探究的ViewModel 原理。
从ViewModel 的创建开始分析:
moneyViewModel = new ViewModelProvider(this).get(MoneyViewModel.class);
ViewModelProvider 顾名思义:ViewModel 的提供者。
构造函数的形参为:ViewModelStoreOwner,该接口的唯一方法:
ViewModelStore getViewModelStore();
我们看到上面传入了this,也即是说咱们的ViewModelActivity实现了该接口。
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
mFactory、mViewModelStore 为ViewModelProvider 成员变量。
mFactory 为创建ViewModel的工厂方法,mViewModelStore 为ViewModel的存储器,它是通过 ViewModelStoreOwner.getViewModelStore()获取的。
ViewModelActivity 继承自AppCompatActivity,进而继承自ComponentActivity,而ComponentActivity 实现了ViewModelStoreOwner 接口,实现方法如下:
#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) {
//没找到,则创建ViewModelStore
mViewModelStore = new ViewModelStore();
}
}
}
可以看出,ViewModelProvider. mViewModelStore 来源于ComponentActivity.mViewModelStore,而ComponentActivity.mViewModelStore 的赋值有两个地方:
1、从Activity.mLastNonConfigurationInstances里获取。
2、全新创建,直接new ViewModelStore。
ViewModel 的获取
ViewModelProvider.get(MoneyViewModel.class)
#ViewModelProvider.java
public T get(@NonNull Class 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 get(@NonNull String key, @NonNull Class 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) {
//根据工厂创建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 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 mActivities = new ArrayMap<>();
ActivityClientRecord r = mActivities.get(tmp.token);
...
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}
现在看来有点眉目了,在重建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 新建时,创建了ViewModeStore,而ViewModelStore 里存储了ViewModel。
2、竖屏切换为横屏时,先将当前Activity 销毁,此时会调用到performDestroyActivity(),该方法里将ViewModeStore 封装在NonConfigurationInstances里,而NonConfigurationInstances 最终赋值给ActivityClientRecord.lastNonConfigurationInstances里。
3、ActivityClientRecord 实例存储在ActivityThread里的Map里(Activity 启动时存放的)。
4、Activity 销毁后,重建Activity时,通过ActivityThread的Map 找到之前被销毁的Activity关联的ActivityClientRecord,从中取出lastNonConfigurationInstances赋值给Activity.mLastNonConfigurationInstances。
5、在Activity.onCreate()里调用new ViewModelProvider(this).get(MoneyViewModel.class) 时,发现此时的ViewModelStore == null,于是从Activity.mLastNonConfigurationInstances里获取,此时就能够拿到上一次的ViewModeStore,进而获取到ViewModel。
以上步骤就是ViewMode 数据恢复的过程。
核心的地方在于:ViewModel 最终存储在ActivityThread里,而ActivityThread一直存在(和进程生命周期一致),与Activity 生命周期毫无关联。
这也就是为什么经常说ViewModel里不能持有Activity 的引用,因为ViewModel 的生命周期比Activity 长。
讲到这,你可能有疑惑了:ViewModel生命周期具体有多长呢?
ComponentActivity 里有通过Lifecycle监听Activity生命周期,当Activity 处在"ON_DESTROY"状态时,有如下判断:
#ComponentActivity.java
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
//如果配置项没有发生变更,则认为是Activity 正常销毁
//清除ViewModelStore
getViewModelStore().clear();
}
}
最终将ViewModel从ViewModeStore 的Map里移除。
当我们重写ViewModel.onCleared()方法时,在ViewModel 被清除时将会被调用,用于一些资源的释放。
因此,ViewModel 具体的生命周期如下:
1、当配置项没发生变更时,ViewModel 随着Activity 销毁而销毁。
2、当配置项发生变更而导致Activity 重建时,ViewModel 生命周期长于Activity。
4、Lifecycle/LiveData/ViewModel 关联
ViewModel 优势
通篇分析下来,发现ViewModel.java 本身很简单,系统为了恢复ViewModel 做了很多工作。
优势
1、ViewModel 不限制类型,不限制大小。
2、没有onSaveInstanceState 保存/恢复数据时的缺陷。
3、ViewModel 配合LiveData,既可以分离UI和数据,又可以通过数据驱动UI。
4、ViewModel 通过Activity 获取,只要拿到Activity实例就有机会拿到ViewModel,因此可以作为多个Fragment的数据共享中转。
Lifecycle 与LiveData 关联
LiveData 借助Lifecycle 实现生命周期监听,判别活跃与非活跃状态等,实现一个有生命周期感知的数据。
ViewModel 与Lifecycle 关联
没啥关联,也没有配合使用。
ViewModel 与 LiveData 关联
ViewModel 分离了UI 和数据,想要通过数据驱动UI,得配合LiveData 使用更香。
如下简单示例:
public class LiveDataViewModel extends ViewModel {
private MutableLiveData mutableLiveData;
public MutableLiveData getLiveData() {
if (mutableLiveData == null) {
mutableLiveData = new MutableLiveData<>();
}
return mutableLiveData;
}
}
更多例子都在github 上,有兴趣可以查看。
在分析LiveData和ViewModel时,在一开始都没有将两者一并提出来,就是为了让大家能够清晰地认识到两者的职能边界,因为应用场景不一样,两者都可以单独使用,而对于配置项更改敏感的场景,两者结合会更加方便。
下篇将分析ViewModel,彻底厘清为啥ViewModel能够存储数据以及运用场合。
本文基于:implementation 'androidx.appcompat:appcompat:1.4.1' & Android 10.0
ViewModel 演示&工具
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
持续更新中,和我一起步步为营系统、深入学习Android
1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易懂易学系列