Jetpack架构组件库-ViewMode之SavedStateHandle数据存储

前言

在引入了 Jetpack 之后,我们通常使用 ViewModel 组件来管理数据,当页面因配置变更(尤其是在发生像旋转这样频繁的配置更改之后)而重建时,可以使用 ViewModel 和 onSaveInstanceState(),以确保应用满足用户对其界面状态的预期。

参考:Jetpack-VM再不懂你就out了

但如果是内存不足或者电量不足等系统原因导致的页面被回收时 ViewModel 是不会被复用的。

一、用户预期和系统行为

参考官网 :https://developer.android.google.cn/topic/libraries/architecture/saving-states

1、用户发起的界面状态解除

用户希望当他们启动 Activity 时,该 Activity 的暂时性界面状态会保持不变,直到用户完全关闭 Activity 为止。用户可以通过以下方式完全关闭 Activity:

  • 按返回按钮

  • 从“概览”(“最近使用的应用”)屏幕中滑动关闭 Activity

  • 从 Activity 向上导航

  • 从“设置”屏幕中终止应用

  • 完成某种“完成”Activity(由 Activity.finish() 提供支持)

以上情况 Activity 实例将连同其中存储的任何状态以及与该 Activity 关联的任何已保存实例状态记录一起被销毁并从内存中移除。

2、系统发起的界面状态解除

用户期望 Activity 的界面状态在整个配置变更(例如旋转或切换到多窗口模式)期间保持不变。

但是,默认情况下,系统会在发生此类配置更改时销毁 Activity;

例如应用切换至后台后重新回到App时内存不足导致App被重启;

我们知道Activity可以通过 onSaveInstanceState() onRestoreInstanceState()方式 状态保存机制,页面重建后 ,可以恢复之前的状态,为用户提供更好的体验。

onSaveInstanceState() 回调会存储一些数据,如果系统销毁后又重新创建界面控制器(如 Activity 或 Fragment),则需要使用这些数据重新加载该控制器的状态。

因为 onSavedInstanceState() 会将数据序列化到磁盘。如果序列化的对象很复杂,序列化会占用大量的内存。

所以onSavedInstanceState() 不能用于存储大量的数据(如位图),也不能用于存储冗长复杂数据结构

而是只能用于存储基本类型和简单的小对象,例如字符串

二、内存不足或者电量不足等系统原因导致的页面被回收时 ViewModel 不会被复用的情况

模拟购买商品时增加/减少数量的场景:

1、创建ViewModel,添加add()&del()控制num变化
public class MyNormalViewModel extends ViewModel {
    private MutableLiveData<Integer> num = new MutableLiveData<Integer>(1);


    public LiveData<Integer> getNumber(){
        return num;
    }

    public void add(){
        num.setValue((int)num.getValue() + 1);
    }
    public void del(){
        if(num.getValue() > 1){
            num.setValue((int)num.getValue() - 1);
        }
    }
}
2、创建Activity,通过DataBinding控制UI数据
public class MyActivity extends AppCompatActivity {
    MyNormalViewModel myViewModel;
    MainActivityMyBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity_my);
        myViewModel = new ViewModelProvider(this).get(MyNormalViewModel.class);
        binding.setVm(myViewModel);
        binding.setLifecycleOwner(this);
    }
}
3、创建布局视图,通过DataBinding控制视图属性
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="vm"
            type="com.pxwx.main.viewmodel.MyNormalViewModel" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="@dimen/dp_30"
            android:orientation="horizontal">
    
            <androidx.appcompat.widget.AppCompatButton
                android:id="@+id/btnDecrease"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:gravity="center"
                android:text="-"
                android:textStyle="bold"
                android:background="#999999"
                android:onClick="@{()->vm.del()}"/>
    
            <EditText
                android:id="@+id/etAmount"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:minWidth="50dp"
                android:background="@null"
                android:inputType="number"
                android:gravity="center"
                android:enabled="false"
                android:textColor="@color/color_222222"
                android:text="@{vm.number.toString()}"/>
    
            <androidx.appcompat.widget.AppCompatButton
                android:id="@+id/btnIncrease"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:gravity="center"
                android:text="+"
                android:textStyle="bold"
                android:background="#999999"
                android:onClick="@{()->vm.add()}"/>
        </LinearLayout>
    
    </RelativeLayout>
