Jetpack组件系列文章目录
文章目录
前言
本文是Jetpack组件专题的第三篇,Jetpack是Google官方在2018年推出的一套组件库,用来帮助开发者更轻松地编写优质应用。包含四大模块:Architecture( 架构组件)、Foundation(基础组件)、Behavior(行为组件)、UI(界面组件)。

有之以为利,无之以为用。一起来学习先进的组件知识。
一、ViewModel是什么?
先看下官方介绍:
ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
ViewModel是一个管理和提供Activity或fragment页面数据的类。可以保存UI 界面的数据,可以解决activity或fragment之间的通信问题。
1、优点
- 用来存储界面的数据,解耦
- 屏幕旋转的时候依然可以保存数据
- 可以用来解决activity和fragment之间的通信
2、局限性
- ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类,否则会导致内存泄露
二、使用步骤
1.添加依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
2.创建数据类
书接上文DataBinding,新建一个数据类,使用了数据刷新

然后进入正文,创建一个ViewModel用来保存上边的数据,数据通常使用LiveData来维护,后续再结合LiveData细谈。

3.ViewModel使用
使用方法很简单,在调用合适的地方初始化即可
// 实例化ViewModel
mUserViewModel = new ViewModelProvider(this).get(UserViewModel.class);
带参数的ViewModel怎么整?
继续
…
4.带参数的ViewModel的使用
数据类增加参数

ViewModel提供了一个默认的ViewModelProvider,使用其factory方法可以用来创建ViewModel

调用带参数的ViewModel
// 实例化带参数的ViewModel
mUserViewParamsModel = new UserViewModelFactory(mStu).create(UserViewParamsModel.class);
mActivityBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_model);
// 关联数据和绑定意图
mActivityBindingBinding.setUser(mStu);
mActivityBindingBinding.tvSet.setOnClickListener(v -> {
// 改变数据,刷新数据
// mStu.setName("项羽");
mUserViewParamsModel.reName();
});
5.fragment之间的通信
viewModel还可以在同属一个Activity之间的不同fragment进行通信,共用的一份ViewModel
public class MyFragment extends Fragment {
public void onStart() {
UserModel userModel = new ViewModelProvider(requireActivity()).get(UserModel.class);
}
}
三、生命周期
上官图

从官方提供的ViewModel作用域不难看出,ViewModel生命周期作用在activity的从生到死,最后拥有者销毁了在调用一下清理onCleared(),所以因为ViewModel的长生命周期,所以不能持有View、Lifecycle、Acitivity引用,防止出现内存泄露。
非得要个context,咋整?
官方提供了一个AndroidViewModel,增加了一个获取Application的方法,直接继承即可。
另外这张图是页面旋转的Activity的生命周期,也解释了viewModel可以在页面旋转的时候依然可以保存数据的由来。
四、敲一下原理
上狠活
从实例化开始
mUserViewModel = new ViewModelProvider(this).get(UserViewModel.class);
进入get方法,ViewModelProvider首先是一个提供ViewModel的实用类,通过传入的类类型获取规范化的类名,通过组合key,加类类型查找ViewModel
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);
}
可以看到是通过mViewModelStore根据组合的key获取的ViewModel。
mViewModelStore 那么是什么呢?
继续看
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
从介绍其实就看得出来,ViewModelStore就是一个保存管理ViewModel的类,通过HashMap的形式,以刚才组合的key对ViewModel进行存取。

返回上一步get方法,就是先去ViewModelStore查缓存,有就直接取出来。没找到则通过工厂模式实例化创建,思路很清晰了。
看下里面具体用到的东西

factory 就是一个创建ViewModel的接口,看下其实例化的地方

factory实例化是第三个方法,由第一个方法传递而来,第一个方法也是ViewModelProvider创建调用的方法。看下ViewModelStoreOwner

ViewModelStoreOwner 很简单就是用来获取ViewModelStore的方法,ViewModelStore刚才讲过是用来存取ViewModel的。继续看下getViewModelStore方法

