05 SavedState组件架构原理解析

前言

在拥抱了Jetpack之后,我们通常使用ViewModel组件来管理数据,但ViewModel只能当页面因配置变更而重建时才能复用,但如果是内存不足或者电量不足等系统原因导致的页面被回收时ViewModel是不会被复用的。

我们都知道Activity有着一套 onSaveInstanceState-onRestoreInstanceState 状态保存机制,旨在页面因系统原因被回收时可以保存转改,页面重建后,可以恢复之前的状态,为用户提供更好的体验。

但是ViewModel是无法直接感知onSaveInstanceState被处罚的时机的。

于是乎,SavedState这个中间组件就诞生了,它能够帮助开发者在ViewModel中处理Activity和fragment状态保存和恢复。

什么是SavedState?

在页面即将被销毁的时候,每个使用SavedState的ViewModel都会创建一个Bundle来存储自己的这份数据,最后这些Bundle会被汇总到一个Bundle中,然后再保存到onSaveInstanceState(Bundle outState)的outState中。

当页面回复的时候,会从onCreate(Bundle savedInstanceState)中的savedInstanceState中取出原来存放的总的那个Bundle,然后再取出一个个的术语ViewModel的子Bundle,于是我们就能愉快的在ViewModel中复用之前存储的数据了。

其实就是利用Bundle可以保存另一个Bundle这么一个特点,分层分区保存数据,让数据之间相互分离,进而方便整存整取。

SavedState的用法

首先还是添加依赖:

api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
ViewModel搭配SavedState实现数据复用

当Activity的onSaveInstanceState方法被执行的时候,这个实际会触发SavedStateHandle的Bundle saveState()方法,所以它的数据才能被存储。

当Activity被恢复的时候,在新建ViewModel实例对象的时候,会从Activity的savedInstanceState中提取之前存储数据,然后构造SavedStateHandle对象并把恢复的数据赋值给它,随后把SavedStateHandle传递到ViewModel中。所以它的数据才能被复用。

//我们只需要在构造函数上添加SavedStateHandle参数即可。其他不变
  class HiViewModel(val savedState: SavedStateHandle) : ViewModel() {
    private val KEY_HOME_PAGE_DATA="key_home_page_data"
    private val initData = MutableLiveData<List<GoodsModel>>()
    fun loadInitData():LiveData<List<GoodsModel>> {
       if(initData.value==null){
            //1.from memory .
            //加载数据的时候,先从savedState中尝试读取,如果有直接内存复用
            val memoryData =savedState.get<List<GoodsModel>>(KEY_HOME_PAGE_DATA)
            if(memoryData!=null){
             initData.postValue(memoryData)
          }else{
             //2.from remote
             val remoteData = fetchDataFromRemote()
             //然后存储在savedState以备不时之需,数据模型需要使用parceable接口
             //这种写法,即便页面因配置变更,内存不足被回收
             //页面重建后,我们都能第一时间复用之前的数据,从而快速渲染页面
             //这种能力,对于一级页面尤其首页是至为重要的
             savedState.set(KEY_HOME_PAGE_DATA,remoteData)
             initData.postValue(remoteData)
          }
        }
      return initData
    }
  }

问题是:我们为什么可以直接在ViewModel构造函数上直接添加SavedStateHandle参数呢?这个问题我们后面回答。

SavedState数据存储&复用实现原理
从上面介绍上来理解的话,我认为SavedState还是比较简单,思路清晰的,但是谷歌搞了一堆设计模式,这个流程原理会涉及到几个类。所以先捋一捋各种关系,以及它们各自的职责:

  • SavedStateRegistryOwner:用于获取SavedStatedRegistry对象;
  • SavedStateRegistryController:用于创建SavedStatedRegistry,用于连接Activity/Fragment和SavedStateRegistry;
  • SavedStateRegistry:数据存储、恢复中心;
  • SavedStateHandleController: 用于从数据中心提出数据并创建SavedStateHandle的;
  • SavedStateHandle:单个ViewModel用于数据存储和恢复的地方。
