ViewModel 凭什么能保存重建数据

简介: ViewModel 凭什么能保存重建数据

打开官网,我们可以看到 ViewModel 的描述:


The ViewModelclass allows data to survive configuration changes such as screen rotations.

同时还给出了 ViewModel 的生命周期图:

image.png


通过该图我们可以很清晰的看到,ViewModel 与 Activity 的生命周期几乎是一致的,但这并没有引起我的关注,我在意的是描述中的那句话, 在发生屏幕旋转时,ViewModel 实例依然存在 ,为了验证这句话,我写了一个小 demo 来佐证,示例 demo 如下:


override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        Log.e("TAG", "onCreate:$mainViewModel")
 }
  override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        Log.e("TAG", "onSaveInstanceState:")
 }
 override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        Log.e("TAG", "onRestoreInstanceState:")
 }
复制代码


操作步骤很简单,就是操作屏幕旋转,佐证下 ViewModel 实例是否仍是 Activity 旋转屏幕之前的实例,打印结果如下:


2019-08-13 21:45:55.381  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.435  E/TAG: onSaveInstanceState:
2019-08-13 21:46:06.608  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.617  E/TAG: onRestoreInstanceState:
复制代码


通过日志我们可以看到,在发生屏幕旋转时,旋转之前的 MainViewModel 与旋转后的 MainViewModel 内存地址一致,验证了官网对 ViewModel 描述的正确性。


我们知道,在屏幕发生旋转时,整个 Activity 都会被销毁和重建,与之所对应的对象和变量也都会被重新初始化,但 ViewModel 的实例并没有受之影响,重建之后仍是之前的实例,难道 ViewModel 实例被 Activity 之外的某个变量持有?带着这样的疑问我们来跟踪 ViewModel 的源码,看看是如何做到这点的。


分析


初始化 ViewModel


我们来看下 ViewModel 对象的初始化:

val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

1、跟踪 ViewModelProviders.of


public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    // 关键点:activity.getViewModelStore()
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
复制代码

默认会初始化一个 factory,该 factory 是一个反射初始化 MainViewModel::class.java 的工厂类,关键点我们稍后再说,ViewModelProvider 的初始化需要传入 activity 的 ViewModelStore 和 factory。


2、跟踪 get(MainViewModel::class.java)


public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        ...
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
  } 
  public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 1、 
        ViewModel viewModel = mViewModelStore.get(key);
    // 2、
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } 
        ...
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            //3、
            viewModel = (mFactory).create(modelClass);
        }
        //4、
        mViewModelStore.put(key, viewModel);
        //5、
        return (T) viewModel;
   }
复制代码


序列号解释:


  1. 根据 key 从 Activity 的 ViewModelStore 中取出 ViewModel
  2. 判断取出的 ViewModel 是否与 get 的 ViewModel 实例一致,是的话,直接返回 ViewModel 对象
  3. 通过 factory 来反射初始化 ViewModel 对象
  4. 将 ViewModel 存到 Activity 的 ViewModelStore 中
  5. 返回 ViewModel 对象


从整个过程来看,ViewModelStore 这个对象非常重要,ViewModel 的存储与获取都与和他有关,Activity 销毁重建也是从 ViewModelStore 中获取 ViewModel 的实例,并且这个实例一直是同一个对象。

获取 ViewModelStore


1、我们来说说关键点: activity.getViewModelStore()


FragmentActivity.class

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
} 
public ViewModelStore getViewModelStore() {
        //1、
        if (mViewModelStore == null) {
            //2、
            NonConfigurationInstances nc =
                     //3、
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            //3、
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
 }
复制代码


序列号解释:

  1. 检查全局变量 mViewModelStore 是否为空
  2. NonConfigurationInstances 是 FragmentActivity 的一个静态内部类
  3. 获取 NonConfigurationInstances 对象
  4. 如果 viewModelStore 仍为空,则创建一个 ViewModelStore

2、我们来看下 getLastNonConfigurationInstance 是如何拿到 FragmentActivity.NonConfigurationInstances


Activity.class

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
} 
public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
 }
复制代码


mLastNonConfigurationInstances 是 Activity 的一个叫 NonConfigurationInstances 的静态内部类。


3、我们来看下 mLastNonConfigurationInstances 是如何初始化的

Activity.class

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) {
 ...
 mLastNonConfigurationInstances = lastNonConfigurationInstances;  
 ... 
}
复制代码


在 Activity attach 的时候被外部传入进来,那么,是谁触发的 attach 呢?我们知道,Activity 的整个创建都是 ActivityThread 来操作的。


4、我们来看下 ActivityThread


我们找到触发 Activity.attach() 的地方


ActivityThread.class

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         ActivityInfo aInfo = r.activityInfo;
         ...
         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);
         ...
         mActivities.put(r.token, r);
         ...
 }
复制代码


我们注意到 r.lastNonConfigurationInstances 这个对象,这个对象是从 ActivityClientRecord  中取出来的,并且又将 ActivityClientRecord 存储到了 ActivityThread 的全局变量 mActivities 中。


5、我们来看看 ActivityClientRecord 是怎么来的


下面简洁下调用链:


performLaunchActivity -> handleLaunchActivity -> handleRelaunchActivityInner -> handleRelaunchActivity


最终走到了 handleRelaunchActivity 方法:


@Override
  public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
      ...
      //1、通过 Activity 的 token 获取对应的 ActivityClientRecord
      ActivityClientRecord r = mActivities.get(tmp.token);
      ...
      handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");  
      ...
  }
