ViewModel进阶 | 使用SavedState实现数据复用的另一种方式

前言:金风玉露一相逢,便胜人间无数。

前言

本文需要有上一篇文章基础,如果不了解的可以先看看《由浅入深,ViewModel配置变更的复用详解》

前面我们了解到,ViewModel 它只能做到因为配置变更页面被销毁而导致重建数据才能复用
这样的话它的使用场景就不是很大了,因为配置变更的情况是比较少的。但是如果因为内存不足,电量不足等系统原因导致的页面被回收,那么被重建之后的 ViewModel 还能不能被复用呢?

答案是可以的。这需要用到 SavedState 能力,这种方式即便页面被重建之后,ViewModel 不是同一个实例了,都没有问题。但是它们这两种方式复用、存储以及实现原理是不一样的。ViewModel 远没有我们想象中的简单。

一、SavedState的进阶用法

先来了解一下如何使用 ViewModel 无论是因为配置变更,还是内存不足,电量不足等非配置变更导致的页面被回收再重建,都可以复用存储的数据。即便 ViewModel 不是同一个实例,它存储的数据也能做到复用。

需要在 build.gradle 引入依赖组件 savedstate

api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1'

如果页面被正常关闭,这里的数据会被正常清理释放,打开 开发者模式-开启"不保留活动"的选项,以此来模拟 Activity 由于系统内存不足被销毁的情况。

//SavedStateHandle里面就是包装了一个HashMap,可以使用它去存储数据
class MainSaveViewModel(val savedState: SavedStateHandle) : ViewModel() {
    
    
    private val KEY_USER_INFO = "key_user_info"
    val savedStateLiveData = MutableLiveData<String?>()

    fun getUserInfo() {
    
    
        if (savedStateLiveData.value != null) return
        //1.从savedState中获取数据
        val memoryData = savedState.get<String>(KEY_USER_INFO)
        if (!memoryData.isNullOrEmpty()) {
    
    
            savedStateLiveData.postValue("缓存数据-$memoryData")
        } else {
    
    
            //2.从网络获取数据
            // 模拟请求接口返回数据
            viewModelScope.launch {
    
    
                delay(1000)
                TipsToast.showTips("请求网络获取数据")
                val data = "SavedStateHandle-苏火火 苏火火 苏火火 苏火火 苏火火"
                savedState.set(KEY_USER_INFO, data)
                savedStateLiveData.postValue(data)
            }
        }
    }
}

class DemoViewModelActivity : BaseDataBindActivity<ActivityViewmodelBinding>() {
    
    
    override fun initView(savedInstanceState: Bundle?) {
    
    
        LogUtil.d("onCreate()")
        // 获取 SavedState 保存的数据
        val saveViewModel = ViewModelProvider(this).get(MainSaveViewModel::class.java)
        saveViewModel.savedStateLiveData.observe(this) {
    
    
            mBinding.tvUserInfo.text = it
        }
        mBinding.tvRequestSavedStateInfo.onClick {
    
    
            saveViewModel.getUserInfo()
        }
    }
}

首先模拟从网络获取数据,然后按 Home 键进入后台,此时 Activity 在后台就会被销毁。再将应用从后台回到前台,Activity 恢复重建,再次获取数据:

在这里插入图片描述

可以看到,从内存中取出的数据是非常快的,是缓存中的数据。生命周期打印数据如下:

5074-5074/com.sum.tea D/LogUtil: onCreate()
5074-5074/com.sum.tea D/LogUtil: onStop()
5074-5074/com.sum.tea D/LogUtil: onDestroy()
5074-5074/com.sum.tea D/LogUtil: onCreate()

源码地址:https://github.com/suming77/SumTea_Android

扫描二维码关注公众号,回复: 16424976 查看本文章

二、SavedState架构

