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

7,267 阅读9分钟

系列文章

【背上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 成为了 FragmentActivity 和 AppCompatActivity 的基类。

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 包中的ComponentActivity 和 Fragment 都实现此接口。

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

Activity 的状态保存

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

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

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

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

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

onSaveInstanceState

关于 saveInstanceState() 的调用时机

  • targetSdkVersion 为 28 及以后:在 onStop()后 执行
  • targetSdkVersion 高于 11 且低于28:在 onStop()前 执行
  • targetSdkVersion 低于 11:在 onPause() 前 执行

前面我们提到 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 的状态。FragmentActivity 的 onSaveInstanceState 方法有对其内部 fragment 的状态进行保存,并在 onCreate 方法中对已保存的 fragment 进行恢复。这解释了如果操作不当会导致 fragment 重叠的问题

Fragment 的状态保存

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

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

  • saveState
  • saveBasicState
  • saveViewState
  • saveInstanceState

FragmentStateManager

其调用链为 activity 通过 FragmentController 间接 调用 FragmentManager 的 saveAllState,接着依次调用后面的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.KeyedFactory 的 ViewModel 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) 方法被调用,其 SavedStateRegistry 的 performSave(outState) 方法将被执行,其内部的所有 SavedStateProvider 的 saveState 方法均被执行,一旦执行完毕,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