前言

当屏幕旋转或者切换系统语言时,Activity 生命周期从销毁再重建,但是ViewModel里面的变量值不受到影响,说明ViewModel中的变量在屏幕旋转前进行了存储,在屏幕旋转后又进行了恢复。

里面的原理是怎么实现的呢?

一、获取ViewModel实例

// MainActivity.kt
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

这个代码拆分成2段来分析:ViewModelProvider(this)get(MainViewModel::class.java)

二、ViewModelProvider(this)

用于获取 ViewModelProvider 实例

// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory(): NewInstanceFactory.getInstance());
}
// ComponentActivity.java
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;
}@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {if (mViewModelStore == null) {// 先从 NonConfigurationInstances 获取NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstances// 从缓存中恢复mViewModelStore = nc.viewModelStore;}// 如果缓存里面没有,直接创建新的if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}
}

代码很简单,可以看出,最后调用的是ComponentActivity中的ensureViewModelStore()方法,这个方法很重要。

这个方法涉及2个很重要的类:ViewModelStore 和 NonConfigurationInstances

ViewModelStore

ViewModelStore从名字可以看出,是用来存储ViewModle对象的,做一个缓存的作用,一般底层都是用Map或者List实现。来看下源码:

// ViewModelStore.java
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();}
}

代码很简单,可以看出ViewModelStore 里面就是一个HashMap,用于缓存ViewModel实例对象。

NonConfigurationInstances

// ComponentActivity$NonConfigurationInstances.java
static final class NonConfigurationInstances {Object custom;ViewModelStore viewModelStore;
}

这其实就是一个Java Bean类,里面存在2个字段,包过 viewModelStore 字段。

三、get(MainViewModel::class.java)

// ViewModelProvider.javaprivate static final String DEFAULT_KEY ="androidx.lifecycle.ViewModelProvider.DefaultKey";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);
}
// ViewModelProvider.java
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 StatementWithEmptyBodyif (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;
}

先根据key从mViewModelStore获取缓存中的ViewModel,如果存在,则返回viewModel实例。

如果mViewModelStore缓存中不存在当前modelClass的实例,则用工厂方法创建一个,再将新创建的加入缓存。

我们在Activity中并没有设置key,默认的key又是一个常量,猜测:

屏幕旋转前后,mViewModelStore应该是同一个对象,得到的跟viewModel也是同一份实例对象。  所以我们只要找出屏幕旋转前后,mViewModelStore如何保存和恢复即可。

验证猜测最直接的方法就是log打印:

val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
Log.i("wutao--> ", "viewModel--> $viewModel"  + "     getViewModelStore--> ${getViewModelStore()}")

屏幕旋转后,mViewModelStore和viewModel对象地址确实是同一个。

四、mViewModelStore 的恢复

获取 mViewModelStore 代码如下:

// ComponentActivity.java
void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstances// 屏幕旋转在这里恢复mViewModelStore = nc.viewModelStore;}// 屏幕旋转后,数据恢复不是new出来的对象if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}
}

屏幕旋转前后,mViewModelStore 在屏幕旋转前后都是同一个对象,这个对象不可能是new出来的,那就是走的 mViewModelStore = nc.viewModelStore;,也就是从getLastNonConfigurationInstance() 得到的屏幕旋转前保存的数据。

屏幕旋转后,Activity重建后从 getLastNonConfigurationInstance() 中获取到了屏幕旋转前保存的 NonConfigurationInstances 实例对象,然后从nc对象中获取存储的mViewModelStore对象。

我们一般是在  onCreate() 中去获取ViewModel 实例对象的,说明getLastNonConfigurationInstance()这个方法在 onCreate() 方法前调用。

那当屏幕旋转前, mViewModelStore  实例是在哪存储的呢?

五、mViewModelStore 的存储

从上面可以看出,屏幕旋转完成,Activity重建后mViewModelStore是从NonConfigurationInstances获取的,那屏幕旋转前肯定也是在这里存储的。

搜索调用的地方:

从上图片可以看到是在第二处,代码如下:

// ComponentActivity.java
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 NonConfigurationInstanceNonConfigurationInstances 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;
}