SaveState 的数据存储与恢复,有几个核心类:

  • SaveStateRegistryOwner:  核心接口,用来声明宿主,Activity 和 Fragment 都实现了这个接口,实现接口的同时必须返回一个 SaveStateRegistry,SaveStateRegistry 的创建委托给 SaveStateRegistryController 来完成。

  • SaveStateRegistryController:控制器,用于创建 SaveStateRegistry,与 Activity、Fragment 建立连接,为了剥离 SaveStateRegistry 与 Activity 的耦合关系。

  • SaveStateRegistry:核心类,数据存储恢复中心,用于存储、恢复一个 ViewModel 中的 bundle 数据,与宿主生命周期绑定。

  • SaveStateHandle: 核心类,一个 ViewModel 对应一个 SaveStateHandle,用于存储和恢复数据

  • SaveStateRegistry模型:一个总 Bundle,key-value 存储着每个 ViewModel 对应的子 bundle。

在这里插入图片描述

由于页面有可能存在多个 ViewModel,那么每个 ViewModel 当中的数据都会通过 SaveStateHandle 来存储,所以 SaveStateRegistry 的数据结构是一个总的 Bundle,key 对应着 ViewModel 的名称,value 就是每个 SaveStateHandle 保存的数据,这样做的目的是为整存整取。

因为 ViewModel 在创建的时候需要传递一个 SaveStateHandle,SaveStateHandle 又需要一个 Bundle 对象,这个 Bundle 可以从 Bundle mRestoredState 里面获取。它里面存储的 ViewModel 即便被销毁了,那么在 Activity 重建的时候也会复用的。

三、ViewModel数据复用进阶SavedState

1.SavedState数据存储

下面进入源码,以 Activity 为例,Fragment 也是类似的。ComponentActivity 实现了 SavedStateRegistryOwner 接口,它是一个宿主,用来提供 SavedStateRegistry 这个对象的,这个对象就是存储中心:

// 实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory {
    
    

    @Override
    public final SavedStateRegistry getSavedStateRegistry() {
    
    
        // 委托给SavedStateRegistryController创建数据存储中心SavedStateRegistry
        return mSavedStateRegistryController.getSavedStateRegistry();
    }
}

实例的创建委托给 mSavedStateRegistryController,它是一个被 Activity 委托的对象,用来创建 SavedStateRegistry 的,目的是为了剥离 Activity/Fragment 这种宿主与数据存储中心的关系。

数据存储是在哪里呢,其实是在 onSaveInstanceState() 里面:

@Override
protected void onSaveInstanceState(Bundle outState) {
    
    
    Lifecycle lifecycle = getLifecycle();
    if (lifecycle instanceof LifecycleRegistry) {
    
    
        ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
    }
    super.onSaveInstanceState(outState);
    // 通过Controller直接将Bundle数据转发
    mSavedStateRegistryController.performSave(outState);
}

通过 mSavedStateRegistryController.performSave(outState) 直接转发给SavedStateRegistry:

#SavedStateRegistry.java
@MainThread
// 数据保存
void performSave(Bundle outBundle) {
    
    
    Bundle components = new Bundle();
    if (mRestoredState != null) {
    
    
        components.putAll(mRestoredState);
    }
    // 将ViewModel中的数据存储到Bundle中
    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());
    }
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

真正用来存储每个 ViewModel 数据的地方,遍历了 mComponents:

// 以键值对的形式保存着SavedStateProvider
private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>();

SavedStateProvider 是一个接口,用于保存状态对象。
那么 components 中的元素什么时候被添加进来的呢?实际上就是 SavedStateHandle 对象被创建的时候,调用 registerSavedStateProvider() 注册进来。

//SavedStateHandle 对象创建时调用
@MainThread
public void registerSavedStateProvider(String key, SavedStateProvider provider) {
    
    
    // 保存SavedStateProvider
    SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
}

来看看 SavedStateHandle 是如何完成数据存储工作的:

public final class SavedStateHandle {
    
    
    final Map<String, Object> mRegular;
    final Map<String, SavedStateProvider> mSavedStateProviders = new HashMap<>();
    private final Map<String, SavingStateLiveData<?>> mLiveDatas = new HashMap<>();