SavedStateRegistry模型

一个总Bundle,以key-value形式存储着每个ViewModel对应的子Bundle。这是为了方便整存整取。


SavedState数据存储流程

逐一调用每个SavedStateHandle保存自己的数据。汇总成一个总的Bundle,再存储到Activity的SavedState对象中。



SavedStateHandle参数是如何被传递到ViewModel的?

现在我们来回答刚才提出的问题,为什么可以直接在ViewModel构造函数上直接添加SavedStateHandle参数呢?,这里分成两部,先看下面两张流程图。

SavedState数据复用流程

从Activity的onCreate(Bundle savedState)恢复所有ViewModel的数据到SavedStateRegistry。这一部只是提取出存储的Bundle数据对象,但还没有被复用。

SavedState数据复用流程(2)

创建ViewModel实例的时候会从SavedStateRegistry获取当前ViewModel之前存储的数据并赋值给SavedStateHandle对象。并将SavedStateHandle以参数的形式传递到ViewModel完成复用。

接下来是源码分析,这一切还要从ViewModelProvider构造函数说起:

class ViewModelProvider{
      public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
      //这里他会判断我们的Activity/Fragment是不是 HasDefaultViewModelProviderFactory类型的
      //而恰巧我们的Activity/Fragment都是实现了这个接口的,并且返回的都是SavedStateViewModelFactory实例对象
      //所以,当我们调用这个构造函数创建ViewModelProvider,来获取ViewModel的时候
      //都是由SavedStateViewModelFactory负责具体的创建以及参数传递的。
            this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
}

getDefaultViewModelProviderFactory默认返回SavedStateViewModelFactory

class ComponentActivity extends HasDefaultViewModelProviderFactory{
 public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
}

SavedStateViewModelFactory:创建ViewModel实例的工作

public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
//1.首先判断我们的viewmodel 是不是 AndroidViewModel的子类
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel) {
          //2.如果是AndroidViewModel的子类,那么尝试查找它是否拥有(Application,SavedStateHandle)两个参数的构造函数
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            //3.否则查找它是否拥有(SavedStateHandle)一个参数的构造函数
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            //4. 如果上面两种方式都没找到,说明我们的viewmodel没有声明以上两种类型    
            //此时这里使用AndroidViewModelFactory创建viewmodel实例
            //可以无参数,也可以携带一个(Application)参数
            return mFactory.create(modelClass);
        }

        //5. 走到这里,那么就说明构造函数上存在savedSateHandle参数
        //这里会首先构建出SavedStateHandleController对象
        //这个类的作用是 从SavedStateRegistry提取出该ViewModel的Bundle数据对象,创建savedSateHandle对象
        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);

            T viewmodel;
            if (isAndroidViewModel) {
                //这里反射构造ViewModel实例对象的时候,把参数一同传递了进去。 
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
            //和上面唯一的不同就是没有Application参数。
                viewmodel = constructor.newInstance(controller.getHandle());
            }
             //返回新创建的ViewModel实例对象
             return viewmodel;
    }

SavedStateHandleController提取旧数据并创建SavedStateHandle:

static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
            String key, Bundle defaultArgs) {
        //从数据中心SavedStateRegistry提出出该ViewModel的Bundle数据对象    
        Bundle restoredState = registry.consumeRestoredStateForKey(key);
        //构建SavedStateHandle对象,并把restoredState中的数据再拆分出来,存储到mRegular这个 map集合中。
        SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
        SavedStateHandleController controller = new SavedStateHandleController(key, handle);
        //.....
        return controller;
    }
SavedStateHandle getHandle() {
        return mHandle;
    }
总结
  • ViewModel搭配SavedState组件,可以实现非正常关闭情况的数据存储与复用,这对于一级页面,尤其是首页模块及其重要
  • SavedState本质是利用了onSaveInstanceState的时机。每个ViewModel的数据单独存储在一个Bundle中,再合并成一个整体。再存放到outBundle中。所以它也不能存超过1M的数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335