引言
最近重新学习了Jetpacl相关组件,在这里记录下
先提出几个问题
ViewModel是什么?它有什么作用
屏幕旋转或者配置变更时,如何进行Activity数据保存
ViewModel是如何做到数据保存的
ViewModel介绍
ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候
viewModel的简单使用
- 加入依赖
implementation ‘androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0’
- 创建一个类继承ViewModel,里面放入项目需求的数据。这里可以选择继承viewModel或者继承AndroidViewModel,区别是AndroidViewModel带有application的环境
class PictureInfoViewModel : ViewModel() {
lateinit var uriList: ArrayList<Uri>
var currentPicture: MutableLiveData<Int> = MutableLiveData()
}
class PictureInfoViewModel(application: Application) : AndroidViewModel(application) {
lateinit var uriList: ArrayList<Uri>
var currentPicture: MutableLiveData<Int> = MutableLiveData()
}
- 在Activity中创建viewModel,在老版本是使用ViewModelProviders类,因为扩展性不好,该类被废弃,推荐使用ViewModelProvider进行构建,看上去只是多了一个s,相比之前,我们需要多传入一个实现Factory类create方法的参数,看起来要多传参数变得麻烦了,但是我们可以创建Factory实现类定制化create方法构建viewModel,如果你不想那么麻烦去重写方法,可以直接使用系统的NewInstanceFactory或者是AndroidViewModelFactory来获得一个工厂对象,这两个区别就是AndroidViewModelFactory需要传入application,刚好是给上一步的AndroidViewModel来使用。下面是几中常见的创建方式
// 方式一 : 使用by关键字用委托的方式构建,使用ViewModelProvider类进行,
private val viewModel: PictureInfoViewModel by lazy {
ViewModelProvider(
this,
ViewModelProvider.NewInstanceFactory() // 这里可以用系统的也可以自己实现Factory
).get(PictureInfoViewModel::class.java)
}
// 方式二: 用ComponentActivity提供的扩展函数来构建,如果对Factory没有特别需求,推荐使用这种方式,既简单又方便
private val viewModel: PictureInfoViewModel by viewModels()
// 扩展函数的实现,传进来的Factory为不为空,为空就走默认流程了
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
- 先看一张官方的viewModel生命周期图
- 可以看到只有在Activity被销毁时机才会销毁viewModel,具体是怎么触发销毁的我们后面继续分析源码
源码分析
创建方法分析
上面讲到两种构建viewModel的方式,其实两种方式都一样,写法上的区别而已,最终都会调到ViewModelProvider(store, factory).get(viewModelClass.java)
- 这里就是一个viewModel仓库和一个用于构建ViewModel的工厂
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
// ViewModelStore其实就是一个hashMap,缓存ViewModel
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
- 大致流程就是先从viewModel仓库里面通过Key取拿ViewModel,拿不到就通过我们get传入的class类进行反射创建出ViewModel来
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;
}
- 回到我们创建时,为什么传入this就能得到viewModelStore?往源码里找,原来是Activity的爷爷类ComponentActivity,实现了ViewModelStoreOwner接口
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
// ComponentAcitivty中的具体实现
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
// 往下继续看,如果为空,会尝试从getLastNonConfigurationInstance()里面拿到nc对象
// 再从nc里面获得viweModelstore,getLastNonConfigurationInstance是啥我们后面分析
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
- 创建看完了,我们再来看看如何销毁的,在ComponentActivity初始化时,使用lifecycle添加了监听,我们都知道,lifecycle生命周期发生改变的时候就会回调到这个onStateChanged方法,当回调的事件为destory也就是activity调用了onDestroy的时候,会进入下面的判断,走到getViewModelStore().clear(),清空viewModel
public ComponentActivity() {
..................
// 忽略无关代码
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
..............
}
总结下:viewModel使用Factory进行创建,并把自身缓存在acitivty的viewModelStore中,这是一个haspMap数据结果,里面能放很多ViewModel,当acitivty销毁时,通过lifecycle的监听回调销毁viewModel
Activity状态变更的数据恢复
先来复习下界面旋转时走的生命周期
onPause()->onStop()->onSaveInstanceState()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState()->
onResume()
-
由此可以得到数据恢复的方法一:重写onSaveInstanceState存储那些我们想要保留的重要的临时数据,把他存放在Bundle对象里面,下次再重新开启这个Activity的时候,会执行oncreate方法,我们就可以重onCreate方法的参数中接受这个存储数据的Bundle对象,从里面取出数据。或者从onRestoreInstanceState中取出数据也是同理
-
方式二 : 通过Activity提供的两个方法,onRetainNonConfigurationInstance进行保存数据,然后通过getLastNonConfigurationInstance恢复保存数据。细心的同学应该发现了,ViewModel也就是通过getLastNonConfigurationInstance获得上一次保存的数据,google已经不推荐我们进行重写,因为已经有ViewModel了嘛
-
具体ViewModel实现数据保存和恢复的代码
// 数据保存
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
// 数据恢复,这里就是我们上面分析创建流程时的代码,通过getLastNonConfigurationInstance获得上一次的viewModel数据
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
总结:ViewModel为什么在旋转屏幕后不会丢失状态?
- 通过两个方法getLastNonConfigurationInstance和onRetainNonConfigurationInstance,当设备的配置发生变化时(例如屏幕翻转),AMS通过binder回调到onRetainNonConfigurationInstance保存NonConfigurationInstances的一个实例对象,里面存放着viewModelStore保存activity的所有viewModel信息,当activity重建完成,创建viewModel时,会调用到getLastNonConfigurationInstance()从nc里面找到viewModelStore,然后取出所需要的viewModel,完成数据的恢复