    // 每个SavedStateHandle对象中都有一个SavedStateProvider对象
    private final SavedStateProvider mSavedStateProvider = new SavedStateProvider() {
    
    
        // SavedStateRegistry保存数据时调用,将数据转为Bundel返回
        @Override 
        public Bundle saveState() {
    
    
            Map<String, SavedStateProvider> map = new HashMap<>(mSavedStateProviders);
            for (Map.Entry<String, SavedStateProvider> entry : map.entrySet()) {
    
    
                Bundle savedState = entry.getValue().saveState();
                set(entry.getKey(), savedState);
            }
            // 遍历mRegular集合,将当前缓存的Map数据转换为Bundle
            Set<String> keySet = mRegular.keySet();
            ArrayList keys = new ArrayList(keySet.size());
            ArrayList value = new ArrayList(keys.size());
            for (String key : keySet) {
    
    
                keys.add(key);
                value.add(mRegular.get(key));
            }

            Bundle res = new Bundle();
            // 序列化数据
            res.putParcelableArrayList("keys", keys);
            res.putParcelableArrayList("values", value);
            return res;
        }
    };

重点,每个 SavedStateHandle 对象中都有一个 SavedStateProvider 对象,并且实现了 saveState() 方法,遍历 mRegular 集合,里面放的就是要缓存的键值对数据,然后打包成一个 Bundle 对象返回:

#SavedStateRegistry.java
@MainThread
// 数据保存时调用
void performSave(Bundle outBundle) {
    
    
    Bundle components = new Bundle();
    if (mRestoredState != null) {
    
    
        // 将SavedStateHandle存储到components中
        components.putAll(mRestoredState);
    }

    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());
    }
    // 将components存储到outBundle中
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

把每个 SavedStateHandle 存储到 components 对象当中,然后又把 components 存储到 outBundle 中,这样它就能完成所有 ViewModel 的数据存储,并且每个 ViewModel 中的数据以独立的 Bundle 数据进行存储,这样做的目的就是为整存整取。

SavedState数据存储流程小结:

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

在这里插入图片描述

Activity 在内存不足电量不足等系统原因,被回收的情况下,肯定会执行 onSaveInstanceState() 方法,Activity 紧接着就利用 SaveStateRegistryController 转发给 SaveStateRegistry,让它去完成数据存储的工作,SaveStateRegistry 在存储数据的时候会遍历所有注册的 SaveStateProvider 去存储自己的数据,并且返回一个 Bundle 对象,最后合并成一个总的 Bundle,存储到 Activity 的 savedSate 对象当中。

2.SavedState数据的恢复

SavedState 数据复用流程分为两步:第一步先从 Activity 的 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry。

需要到 ComponentActivity 的 onCreate() 方法中:

#ComponentActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    // 通过SavedStateRegistryController将bundle数据传递给SavedStateRegistry
    mSavedStateRegistryController.performRestore(savedInstanceState);
    //·····
}

然后通过 SavedStateRegistryController 转发到 SavedStateRegistry 的performRestore()

#SavedStateRegistry.java
// 数据恢复会调用这个方法
void performRestore(Lifecycle lifecycle, Bundle savedState) {
    
    
    if (savedState != null) {
    
    
        // savedState中根据key获取数据Bundle数据,components对象
        mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
    }

    lifecycle.addObserver(new GenericLifecycleObserver() {
    
    
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
    
    
            if (event == Lifecycle.Event.ON_START) {
    
    
                mAllowingSavingState = true;
            } else if (event == Lifecycle.Event.ON_STOP) {
    
    
                mAllowingSavingState = false;
            }
        }
    });

    mRestored = true;
}

通过 savedState 取出刚才存储的 components 对象,并且赋值给 mRestoredState,数据的恢复是非常简单的,就是从 Activity 的 savedState 对象中取出前面存储的 Bundle 数据,并且赋值给 mRestoredState

SavedState数据的恢复小结

从 Activity 的 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry:

在这里插入图片描述