复制代码


最终,我们找到了 ActivityClientRecord 的来源,因此,整个获取 ViewModelStore 的调用链就出来了:


image.png


我们再回到 Activity 的 getViewModelStore 方法,只有在 ViewModelStore 为空的情况下才会走这个调用链,这么做的目的是为了避免频繁走调用链才能拿到 ViewModel 的问题,但重建后,ViewModelStore 肯定是为空的,所以,肯定是有一个地方只取一次调用链拿到 ViewModelStore 对象,结果就是在 onCreate 方法中


FragmentActivity.class

protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        NonConfigurationInstances nc =
                // 拿到调用链的 ViewModelStore 对象
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ...
复制代码


既然有获取,那必然就会有存储的过程,获取的源头是在 ActivityThread 的 mActivities 中,那存储必然也是在 mActivities 中。

存储 ViewModelStore


通过 mActivities.get 的方式,在 ActivityThread 中查找相关引用:


ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ...
     if (getNonConfigInstance) {
         try {
             // 1、
             r.lastNonConfigurationInstances
                 // 2、
                 = r.activity.retainNonConfigurationInstances();
         } catch (Exception e) {
            ...
         }
     }
    ...
复制代码


序列号解释:


  1. 该处就是 Activity attach 时传入的  Activity.NonConfigurationInstances 对象
  2. 获取 Activity.NonConfigurationInstances 对象


仔细看方法,原来是 performDestroyActivity ,也就是说,在重建 Activity 前,会将 ViewModelStore 给保存起来给 ActivityClientRecord,ActivityClientRecord 是存放在 ActivityThread 的全局 mActivities 集合中的,等到重建后,再从 mActivities 中取出 ActivityClientRecord,再把 ViewModelStore 通过 Activity 的 attach 方法再传入,这也印证了我们的 demo 和官方的示例图,不过我们还是得来看看 r.activity.retainNonConfigurationInstances(); 是怎么存储起来的


1、activity.retainNonConfigurationInstances

Activity.class

NonConfigurationInstances retainNonConfigurationInstances() {
        //1、
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        ...
        return nci;
    }
复制代码


retainNonConfigurationInstances 是一个创建 Activity.NonConfigurationInstances 的过程,根据上述调用链图可知,Activity.NonConfigurationInstances 会引用 FragmentActivity.NonConfigurationInstances 对象,引用部分在序列号 1 处 onRetainNonConfigurationInstance 方法


2、onRetainNonConfigurationInstance


FragmentActivity.class

@Override
 public final Object onRetainNonConfigurationInstance() {
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        // 1、
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
  }
复制代码


到这里就很清楚了,FragmentActivity.NonConfigurationInstances 会持有 mViewModelStore 对象进行存储。


我们来更改下官方的示例图:


image.png


目录
相关文章
|
存储 前端开发
CreationExtras 来了,创建 ViewModel 的新方式
CreationExtras 来了,创建 ViewModel 的新方式
314 0
|
Go
Go语言浮点数完全手册 float32和float64一文掌握!
Go语言浮点数完全手册 float32和float64一文掌握!
3478 0
|
21天前
|
机器学习/深度学习 人工智能 供应链
智能体人才培养方向:对接国家“AI人才战略”的能力建设体系
“智能体来了”构建分层分类培养体系,覆盖高校学生、职场转型者与企业员工,通过实训实战与认证评价,提升岗位适配率至85%,助力破解AI人才短缺难题,精准对接国家人工智能发展战略。
|
1月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
Flex 布局 布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。 2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。 一、Flex 布局是什么? Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。
220 0
|
21天前
|
人工智能 数据可视化 开发者
抖音怎么发教学智能体的视频?阿里云百炼实战指南,智能体来了教你落地​
2025年,AI智能体教学成抖音新风口。本文详解如何借助阿里云百炼平台,从搭建教学智能体、生成合规视频到SEO优化,全流程打造高搜索量教学内容,助力开发者实现技术变现与品牌曝光,抢占AI传播先机。(238字)
|
存储 Java PHP
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
210 0
|
9月前
|
图形学
Unity时间转换方式
**时间戳与 DateTime 的转换简介** 时间戳是从1970年1月1日00:00:00起的秒数,可转为 DateTime 对象。DateTime 转时间戳则是计算与1970年1月1日的时间差。秒数与时分秒格式互转基于60进制换算规则。Unity 中可通过 Time 类处理游戏时间,并与其他时间格式进行换算,需考虑时区等差异。示例代码展示了 Unity 中计时器的实现及总用时转换成时分秒的两种方法。
663 10
|
12月前
|
人工智能 JSON 自然语言处理
AppFlow全面支持Qwen2.5开源版无代码调用
Qwen2.5是阿里云推出的大型语言模型,无需编码即可快速体验。该模型基于最新大规模数据集训练,支持超29种语言,显著提升了知识量、编码及数学能力,特别是在指令遵循、长文本生成、结构化数据理解和生成等方面。通过AppFlow,Qwen2.5可轻松集成至钉钉机器人等应用,实现智能化交互。
470 4
|
Java
java之压缩流(ZipOutputStream)
  一、文件压缩,是很有必要的,我们在进行文件,传输过程中,很多时候都是,都是单个文件单个文件发送接收,但是当数据量特别大,或者文件数量比较多的时候,这个时候就可以考虑文件压缩。   二、优势:文件压缩过后,只需要进行一次文件的传输就可以了。
9420 0