可以得出结论:屏幕旋转前,数据在 onRetainNonConfigurationInstance()保存,Activity 重建后,在 getLastNonConfigurationInstance() 中恢复。

Activity生命周期调用如下:

继续跟一下源码,寻找数据具体存储在哪里?

六、一探到底

Activity重启后数据的恢复

Activity 重建后,在 getLastNonConfigurationInstance() 中恢复。

// Activity.java
public Object getLastNonConfigurationInstance() {return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;
}

mLastNonConfigurationInstances 赋值的地方:

// Activity.java
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {attachBaseContext(context);···mLastNonConfigurationInstances = lastNonConfigurationInstances;···}

从Activity的启动流程可知,Activity$attach()方法是在ActivityThread调用的:

// ActivityThread.javaActivity.NonConfigurationInstances lastNonConfigurationInstances;/**  Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken);
}

从上得知,数据存储在ActivityClientRecord中,在Activity启动时将ActivityClientRecord中的lastNonConfigurationInstances通过attach()方法赋值到对应的Activity中,然后通过getLastNonConfigurationInstance()恢复数据。

屏幕旋转前数据的存储

屏幕旋转前,数据在 onRetainNonConfigurationInstance()  保存。
在Activity的retainNonConfigurationInstances()方法中被调用。
那retainNonConfigurationInstances()方法又是在哪调用的呢?肯定也跟ActivityThread有关,在ActivityThread搜索下,代码如下:

// ActivityThread.java
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {ActivityClientRecord r = mActivities.get(token);···if (getNonConfigInstance) {try {r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();} catch (Exception e) {···}}···return r;
}

从上得知,performDestroyActivity() 调用了retainNonConfigurationInstances() 方法并把数据保存到了ActivityClientRecord的lastNonConfigurationInstances中。

七、特例-系统杀后台

由于上文已经做过实验了,我这里直接贴上实验的打印结果 第一张图是模拟杀后台的生命周期打印,第二张图是屏幕旋转。

可以看出:

系统杀后台,Activity不会走onDestory()和onRetainCustomNonConfigurationInstance()方法。
可以说杀掉后台,Activity销毁的生命周期都不会走,只有App再回到前台时,才会走Activity重建生命周期。

因为没有执行onRetainCustomNonConfigurationInstance()方法,Activity的数据也没有缓存下来,所以Activity重建也没有数据可以恢复。

下图是Activity中的ViewModel实例对象地址打印:

可以看出,adb模拟杀掉后台后,ViewModel地址值变了,是一个全新的地址。

如果想要系统内存不足,杀掉后台,App再次回到前台,之前的数据进行恢复,应该怎么处理?

请听下回分析。

八、总结

屏幕旋转前,Activity销毁时:

ComponentActivity调用onRetainNonConfigurationInstance()方法,将要销毁的Activity的mViewModelStore转化为NonConfigurationInstances对象,继续调用Activity的retainNonConfigurationInstances()方法,最终在ActivityThread的performDestroyActivity()中将数据保存在ActivityClientRecord中。

Activity重建后:

在Activity启动时,ActivityThread调用performLaunchActivity()方法,将存储在ActivityClientRecord中的lastNonConfigurationInstances通过Activity的attach()方法传递到对应的Activity中,然后通过getLastNonConfigurationInstance()恢复mViewModelStore实例对象,最后根据对应的key拿到销毁前对应的ViewModel实例。

此外,当系统内存不足,系统将后台应用回收后,ViewModel中的数据不会恢复。

附上总体流程图:

