【背上Jetpack】绝不丢失的状态 androidx SaveState ViewModel-SaveState 分析

标签: Jetpack

系列文章

【背上Jetpack】Jetpack 主要组件的依赖及传递关系

【背上Jetpack】AdroidX下使用Activity和Fragment的变化

【背上Jetpack之Fragment】你真的会用Fragment吗?Fragment常见问题以及androidx下Fragment的使用新姿势

【背上Jetpack之Fragment】从源码角度看 Fragment 生命周期 AndroidX Fragment1.2.2源码分析

【背上Jetpack之OnBackPressedDispatcher】Fragment 返回栈预备篇

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo

前言

大家都知道 activity 有着一套 onSaveInstanceState-onRestoreInstanceState 状态保存机制,旨在「系统资源回收」或「配置发生变化」保存状态,为用户提供更好的体验

在 androidx 下,提供了 SavedState 库帮助 activity 和 fragment 处理状态保存和恢复

本文默认您对状态保存机制有一定了解,这部分内容请移步 Saving UI States

此外,关于 android 下的进程管理,推荐 Ian Lake 的 Who lives and who dies? Process priorities on Android

本文介绍了 androidx 下 SavedState 如何帮助 activity 和 fragment 处理状态的保存和恢复,同时介绍 viewmodel-savedstate 库,以及在开发过程中正确使用状态保存的姿势

软件工程中没有什么是中间层解决不了的

在分析 SavedState 库之前我们需要简单聊一聊 ComponentActivity

androidx activity 1.0.0 时,ComponentActivity 成为了 FragmentActivityAppCompatActivity 的基类。

androidx activity 1.0.0

俗话说「百因必有果」,带着强烈的好奇心,我查了一下 ComponentActivity 引入的原因。

可以看到 ComponentActivity 继承了 androidx.core.app.ComponentActivity(在 fragment 库中),并且最初仅实现了LifecycleOwner 接口

我们创建的 activity 的继承关系现在变成了这样:

那么回到最初的问题,为什么要引入 ComponentActivity ?其实看看现在 ComponentActivity 的类结构答案就很清楚了

ComponentActivity 实现了五个接口,代表着其除了 activity 还充当着五种角色。本着职能单一原则,官方通过建立一个中间层将部分功能分别交于专门的类来负责,OnBackPressedDispatcherOwner 就是我们讲 fragment 返回栈(【背上Jetpack之OnBackPressedDispatcher】Fragment 返回栈预备篇)时提到的结构,而其中的 SavedStateRegistryOwner 则是我们今天要讲的主角 SavedState 中的成员

SavedState

引入 SavedState

implementation "androidx.savedstate:savedstate:1.0.0"

其实您不需要显示地声明,因为 activity 库内部已经引入了。jetpack 组件依赖关系可参考 【背上Jetpack】Jetpack 主要组件的依赖及传递关系

这是一个很小的库

图片来自 Android ViewModels: State persistence — SavedState

SavedStateProvider

保存状态的组件,此状态将在以后恢复并使用

public interface SavedStateProvider {
    @NonNull
    Bundle saveState();
}

SavedStateRegistry

管理 SavedStateProvider 列表的组件,此注册表绑定了其所有者的生命周期(即 activity 或 fragment)。每次创建生命周期所有者都会创建一个新的实例

创建注册表的所有者后(例如,在调用 activity 的 onCreate(savedInstanceState) 方法之后),将调用其 performRestore(state) 方法,以恢复系统杀死其所有者之前保存的任何状态。

void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
    // ...
    if (savedState != null) {
        mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
    }
    // ...
}

每个注册表的 SavedStateProvider 都由用于注册它的唯一**标识

private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>();

public void registerSavedStateProvider(@NonNull String key, @NonNull SavedStateProvider provider) {
    SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
    if (previous != null) {
        throw new IllegalArgumentException("SavedStateProvider with the given key is already registered");
    }
}

public void unregisterSavedStateProvider(@NonNull String key) {
    mComponents.remove(key);
}

一旦完成注册,就可以通过consumeRestoredStateForKey(key) 来使用特定**的还原状态

public Bundle consumeRestoredStateForKey(@NonNull String key) {
    if (mRestoredState != null) {
        Bundle result = mRestoredState.getBundle(key);
        //调用后就会清空,第二次调用返回null
        mRestoredState.remove(key);
        if (mRestoredState.isEmpty()) {
            mRestoredState = null;
        }
        return result;
    }
    return null;
}

请注意,此方法检索保存的状态,然后清除其内部引用,这意味着用相同的键调用它两次将在第二次调用中返回 null