在 Activity 创建的时候会执行 onCreate() 方法,也有一个 savedState 的 Bundle 对象,在 ComponentActivity 里面 onCreate() 里面它又会调用这里的 SaveStateRegistryController,把 Bundle 转发给 SaveStateRegistry,它实际上就是从这个 performRestore(Bundle) 的 savedState 取出它刚才存储的 storeBundle 数据对象,并且保存它。这一步仅仅是取出数据,叫做数据的恢复。

3.SavedState数据复用

第二步:上面仅仅是在 Activity 重新创建时完成数据的恢复,但是这时数据还没有被复用,复用需要去到 Activity 中:

class DemoViewModelActivity : BaseDataBindActivity<ActivityViewmodelBinding>() {
    
    
    override fun initView(savedInstanceState: Bundle?) {
    
    
        // 获取 SavedState 保存的数据
        val saveViewModel = ViewModelProvider(this).get(MainSaveViewModel::class.java)
    }
}

假设 onCreate() 是因为被系统原因销毁了重建,才执行过来的:

// ViewModelProvider的构造方法
public ViewModelProvider(ViewModelStoreOwner owner) {
    
    
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

owner.getViewModelStore() 这里不仅仅会获取 Activity 缓存里面的 ViewModelStore,还会判断宿主是否实现了 HasDefaultViewModelProviderFactory 接口,ComponentActivity 中是已经实现了该接口的:

// 实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory {
    
    

    // DefaultViewModelProviderFactory工厂实现
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    
    
        if (mDefaultFactory == null) {
    
    
            // 创建一个SavedStateViewModelFactory返回
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
}

这里返回的是一个 SavedStateViewModelFactory,那就是说 SavedStateViewModel 实例都是默认使用这个 Factory 来创建,这个 Factory 有什么不同呢,它的不同之处在于,定义一个 ViewModel 的时候,可以在构造函数里面指定一个参数

// 指定一个SavedStateHandle参数
class MainSaveViewModel(val savedState: SavedStateHandle) : ViewModel() {
    
    }

//指定两个参数Application和SavedStateHandle
class HomeAndroidViewModel(val application: Application, val savedStateHandle: SavedStateHandle) : AndroidViewModel(appInstance) {
    
    }

SavedStateViewModelFactory 在新建 ViewModel 的时候就会判断你的构造函数有没有参数,如果没有参数就以普通的形式进行反射创建它的实例对象,如果有参数就会判断是不是 SavedStateHandle 类型的,如果是则会从刚才 SavedStateRegistry 当中去取出它所缓存的数据,并构建一个 SavedStateHandle 对象,传递进来

#ViewModelProviderpublic ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    
    
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

getDefaultViewModelProviderFactory() 实际是 SavedStateViewModelProviderFactory:

SavedStateViewModelFactory类:
@Override
public <T extends ViewModel> T create(String key, Class<T> modelClass) {
    
    
    // 判断是否为AndroidViewModel
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    Constructor<T> constructor;
    // 获取构造器
    if (isAndroidViewModel && mApplication != null) {
    
    
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
    
    
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // 普通方式创建ViewModel实例
    if (constructor == null) {
    
    
        return mFactory.create(modelClass);
    }
    
    // 创建SavedStateHandleController
    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
    
    
        T viewmodel;
        // 根据构造器参数创建viewmodel
        if (isAndroidViewModel && mApplication != null) {
    
    
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
    
    
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        return viewmodel;
    }
}

创建的时候判断 modelClass 是否拥有两种构造函数:

//第一种:有两个参数
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{
    
    Application.class,
        SavedStateHandle.class};
//第二种:只有一个参数
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{
    
    SavedStateHandle.class};

如果上面两种都没有,那么在构造实例的时候,就会以普通的形式构造实例 AndroidViewModelFactory,实际上是通过反射

if (constructor == null) {
    
    
    return mFactory.create(modelClass);
}

如果上的构造函数 constructor != null,都会走到下面,并且通过刚才获取到的 constructor 构造函数去创建具体的实例对象,并且传递指定的参数:

SavedStateViewModelFactory类:
@Override
public <T extends ViewModel> T create(String key, Class<T> modelClass) {
    
    
    //······
    // 创建SavedStateHandleController
    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
    
    
        T viewmodel;
        // 根据构造器参数创建viewmodel
        if (isAndroidViewModel && mApplication != null) {
    
    
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
    
    
            viewmodel = constructor.newInstance(controller.getHandle());
        }
    }
}

controller.getHandle() 实际上得到的是 SavedStateHandle,controller 是通过SavedStateHandleController.create() 创建,这个类有三个作用:

static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
        String key, Bundle defaultArgs) {
    
    
    // 1.通过key获取到先前保存起来的数据 得到bundle对象
    Bundle restoredState = registry.consumeRestoredStateForKey(key);
    // 2.传递restoredState,创建SavedStateHandle
    SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
    // 3.通过key和handle创建SavedStateHandleController对象
    SavedStateHandleController controller = new SavedStateHandleController(key, handle);
    // 4.添加生命周期监听,向SavedStateRegistry数据存中心注册一个SavedStateProvider
    controller.attachToLifecycle(registry, lifecycle);
    tryToAddRecreator(registry, lifecycle);
    return controller;
}