查找这个getViewModelStore发现在ComponentActivity和Fragment中都有,很简单就是实例化一个ViewModelStore,示例是ComponentActivity中的方法,Fragment中类似,没有就新建一个。
有个重点,getLastNonConfigurationInstance是做什么的?ViewModelStore实例化先从
NonConfigurationInstance从获取 ViewModelStore实例,如果NonConfigurationInstance不存在,就new一个mViewModelStore。
并且还注意到,在onRetainNonConfigurationInstance()方法中 会把mViewModelStore赋值给NonConfigurationInstances:


onRetainNonConfigurationInstance()方法很重要:在Activity因配置改变 而正要销毁时,且新Activity会立即创建,那么系统就会调用此方法。 也就说,配置改变时 系统把viewModelStore存在了NonConfigurationInstances中。所以屏幕旋转的时候数据保存是这样来的?
方法是在Acticity.java中,它返回的是Acticity.java中的NonConfigurationInstances的属性activity,也就是onRetainNonConfigurationInstance()方法返回的实例。
那么mLastNonConfigurationInstances是怎么来的?
搜索发现是在 Acticity.java的attach()方法中,attach方法是为Activity关联上下文环境,是在Activity 启动的核心流程——ActivityThread的performLaunchActivity方法中调用,这里的lastNonConfigurationInstances是存在 ActivityClientRecord中的一个组件信息
/**
Maps from activity token to local record of the activities that are preparing to be launched.
*/
final Map<IBinder, ActivityClientRecord> mLaunchingActivities =
Collections.synchronizedMap(new ArrayMap<IBinder, ActivityClientRecord>());
所以,ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影响,那么ActivityClientRecord中lastNonConfigurationInstances也不受影响,那么其中的Object activity也不受影响,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影响,那么viewModel也就不受影响了。
回头来看,ViewModelProvider调用了getViewModelStore第一步初始化了一个ViewModelStore;
接着看Provider的后半部分,判断owner属于哪个工厂创建的,NewInstanceFactory.getInstance())很简单,实现Factory方法,实例化和一个初始化ViewModel的方法,主要看HasDefaultViewModelProviderFactory这个类

HasDefaultViewModelProviderFactory是一个接口,用来获取factory方法,查找发现ComponentActivity和Fragment都实现了此接口

getDefaultViewModelProviderFactory实例化了一个SavedStateViewModelFactory

此构造方法获取了activity的Registry、lifecycle、intent中的数据、application,实例化一个factory,保存了activity相关的数据
综上
- ViewModelProvider(this)先是初始化一个ViewModelStore存储器,再初始化了一个ViewModelFactory,里面使用了反射
- ViewModelStore先从NonConfigurationInstances取出,没有则新建,屏幕旋转的时候ViewModelStore会存储进NonConfigurationInstances,保存数据不受activity重建的影响。onRetainNonConfigurationInstance()保存状态,getLastNonConfigurationInstance() 恢复。
- get(@NonNull Class modelClass)主要是初始化ViewModel,有缓存取缓存,无缓存则新建
五、总结
ViewModel就是一个UI界面的数据管理类,长生命周期,屏幕旋转的时候依然可以保存数据,支持从属一个activity的fragment之间的通信,是实现MVVM的重要组件之一。
一般都是结合LiveData组合使用,和LiveData之间的用法,后续继续介绍。
六、参考链接
- https://www.jianshu.com/p/5fbde7a2189e
- https://blog.51cto.com/u_15482826/4911080
- https://blog.youkuaiyun.com/FrenkX72/article/details/126117111
- https://developer.android.google.cn/topic/libraries/architecture/viewmodel?hl=zh_cn#samples
- https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650245802&idx=1&sn=b908cb1227d0b233e0addf712b0e808c&chksm=88637bc5bf14f2d383fa2f6d74c2c1205d7b8652bd1f26a5b0ed34ba5c6a173afbfb0cdf572d&scene=27
1074