一旦注册表恢复了其保存状态,则由提供者决定是否要求其恢复的数据。 如果没有,下次注册表的所有者被系统杀死时,未使用的还原数据将再次保存到保存状态

已注册的 provider 能够在其所有者被系统杀死之前保存状态。 发生这种情况时,将调用其 Bundle saveState() 方法。 对于每个已注册的 SavedStateProvider,都可以像这样保存状态。

savedState.putBundle(savedStateProviderKey, savedStateProvider.saveState());

performSave(outBundle) 方法的源码如下

void performSave(@NonNull Bundle outBundle) {
    Bundle components = new Bundle();
    
    // 1.保存未使用的状态
    if (mRestoredState != null) {
        components.putAll(mRestoredState);
    }
    
    // 2. 通过 SavedStateProvider 保存状态
    for (Iterator<Map.Entry<String, SavedStateProvider>> it = mComponents.iteratorWithAdditions(); it.hasNext(); ) {
        Map.Entry<String, SavedStateProvider> entry1 = it.next();
        components.putBundle(entry1.getKey(), entry1.getValue().saveState());
    }
    
    // 3. 将bundle 保存到 outBundle 对象中
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

执行状态保存将所有未使用的状态与注册表提供的状态合并。 此 outBundle 是 activity 的 onSaveInstanceState 中传入的 bundle 。

SavedStateRegistryController

一个包装 SavedStateRegistry 并允许通过其2个主要方法对其进行控制的组件:performRestore(savedState) 和 performSave(outBundle )。 这两个方法将内部通过 SavedStateRegistry 中的方法处理 。

public final class SavedStateRegistryController {
    private final SavedStateRegistryOwner mOwner;
    private final SavedStateRegistry mRegistry;

    public void performRestore(@Nullable Bundle savedState) {
        // ...
        mRegistry.performRestore(lifecycle, savedState);
    }

    public void performSave(@NonNull Bundle outBundle) {
        mRegistry.performSave(outBundle);
    }
}

SavedStateRegistryOwner

持有 SavedStateRegistry 的组件。 默认情况下,androidx 包中的ComponentActivityFragment 都实现此接口。

public interface SavedStateRegistryOwner extends LifecycleOwner {
    @NonNull
    SavedStateRegistry getSavedStateRegistry();
}

Activity 的状态保存

这里我们要明确一件事情,activity 保存的状态究竟都有什么?

这部分内容可以参见 官方文档

简单来说,activity 的状态保存分为 view 状态和成员状态

默认情况下,系统使用 Bundle 实例状态来保存有关 activity 布局中每个 View 对象的信息(例如,输入到 EditText 中的文本值或 recyclerview 的滚动位置)。 因此,如果 activity 实例被销毁并重新创建,则布局状态将恢复为之前的状态,而无需您执行任何代码。(注意,需要恢复状态的 view 需要配置 id

这部分逻辑在 activity 中的 onSaveInstanceState 方法内实现

onSaveInstanceState

不同平台 onSaveInstanceState 方法的执行时机稍有不同,android P 之前 onSaveInstanceState 执行在 onStop 之前,但不限于在 onPause 之前或之后。android P 及之后该方法在 onStop 后执行

前面我们提到 ComponentActivity 实现了 SavedStateRegistryOwner ,下面我们来看一看 activity 如何利用该库来实现状态的保存与恢复

public class ComponentActivity extends androidx.core.app.ComponentActivity implements SavedStateRegistryOwner {

    private final SavedStateRegistryController mSavedStateRegistryController = SavedStateRegistryController.create(this);
  
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSavedStateRegistryController.performRestore(savedInstanceState);
        // ...
    }
  
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        // ...
        //这里先调用父类的 onSaveInstanceState 保存 view 状态
        super.onSaveInstanceState(outState);
        mSavedStateRegistryController.performSave(outState);
    }
  
    @NonNull
    @Override
    public final SavedStateRegistry getSavedStateRegistry() {
        return mSavedStateRegistryController.getSavedStateRegistry();
    }
}

其内部持有 SavedStateRegistryController 的实例 mSavedStateRegistryController ,在 activity 的 onCreate 方法中 通过 controller 的 performRestore 方法来查询已保存的状态,在 onSaveInstanceState 中 使用 controller 的 performSave 方法来保存状态

除了 view 状态和成员状态,activity 还负责保存其内部的 fragment 的状态FragmentActivityonSaveInstanceState 方法有对其内部 fragment 的状态进行保存,并在 onCreate 方法中对已保存的 fragment 进行恢复。这解释了如果操作不当会导致 fragment 重叠的问题

Fragment 的状态保存

