Android开发SavedState Jetpack状态保存利器

目录
  • 背景
  • SavedState的登场
  • 理解SavedState
    • 用法
    • SavedState组成概念
    • 原理探究
  • 最后

背景

在我们android开发中,如果需要actiivty/fragment等有状态的控件保存当前状态,由系统进行数据保存的恢复的时候

比如正常的暗黑模式切换/后台时低内存系统回收等等,都需要我们对当前的用户数据进行保存,不然下次重新恢复的时候,就会出现丢失数据的情况,给用户造成不太好的体验

一般都会重写onSaveInstanceState方法进行,在里面的Bundle对象进行数据的写入,然后会在onCreate阶段或者onRestoreInstanceState阶段进行数据的恢复!这套生命周期框架一直是深深嵌入在我们的开发习惯中!

但是随着项目的迭代,也随着业务的发展,我们可以发现,在页面复杂度提高的同时,在onSaveInstanceState里面需要保存的数据也是越来越多这就带来了几个问题,仅举例

  • onSaveInstanceState存储数据复杂,可能多人迭代就存在重复存取的现象,造成代码结构问题与隐藏bug的风险
  • 不可复用,比如activity1需要存储的数据,刚好activity2也需要存储,这个时候就只能写两份代码,以此类推
  • 没有统一的管理层,即数据的维护可能需要团队的代码规范

SavedState的登场

为了解决这些历史android的设计问题,也为了更方便广大开发者进行更好的代码结构解耦设计,所以google大哥在jetpack库中,推出了SavedState,它的定位是在Android开发中,编写可插入组件,以在进程终止时保存界面状态,并在进程重启时恢复界面状态。

虽然Savedstate推出来一段时间了,但是却一直处在“默默无闻”的状态,可能的原因有很多,比如日常开发很少接触状态保存,大部分app中真正需要保存状态的其实是很少一部分,很多app甚至是不写状态保存逻辑的,被系统回收就回收掉了,重建就是了(即使丢失了)。

还有就是学习资料比较少,现在基本找不到SavedState的相关资料(区别于我们viewmodel常用的SavesStateHandle)。

官方的demo例子也没有,所以Savedstate很长一段时间都是处于雪藏的状态。但是!为了让我们的app拥有更加好的体验,同时也提高我们的技术视野,学习Savedstate还是很有必要的,不然官方也不会白白将其加入jetpack系列。

理解SavedState

用法

深入理解之前呢,我们必须要会用不是嘛!我们下面来看一下例子

在Activity onCreate方法中执行以下代码

注意:savedStateRegistry.isRestored在onCreate之后就会变成true,

也就是说,我们必须在ComponentActivity的onCreate走完之后才能用,

原理看下边的解析

if(savedStateRegistry.isRestored){
    val consumeRestoredStateForKey = savedStateRegistry.consumeRestoredStateForKey("test")
    val test = consumeRestoredStateForKey?.getInt("test")
    Log.i("hello","tag test is $test")
}
savedStateRegistry.registerSavedStateProvider("test",DataProvider())
class DataProvider: SavedStateRegistry.SavedStateProvider {
    override fun saveState(): Bundle {
        return Bundle().apply {
            this.putInt("test",1)
        }
    }
}

如果对上诉程序不理解,不要紧,我们会接下来讲解!运行上诉程序,在app一开始启动的时候,log输出就是null,这个时候我们退到后台(可以设置不保留活动),再次打开的时候,可以看到activity被回收重建,这个时候log输出的却是1,这里就验证了SavedState具有保存数据的能力。

在demo中savedStateRegistry其实是ComponentActivity的一个对象,我们接下里分析一下,SavedState的概念组成

SavedState组成概念

SavedState库中,有以下几个概念

SavedStateRegistryOwner SavedStateRegistryController SavedStateRegistry SavedStateProvider
保存状态拥有者,用于提供声明周期的同步操作 控制器,相当于一个连接角色,用于连接SavedStateRegistryOwner与SavedStateRegistry 数据管理者,用于提供对存储数据的相关操作 数据提供者,用于提供存储的数据

我们再来看一下依赖关系,可以看到这个是单向依赖

看到这里,我们就对SavedState有了个初步的认识,这个时候就可以再回到demo了,首先我们的Activity是继承了ComponentActivity,而ComponentActivity其实是实现了SavedStateRegistryOwner接口的

所以我们的数据存储过程发起者都是由该activity的生命周期决定,而ComponentActivity里面拥有一个SavedStateRegistryController对象

我们可以通过SavedStateRegistryController对象,获取到SavedStateRegistry

