前语:金风玉露一相逢,便胜人世很多。

前语

本文需求有上一篇文章基础,假如不了解的能够先看看《由浅入深,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 康复重建,再次获取数据:

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

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

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()

源码地址:github.com/suming77/Su…

二、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进阶 | 使用SavedState实现数据复用的另一种方式

由于页面有可能存在多个 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 目标中。

ViewModel进阶 | 使用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:

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

在 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 目标,传递进来

#ViewModelProvider类
public 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进阶 | 使用SavedState实现数据复用的另一种方式

这个数据的复用它发生在 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短视频。项目地址:github.com/suming77/Su…

点重视,不迷路


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

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

参考链接:

  • ViewModel官网

希望咱们能成为朋友,在 Github、 上一起共享常识,一起共勉!Keep Moving!