androidx fragment 使用 FragmentStateManager 来处理 fragment 的状态保存

其内部有四个保存相关的方法

  • saveState
  • saveBasicState
  • saveViewState
  • saveInstanceState

FragmentStateManager

其调用链为 activity 通过 FragmentController 间接 调用 FragmentManagersaveAllState,接着依次调用后面的save 方法

Fragment 的状态保存可分为 view 状态,成员状态,child fragment 状态

关于 view 状态 , FragmentStateManager 提供了 saveViewSate 方法,它的调用有两处:

  1. 在 activity 或父 fragment 触发状态保存时调用,即上述流程
  2. 在 fragment 即将进入 onDestroyView 生命周期时调用,其位置在 FragmentManager moveToState 方法内部,这解释了为什么加入返回栈的 replace 操作在返回时 view 状态可以自动恢复

关于成员状态,由 activity 中的状态机制处理,即上节内容

关于 child fragment 状态,fragment 的 onCreate 方法会调用 restoreChildFragmentState 来恢复 child fragment 的状态,并在 FragmentStateManager 中的 saveBasicState 方法中 调用 performSaveInstanceState 来保存 child fragment 的状态

Viewmodel-SavedState

2020-01-22,ViewModel-SavedState 1.0.0 正式版发布,02-05 发布了 2.2.0 正式版

 implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"

您不需要手动引入该库,因为 fragment 库已经在内部引入该库

Jetpack MVVM 下 UI State 通常被 ViewModel 持有并存储,因此该模块出现了,配置该模块后,ViewModel 对象将通过其构造函数接收 SavedStateHandle 对象(键值映射),可让您保存状态并查询已保存的状态。 这些值将在系统终止进程后继续存在,并可以通过同一对象使用。

ViewModel-SavedState

图片来自 Android ViewModels: State persistence — SavedState

SavedStateHandle

内部持有已保存状态 key-value 的 map,允许读取和写入状态,这些状态在应用进程被杀死后仍然存在

SavedStateHandle 通过 ViewModel 的构造器传入,下面是其主要的主要的几个方法

  • T get(String key)
  • MutableLiveData getLiveData(String key)
  • void set(String key, T value)

SavedStateHandle 还包含 SavedStateProvider 的实例,用于帮助 ViewModel 的 owner 保存状态

AbstractSavedStateViewModelFactory

一个实现 ViewModelFactory.KeyedFactoryViewModel Factory,它会创建一个与实例化的请求的 ViewModel 关联的 SavedStateHandle

public abstract class AbstractSavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {
  
    private final SavedStateRegistry mSavedStateRegistry;
  
    // Default state used when the saved state is empty
    private final Bundle mDefaultArgs;

    @Override
    public final <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        // 读取保存的状态
        Bundle restoredState = mSavedStateRegistry.consumeRestoredStateForKey(key);
      
        // 创建保存状态的 handle
        SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, mDefaultArgs);
        
        // ... 
      
        // 创建 viewModel
        T viewmodel = create(key, modelClass, handle);
      
        // ... 

        return viewmodel;
    }
}

SavedStateViewModelFactory

AbstractSavedStateViewModelFactory 的具体实现

public final class SavedStateViewModelFactory extends AbstractSavedStateVMFactory {

    public SavedStateViewModelFactory(@NonNull Application application,
            @NonNull SavedStateRegistryOwner owner) {
        this(application, owner, null);
    }

    public SavedStateViewModelFactory(@NonNull Application application, @NonNull SavedStateRegistryOwner owner, @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    
	public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }

        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);        
        T viewmodel;
        if (isAndroidViewModel) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
        return viewmodel;
        //...
    }
}

工作流程

ViewModelProvider(this).get(MyViewModel::class.java)

在 activity 中创建 ViewModel 实例,传入 this (SavedStateRegistryOwner )作为参数,该参数可以访问其 SavedStateRegistry,如果没有传入 factory 会通过 activity 重写的 getDefaultViewModelProviderFactory 方法来获取默认的 factory 。然后 factory 将使用保存的状态, 将其包装在 SavedStateHandle 中,并将其传递给 ViewModel。 ViewModel 可以读取和写入该 handle

当 activity 的 onSaveInstanceState(outState) 方法被调用,其 SavedStateRegistryperformSave(outState) 方法将被执行,其内部的所有 SavedStateProvidersaveState 方法均被执行,一旦执行完毕,outState 就包含了已保存的状态

当 app 被重启后,activity 和新的 registry 将被创建,activity 的 onCreate(savedInstanceState) 方法会被调用,然后 registry 的 performRestore(savedInstanceState) 将被调用以便恢复之前保存的状态