构成已经很清楚了,我们再来解释一下上面的用法,其实分为两步:

  • savedStateRegistry.registerSavedStateProvider,通过savedStateRegistry的registerSavedStateProvider方法注册一个数据保存集合,第一个参数就是自定义的key,第二个参数为实现SavedStateProvider接口的类,即DataProvider
  • savedStateRegistry.consumeRestoredStateForKey可以获取当前的存储的数据集合,参数为我们自定义的key,如果当前存在key对应的数据,就返回具体存储的数据(发生在重组时),如果不存在就返回null(发生在首次进入的时候)。值得注意的一个点是,如果我们没有调用unregisterSavedStateProvider(删除对应key的存储数据)方法,那么除了首次进入之外,每次系统回收都会帮我们恢复在registerSavedStateProvider创建时的数据集合。

到这里,用法其实就很明确了,通过以上的分层,我们就可以把原本耦合在onSaveInstanceState的数据存储逻辑,变成了一个个SavedStateProvider,方便了后期数据的管理与复用,即像插件一样我们随时可以替换具体的逻辑而不影响业务本身。

原理探究

我们来看一下registerSavedStateProvider究竟做了些什么

@MainThread
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");
    }
}
private SafeIterableMap<String, SavedStateProvider> mComponents =
        new SafeIterableMap<>();

可以看到,其实就是在mComponents里面放了一个SavedStateProvider对象,当前,如果我们之前存放过了,再次存放就会抛出异常,而mComponents,其实就是一个map对象。

那么我们SavedStateRegistry什么时候才触发这个存储逻辑呢?其实在performSave方法里面

@MainThread
void performSave(@NonNull Bundle outBundle) {
    Bundle components = new Bundle();
    if (mRestoredState != null) {
        components.putAll(mRestoredState);
    }
    for (Iterator<Map.Entry<String, SavedStateProvider>> it =
            mComponents.iteratorWithAdditions(); it.hasNext(); ) {
        Map.Entry<String, SavedStateProvider> entry1 = it.next();
        // 触发了SavedStateProvider的saveState方法
        components.putBundle(entry1.getKey(), entry1.getValue().saveState());
    }
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

这里有个有趣的点,它接受一个外来的Bundle,在外来的Bundle里面,再次存了一个子Bundle,而这个子Bundle,其实就是我们上文的mComponents的数据,而对应的key是一个常量

private static final String SAVED_COMPONENTS_KEY =
        "androidx.lifecycle.BundlableSavedStateRegistry.key";

存放的子Bundle通过遍历的方式,触发了SavedStateProvider的saveState方法,获取到我们实际上想要保存的数据。 那么是谁调用了SavedStateRegistry的performSave方法呢?当然就是 SavedStateRegistryController啦,我们说过它其实是个中间角色,大部分操作需要经过它才能调用到SavedStateRegistry

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

而SavedStateRegistryController的performSave方法,真的被调用起来,就是在ComponenntActivity中

@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    Lifecycle lifecycle = getLifecycle();
    if (lifecycle instanceof LifecycleRegistry) {
        ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
    }
    super.onSaveInstanceState(outState);
   被调用
   mSavedStateRegistryController.performSave(outState);
    mActivityResultRegistry.onSaveInstanceState(outState);
}

到这里我们应该豁然开朗了,其实还是换汤不换药,都是在onSaveInstanceState里面进行的逻辑保存,只不过这一层被SavedState给封装起来了,同时加入到了androidx中,也算是官方想要重新完善onSaveInstanceState架构的体现。

看到这里了,我们也就能解释存放在SavedStateProvider的数据,其实就存放在了onSaveInstanceState的Bundle中,key为SAVED_COMPONENTS_KEY的子Bundle中。

我们再来看一下consumeRestoredStateForKey,取数据的逻辑

@MainThread
@Nullable
public Bundle consumeRestoredStateForKey(@NonNull String key) {
    if (!mRestored) {
        throw new IllegalStateException("You can consumeRestoredStateForKey "
                + "only after super.onCreate of corresponding component");
    }
    if (mRestoredState != null) {
        Bundle result = mRestoredState.getBundle(key);
        mRestoredState.remove(key);
        if (mRestoredState.isEmpty()) {
            mRestoredState = null;
        }
        return result;
    }
    return null;
}

可以看到,只有mRestored为true的时候,我们才能真正进入到取数的逻辑,否则抛出异常,因为我们能够获取到数据的前提是,系统帮我们把数据进行恢复之后!而mRestored被赋值的时候,其实就在performRestore中

@SuppressWarnings("WeakerAccess")
@MainThread
void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
    if (mRestored) {
        throw new IllegalStateException("SavedStateRegistry was already restored.");
    }
    if (savedState != null) {
        mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
    }
    ....
    mRestored = true;
}