屏幕旋转导致Activity销毁重建,ViewModel是如何恢复数据的相关推荐

  1. Android 屏幕旋转时Activity的变化

    Android开发文档上专门有一小节解释这个问题.简单来说,Activity是负责与用户交互的最主要机制,任何"设置"(Configuration)的改变都可能对Activity的 ...

  2. 退出android app时界面残留影响,【Android】App 或 Activity 销毁重建的状态恢复对回调带来的影响...

    问题背景 在开发 PassportSDK 时遇到的此类问题,测试反馈说当打开 App 进入登录页面,此时如果切换出去到手机设置页面将App 的定位权限设置为「拒绝授予」,在切换回 App 会发生登录信 ...

  3. Android屏幕旋转时Activity不重新调用onCreate的方法

    2019独角兽企业重金招聘Python工程师标准>>> android屏幕旋转时Activity不重新调用onCreate的方法 当手机转屏时,Activity的onDestroy和 ...

  4. android 旋转屏幕 不重走生命周期,屏幕旋转后Activity生命周期

    主要针对屏幕旋转对 Activity 生命周期有何影响. 第一种情况 在没有其它配置的情况下,通过日志打印屏幕旋转会调用的方法. //onPause()----onStop()-----onDestr ...

  5. Android 设定横屏,禁止屏幕旋转,Activity重置 [更新视频播放器相关]

    1. 设定屏幕方向 当指定了屏幕的方向后(非SCREEN_ORIENTATION_UNSPECIFIED),屏幕就不会自己主动的旋转了 有2中方式控制屏幕方向: 1.1 改动AndroidManife ...

  6. android activity 旋转,Android 设定横屏,禁止屏幕旋转,Activity重置

    1. 设定屏幕方向 有2中方式控制屏幕方向: 1.1 修改AndroidManifest.xml 在AndroidManifest.xml的activity中加入: 横屏: android:scree ...

  7. Activity销毁重建导致LiveData数据倒灌

    问题前因 我们做的是一个类似ofo的App,面向海外市场,有些国家存在多种语言,例如加拿大. 用户骑行完毕后,在HomeActivity请求结束行程的接口,HomeActivity中注册结束行程的Li ...

  8. Android 面试题:为什么 Activity 都重建了 ViewModel 还存在?

    作者:彭旭锐 链接:https://juejin.cn/post/7121998366103306254 前言 ViewModel 是 Jetpack 组件中较常用的组件之一,也是实现 MVVM 模式 ...

  9. Android屏幕旋转后的变更--ConfigChange

    文章目录 1. Activity生命周期的变化 1.1 正常生命周期 1.2 屏幕旋转后重建Activity 1.3 解决数据丢失问题--onSaveInstanceState和onRestoreIn ...

最新文章

  1. php 原生查询mongo,PHP操作MongoDB的原生CURD方法
  2. FireMonkey 导出目前 Style 另存文件
  3. 非库存采购的自动记帐
  4. 【深度学习】利用CNN来检测伪造图像
  5. 帷幕的帷是什么意思_“战斗民族”的鲜花礼品凭什么火遍全球?
  6. 《c陷阱与缺陷》笔记--注意边界值
  7. maven 添加本地库
  8. 为什么说Java 程序员必须掌握 Spring Boot?
  9. linux 的间隔定时器函数setitimer
  10. CoralCache:一个提高微服务可用性的中间件
  11. 用C#实现MVC(Model View Control)模式介绍
  12. python xpath爬虫_[爬虫]python下的xpath清洗数据之html数据清洗
  13. 基于FP5207的5V升12V电路设计
  14. C/C++大数运算库介绍及安装
  15. html转换txt文件,HTML网页转TXT文件、文本转换器_TxtEasy! V1.5.5 免费版
  16. 电脑重装系统后usbcleaner怎么格式化u盘
  17. C#仿win10计算器
  18. python绘制折线图显示单位_如何使用python语言pygal模块创建折线图并显示
  19. plc梯形图语言c1,梯形图的特点——为什么梯形图能成为PLC第一编程语言
  20. DMS疲劳驾驶监测系统

热门文章

  1. 英特尔的指令集体系结构_对标英特尔的RISC-V大有可为,CPU三分天下格局可期
  2. C++知识点32——使用C++标准库(关联容器set和multiset的初始化,赋值,查找,添加,删除与迭代器失效)
  3. OPENCV标定外参
  4. 实现MFC中Radio Button组绑定同一变量控制
  5. C# Task 循环任务_理解C#中的ValueTask
  6. Eclipse 3.5 Classic+Tomcat 6.0+MySql 5.5搭建java web开发环境
  7. 一种简单的数据库性能测试方法
  8. 东芝发布15nm SG5固态硬盘 容量高达1TB
  9. webpack - vue Component 从入门到放弃(三)
  10. Hadoop三种安装模式