状态保存的正确姿势

ViewModel 构造器加入 SavedStateHandle 参数,并将想要保存的数据使用该 handle 保存

class WithSavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
    private val key = "key"
    fun setValue(value: String) = state.set(key, value)
    fun getValue(): LiveData<String> = state.getLiveData(key)
}

无需重写 onSaveInstanceState/onRestoreInstanceState 方法

运行示意图

Demo 地址

SavedState 仅适合保存轻量级的数据,重量级操作请考虑使用 sp,数据库等持久化方案


关于我


我是 Fly_with24

版权声明:本文为fly_with_24原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fly_with_24/article/details/105054671

智能推荐

2018.8.27

2018.8.27...

HTML 表单元素的基本样式

HTML 表单元素的基本样式 原创 ixygj197875 发布于2018-02-22 17:48:53 阅读数 2296 收藏 更新于2018-05-20 15:35:58 分类专栏: 揭秘 CSS 揭秘 CSS 收起 表单元素主要包括 label、input、textarea、select、datalist、******、progress、meter、output等,以及对表单元素进行分组的 ...

php输出语句

php输出语句 常见的输出语句 echo(): 可以一次输出多个值,多个值之间用逗号分隔。echo是语言结构(language construct),而并不是真正的函数,因此不能作为表达式的一部分使用。 print(): 函数print()打印一个值(它的参数),如果字符串成功显示则返回true,否则返回false。 print_r(): 可以把字符串和数字简单地打印出来,而数组则以括起来的键和值...

工厂模式

简介 常见的实例化对象模式。 用工厂方法替代new操作的一种模式。 当我们使用new操作实例化对象时,调用构造函数完成初始化。若初始化仅是进行赋值等简单的操作,写入构造函数即可。但如果初始化时需要执行一长串复杂的代码,将多个工作装入一个方法,是不妥的。 创建实例与使用实例分离。将创建实例所需的大量初始化工作从基类的构造函数中分离出去。 简单工厂模式、工厂方法模式针对的是一个产品等级结构;而抽象工厂...

B1105 Spiral Matrix (画图)

B1105 Spiral Matrix (25分) //第一次只拿了21分 矩阵的长和宽,求最大因子,从sqrt(num)开始枚举. 每次循环一次,s++,t--,d--,r++ 测试点四运行超时,是因为输入一个数字的时候,需要直接输出这个数字。//1分 测试点二运行超时,最后一个数字不必再while循环一次,直接输出即可。//3分 最后一个测试点卡了好久/(ㄒoㄒ)/~~ 螺旋矩阵...

猜你喜欢

Java基础=>String,StringBuffer与StringBuilder的区别

字符串常量池 什么是字符串常量池? JVM为了减少字符串对象的重复创建,其维护了一块特殊的内存,这段内存被称为字符串常量池(存储在方法区中)。 具体实现 当代码中出现字符串时,JVM首先会对其进行检查。 如果字符串常量池中存在相同内容的字符串对象,如果有,则不再创建,直接返回这个对象的地址返回。 如果字符串常量池中不存在相同内容的字符串对象,则创建一个新的字符串对象并放入常量池,并返回新创建的字符...

java调用其他java项目的Https接口

项目中是这样的: 用户拿出二维码展示,让机器识别二维码, 机器调用开门的后台系统接口, 然后开门的后台系统接口需要调用管理系统的接口, 管理系统需要判断能不能开门.这两个系统是互相独立的.当时使用http调用是没有问题的.当时后来要求必须用https.废话不说,直接代码: 我的项目中调用的是 HttpsUtils.Get(utlStr) 这个接口 开门系统接口如下图:   管理系统的接口...

Hadoop1.2.1全分布式模式配置

一 集群规划 主机名            IP                               安装的软件 &nbs...

Go语言gin框架的安装

尝试安装了一下gin,把遇到的一些小问题来记录一下 安装步骤 首先来看看官方文档,链接点这里 可以看到安装步骤很简单,就一句话 在命令行中输入这句话运行等待就好。 问题来了,因为墙的问题,go get会很慢,所以命令行里面半天什么反应也没有,不要急,慢慢等着就会看到gin-gonic/gin这个目录出现 这个时候命令行还是没有结束,表示还在下一些东西。有的时候可能心急的人就停了(比如我),然后写个...

uni-app表单组件二

input(输入框) 属性名 类型 说明 平台差异 value String 输入框的初始内容 type String input 的类型 password Boolean(默认false) 是否是密码类型 placeholder String 输入框为空时占位符 placeholder-style String 指定 placeholder 的样式 placeholder-class Strin...