</layout>

模拟购买商品时增加/减少数量的场景步骤:

  • 1、增加要兑换的商品数量

  • 2、开发者选项打开【不保留活动】

  • 3、按home键到桌面后再从进程中回到app

  • 4、再进行加减操作时数量是重置状态了

设置不保留活动
App进入后台销毁重新进入进程流程

通过ViewModel该如何解决呢?

应用切换至后台后重新回到App时内存不足导致App被重启;为了它能够帮助开发者在 ViewModel 中处理 Activity 和 fragment 状态保存和恢复,googole提供了viewmodel-savedstate组件

三、ViewModel-SavedState保存数据

1、首先先添加依赖
api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
2、ViewModel 搭配 SavedState 实现数据复用

ViewModel 可以有一个接收 SavedStateHandle 的构造函数:

public class MySavedStateViewModel extends ViewModel {
    private SavedStateHandle handle;
    public static final String KEY = "KEY_NUMBER";

    public MySavedStateViewModel(SavedStateHandle handle){
        if (!handle.contains(KEY)) {
            handle.set(KEY, 0);
        }
        this.handle = handle;
    }

    public LiveData<Integer> getNumber(){
        return handle.getLiveData(KEY);
    }

    public void add(){
        handle.set(KEY,(int)handle.get(KEY) + 1);
    }
    public void del(){
        handle.set(KEY,(int)handle.get(KEY) - 1);
    }
}
3、通过传入savedstateviewmodel创建viewmodel
public class MyActivity extends AppCompatActivity {
    MySavedStateViewModel myViewModel;
    MainActivityMyBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //用databinding来绑定布局
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity_my);
        //通过传入savedstateviewmodel创建viewmodel
        myViewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MySavedStateViewModel.class);
        binding.setVm(myViewModel);
        binding.setLifecycleOwner(this);
    }
}
4、布局文件处理,同上面的列子一样,不变
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="vm"
            type="com.pxwx.main.viewmodel.MySavedStateViewModel" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="@dimen/dp_30"
            android:orientation="horizontal">
    
            <androidx.appcompat.widget.AppCompatButton
                android:id="@+id/btnDecrease"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:gravity="center"
                android:text="-"
                android:textStyle="bold"
                android:background="#999999"
                android:onClick="@{()->vm.del()}"/>
    
            <EditText
                android:id="@+id/etAmount"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:minWidth="50dp"
                android:background="@null"
                android:inputType="number"
                android:gravity="center"
                android:enabled="false"
                android:textColor="@color/color_222222"
                android:text="@{vm.number.toString()}"/>
    
            <androidx.appcompat.widget.AppCompatButton
                android:id="@+id/btnIncrease"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:gravity="center"
                android:text="+"
                android:textStyle="bold"
                android:background="#999999"
                android:onClick="@{()->vm.add()}"/>
        </LinearLayout>
    
    </RelativeLayout>
</layout>

再次模拟购买商品时增加/减少数量的场景步骤:

  • 1、增加要兑换的商品数量

  • 2、开发者选项打开【不保留活动】

  • 3、按home键到桌面后再从进程中回到app

  • 4、再进行加减操作时数量是在原来保存的数据基础上增加(正常)

SavedStateHandle 类包含键值对映射应有的方法:
  • get(String key)

  • contains(String key)

  • remove(String key)

  • set(String key, T value)

  • keys()

如上使用:此外,还有一种特殊的方法:getLiveData(String key),用于返回封装在 LiveData 可观察对象中的值。

当使用Savedstate保存数据之后,后台进程关闭,数据也会得到保留

SavedStateHandle生命周期
< END >【Android进化之路】
【Android进化之路】
微信扫描二维码,关注我的公众号。 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值