有了上面存数据的逻辑,我们很容易知道,performRestore的调用者,最终肯定是由实现了 SavedStateRegistryOwner接口的ComponentActivity发起调用

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // Restore the Saved State first so that it is available to
    // OnContextAvailableListener instances
   //这里就是恢复数据来源
   mSavedStateRegistryController.performRestore(savedInstanceState);
    mContextAwareHelper.dispatchOnContextAvailable(this);
    super.onCreate(savedInstanceState);
    mActivityResultRegistry.onRestoreInstanceState(savedInstanceState);
    ReportFragment.injectIfNeededIn(this);
    if (mContentLayoutId != 0) {
        setContentView(mContentLayoutId);
    }
}

最后我们再给出具体的流程图,存数据和取数据的逻辑:

最后

通过SavedState,我们也能够看到jetpack官方希望改变历史android架构的决心,也想要提供更加便捷优雅的方式提供给开发者。看到这里了,也希望我们在日常开发中,可以运用到SavedState进行数据保存恢复,别让它继续雪藏啦!毕竟连依赖都不需要导入呢 ! 更多关于SavedState Jetpack状态保存的资料请关注我们其它相关文章!

(0)

相关推荐

  • Jetpack Compose实现列表和动画效果详解

    目录 创建一个列表消息卡片 可交互的动画效果 创建一个列表消息卡片 到目前为止,我们只有一个消息的卡片,看上去有点单调,所以让我们来改善它,让它拥有多条信息.我们需要创建一个能够显示多条消息的函数.对于这种情况,我们可以使用 Compose 的 LazyColumn 和 LazyRow.这些 Composable 只渲染屏幕上可见的元素,所以它们的设计对于长列表来说很有效果.同时,它们避免了 RecyclerView 与 XML 布局的复杂性. import androidx.compose.f

  • Android Jetpack组件Navigation导航组件的基本使用

    目录 1.Navigation 基本概念 2.Navigation 使用入门 2.1 添加Navigation依赖 2.2 创建导航图 2.3 导航图中添加目的地Fragment 2.4 Activity添加 NavHost 2.5 LoginFragment 代码编写 2.6 welcomeFragment 代码编写 总结 本篇主要介绍一下 Android Jetpack 组件 Navigation 导航组件的 基本使用 当看到 Navigation单词的时候 应该就大概知道 这是一个关于导航

  • Android Jetpack Compose实现列表吸顶效果

    目录 stickyHeader 实体类 加载假数据 吸顶标题 二级条目 完整代码 效果图 安卓传统的 Recyclerview 打造悬浮头部StickyHeader的吸顶效果,十分麻烦,而在Compose中就简单多了 stickyHeader Compose设计的时候考虑得很周到,他们提供了stickyHeader 作用就是添加一个粘性标题项,即使在它后面滚动时也会保持固定.标头将保持固定,直到下一个标头取而代之. 参数key - 表示唯一的密钥键. 它不允许对列表出现使用相同的键.密钥的类型应

  • Android Jetpack Compose无限加载列表

    目录 前言 方法一: paging-compose 方法二:自定义实现 添加 LoadingIndicator 总结 前言 Android 中使用 ListView 或者 RecycleView 经常有滚动到底部自动 LoadMore 的需求,那么在 Compose 中该如何实现呢? 两种方法可供选择: 基于 paging-compose 自定义实现 方法一: paging-compose Jetpack 的 Paging 组件提供了对 Compose 的支持 dependencies { ..

  • 利用Jetpack Compose实现主题切换功能

    目录 前言 color.kt Theme.kt 关于compositionLocalOf 完整代码 前言 新建的Compose项目默认的 Material 主题为我们提供了一些颜色,但对我这种花里胡哨的人来说根本不够呀. 所以系统提供的主题不能满足需求时候可以自己配置主题 compose 实现换肤很简单 之前xml方法可复杂了 通过LayoutInflater调用inflate方法加载XML布局,在inflate方法中有一个createViewFromTag,再根据LayoutInflater当

  • Android开发SavedState Jetpack状态保存利器

    目录 背景 SavedState的登场 理解SavedState 用法 SavedState组成概念 原理探究 最后 背景 在我们android开发中,如果需要actiivty/fragment等有状态的控件保存当前状态,由系统进行数据保存的恢复的时候 比如正常的暗黑模式切换/后台时低内存系统回收等等,都需要我们对当前的用户数据进行保存,不然下次重新恢复的时候,就会出现丢失数据的情况,给用户造成不太好的体验 一般都会重写onSaveInstanceState方法进行,在里面的Bundle对象进行

  • 实例探究Android开发中Fragment状态的保存与恢复方法

    我们都知道,类似 Activity, Fragment 有 onSaveInstanceState() 回调用来保存状态. 在Fragment里面,利用onSaveInstanceState保存数据,并可在onActivityCreated里面恢复数据. public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ... if (savedInsta

  • Android开发Jetpack组件Lifecycle原理篇

    目录 前言 1.Lifecycle的生命周期状态事件和状态 2.Lifecycle如何观察Activity和Fragment的生命周期 前言 在上一篇文章中,我们学习了如何去使用Lifecycle: 当然之会使用是不够的,还需要了解它的原理,这是成为优秀工程师必备的:这篇文章就来学习Lifecycle的基本原理 1.Lifecycle的生命周期状态事件和状态 **Lifecycle使用两个枚举来跟踪其关联组件的生命周期状态,这两个枚举分别是Event和State:**State指的是Lifecy

  • Android编程实现状态保存的方法分析

    本文实例讲述了Android编程实现状态保存的方法.分享给大家供大家参考,具体如下: 1.当我们正在发短信的时候,已经写了几百字了,这时突然来了一个电话,我们接完电话之后,如果发现辛辛苦苦的几百字不见了,那可就火大了,而实际上这些内容都是保存了的.在我们接电话的过程中,我们发信息的那个Activity是可能会被系统回收的,这时会调用Activity的onSaveInstanceState回调方法,而我们就可以在这个方法中保存状态数据,在onCreate方法或者在2.0之后提供的回调方法onRes

  • Android开发Jetpack组件WorkManager用例详解

    目录 一.简介 二.导入 三.基本使用 3.1 定义后台任务 3.2 配置任务运行条件 3.2.1 只需执行一次的任务 3.2.2 周期性执行的任务 3.3 将任务传给 WorkManager 四.高级配置 4.1 设置任务延迟执行 4.2 给任务添加标签 4.3 取消任务 4.3.1 根据标签取消任务 4.3.2 根据 request 的 id 取消任务 4.3.3 取消所有任务 4.4 任务重试 4.5 监听任务结果 4.6 传递数据 4.7 链式任务 一.简介 WorkManager 用于

  • Android开发Jetpack组件ViewModel使用讲解

    目录 前言 ViewModel概述 ViewModel使用 ViewModel源码 前言 学习ViewModel之前首先我们得简单了解下MVP和MVVM,因为ViewModel是MVVM中的一个元素 MVP MVVM 在MVP中View想要调用Model数据层,需要经过中间层Presenter, 这样就实现了View和Model的解耦,这也是MVP和MVC的差别: 但是如果一个Activity中有太多交互,那么我们的View接口数量就会很庞大达到十几个也不足为奇,并且在View层调用了Prese

  • Android开发Jetpack组件Lifecycle使用篇

    目录 1.为什么需要Lifecycle 2.如何使用Lifecycle 2.1 依赖Lifecycle库 2.2 Lifecycle基本用法 3.Lifecycle应用举例 3.1 Activity中使用 3.2 MVP中使用 4.自定义LifecycleOwner 1.为什么需要Lifecycle 在应用开发中,处理Activity或者Fragment组件的生命周期相关代码是必不可免的: 官方文档中举了一个例子,这里简化一下,在Activity中写一个监听,在Activity的不同生命周期方法

  • Android开发Jetpack组件LiveData使用讲解

    目录 LiveData概述 LiveData优势 共享资源 LiveData使用 1 LiveData基本使用 2 Transformations.map() 3 Transformations.switchMap() 4 MediatorLiveData.addSource()合并数据 LiveData概述 LiveData 是一种可观察的数据存储器类: 与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity.Fragment 或 Servi

  • Android开发实现读取excel数据并保存为xml的方法

    本文实例讲述了Android开发实现读取excel数据并保存为xml的方法.分享给大家供大家参考,具体如下: 前阵子,公司请外面人翻译了一些android中values中的一些strings,然而保存的都是excel格式,如果单纯的将excel中的数据粘贴到指定的xml中的话,工作量非常的大,于是,自己写了个简单的demo,将excel中的数据读取并保存为xml对应的数据,下面的demo和图片展示: 1.数据保存在BeanValue中,包括key和value,方便后续数据读取 package c

  • Android开发之完成登陆界面的数据保存回显操作实例

    本文实例讲述了Android开发之完成登陆界面的数据保存回显操作.分享给大家供大家参考,具体如下: LoginActivity.java: package com.example.login; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.text.TextUtils; import android.view.Menu; import android.view

随机推荐