在 registry 中获取先前保存的数据,通过 key(ViewModel的名称)得到一个 bundle 对象,接着创建一个 SavedStateHandle 对象,并且把 bundle 数据传递了进去,还会调用controller.attachToLifecycle(registry, lifecycle):

void attachToLifecycle(SavedStateRegistry registry, Lifecycle lifecycle) {
    
    
    mIsAttached = true;
    lifecycle.addObserver(this);
    //注册一个 SavedStateProvider,用于实现数据存储的工作
    registry.registerSavedStateProvider(mKey, mHandle.savedStateProvider());
}

向 SavedStateRegistry 数据存中心注册一个 SavedStateProvider,用于实现数据存储的工作,那么 SavedStateHandle 被创建完之后,之前存储的数据就被恢复了,然后传递到了 SavedStateViewModelFactory 中的 controller.getHandle() 然后完成了数据的复用。

SavedState数据复用流程小结

创建 ViewModel 并传递恢复的 SavedStateHandle:

在这里插入图片描述

这个数据的复用它发生在 ViewModel 的创建,要复用之前的数据,它就需要通过 SavedStateViewModelFactory 完成实例的创建,因为它实例化这个 ViewModel 的时候他会从 SaveStateRegistry 当中查询这个 ViewModel 之前所存储的 Bundle 数据,并且创建一个 SaveStateHandel,在创建 SaveStateHandel 的时候就会把 Bundle 传递进去,然后传递到 ViewModel 的构造函数里面,从而完成数据的复用工作。

四、总结

  1. SavedState 本质是利用了 onSaveIntanceState 的时机,每个 ViewModel 的数据单独存储在一个 Bundle,逐一调用每个 SavedStateHandle 保存自己的数据,汇总成一个总 Bundle,存放在 Activity 的 outBundle 中。

  2. 从 Activity 的 onCreate() 方法中 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry,并保存数据。

  3. 通过 SavedStateViewModelFactory 创建 ViewModel,并根据存储的 Bundle 数据创建 SaveStateHand,然后传递到 ViewModel 的构造函数里面,从而完成数据的复用工作。

这就是 SavedState 实现数据复用的原理。

一个大型的 Android 项目架构最佳实践,基于Jetpack组件 + MVVM架构模式,加入 组件化模块化协程Flow短视频。项目地址:https://github.com/suming77/SumTea_Android

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,很感谢您阅读这篇文章。我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力。山水有相逢,我们下篇文章见!

本人水平有限,文章难免会有错误,请批评指正,不胜感激 !

参考链接:

希望我们能成为朋友,在 Github博客 上一起分享知识,一起共勉!Keep Moving!

猜你喜欢

转载自blog.csdn.net/m0_37796683/article/details/131325501