Advanced ViewModel | Another way to achieve data reuse using SavedState

Foreword: Once golden wind and jade dew meet, they are better than countless people in the world.

foreword

This article needs to have the basis of the previous article. If you don’t understand it, you can read "From shallow to deep, detailed explanation of reuse of ViewModel configuration changes"

We learned earlier that ViewModel can only be reused because the configuration change page is destroyed and the data is reconstructed .
In this way, its usage scenario is not very big, because the configuration change is relatively rare. But if the page is recycled due to system reasons such as insufficient memory and insufficient power, can the rebuilt ViewModel be reused?

The answer is yes. This requires the use of the SavedState capability. In this way, even after the page is rebuilt, the ViewModel is not the same instance, and there is no problem. However, the multiplexing, storage and implementation principles of these two methods are different. ViewModel is far less simple than we imagined.

1. Advanced usage of SavedState

Let's first understand how to use ViewModel. Whether it is due to configuration changes, or insufficient memory, insufficient power and other non-configuration changes that cause pages to be recycled and rebuilt, the stored data can be reused. Even if the ViewModel is not the same instance, the data it stores can be reused.

build.gradleDependent components need to be introduced in savedstate:

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

If the page is closed normally, the data here will be cleaned up and released normally. Turn on the developer mode - turn on the "Do not keep activities " option to simulate the situation where the Activity is destroyed due to insufficient system memory.

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

First simulate getting data from the network, and then press the Home button to enter the background, then the Activity will be destroyed in the background. Then bring the app back to the foreground from the background, restore and rebuild the Activity, and get the data again:

insert image description here

It can be seen that the data fetched from the memory is very fast, and it is the data in the cache. The life cycle print data is as follows:

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

Source address: https://github.com/suming77/SumTea_Android

2. SavedState Architecture

The data storage and recovery of SaveState has several core classes:

  • SaveStateRegistryOwner : The core interface, which is used to declare the host. Both Activity and Fragment implement this interface. When implementing the interface, a SaveStateRegistry must be returned. The creation of SaveStateRegistry is delegated to SaveStateRegistryController.

  • SaveStateRegistryController : Controller, used to create SaveStateRegistry, establish connection with Activity and Fragment, in order to strip the coupling relationship between SaveStateRegistry and Activity.

  • SaveStateRegistry : core class, data storage recovery center, used to store and restore bundle data in a ViewModel , bound with host life cycle.

  • SaveStateHandle : The core class, a ViewModel corresponds to a SaveStateHandle for storing and restoring data .

  • SaveStateRegistry model : a total Bundle, key-value stores the sub-bundles corresponding to each ViewModel.

insert image description here

Since there may be multiple ViewModels on the page, the data in each ViewModel will be stored through SaveStateHandle, so the data structure of SaveStateRegistry is a general Bundle , the key corresponds to the name of ViewModel, and the value is the data saved by each SaveStateHandle, so The purpose of doing it is to save and withdraw in whole.

Because ViewModel needs to pass a SaveStateHandle when it is created, and SaveStateHandle needs a Bundle object, and this Bundle can Bundle mRestoredStatebe obtained from it. Even if the ViewModel stored in it is destroyed, it will be reused when the Activity is rebuilt.

3. ViewModel data reuse advanced SavedState

1. SavedState data storage

Enter the source code below, take Activity as an example, and Fragment is similar. ComponentActivity implements SavedStateRegistryOwnerthe interface, which is a host to provide SavedStateRegistrythis object, which is the storage center:

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

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

The creation of the instance is delegated to mSavedStateRegistryController, which is an object entrusted by the Activity to create the SavedStateRegistry. The purpose is to strip the relationship between the host and the data storage center of the Activity/Fragment.

Where is the data storage, in fact, inside 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)Forward directly to the SavedStateRegistry via :

#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);
}

The place where each ViewModel data is actually stored, traverses mComponents:

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

SavedStateProvider is an interface for saving state objects.
So when were the elements in components added? In fact, when the SavedStateHandle object is created, the registerSavedStateProvider()call is registered.

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

Let's see how SavedStateHandle completes the data storage work:

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;
        }
    };

Importantly, each SavedStateHandle object has a SavedStateProvider object, and implements saveState()the method to traverse the mRegular collection, which contains the key-value pair data to be cached, and then packs it into a Bundle object to return :

#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);
}

Store each SavedStateHandle in the components object, and then store the components in the outBundle, so that it can complete the data storage of all ViewModels, and store the data in each ViewModel as independent Bundle data. The purpose of this is It is deposit and withdrawal.

SavedState data storage process summary:

The SavedState data storage process calls each SavedStateHandle one by one to save its own data, summarizes it into a total Bundle, and stores it in the SavedState object of the Activity.

insert image description here

When the Activity is recycled due to system reasons such as insufficient memory and insufficient power, the onSaveInstanceState()method will definitely be executed. Then the Activity will use SaveStateRegistryController to forward it to SaveStateRegistry to let it complete the work of data storage. SaveStateRegistry will traverse all registrations when storing data. SaveStateProvider to store its own data, and return a Bundle object, and finally merged into a total Bundle, stored in the savedSate object of the Activity.

2. Recovery of SavedState data

The SavedState data reuse process is divided into two steps: the first step is to restore all ViewModel data from the savedState of the Activity to the SaveStateRegistry.

Need to go to the method of ComponentActivity onCreate():

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

Then SavedStateRegistryControllerforward to the SavedStateRegistry via 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;
}

Take out the components object stored just now through savedState, and assign it to mRestoredState. Data restoration is very simple, that is, take out the previously stored Bundle data from the savedState object of the Activity, and assign it to mRestoredState .

Summary of Restoration of SavedState Data

Restore all ViewModel data from Activity's savedState to SaveStateRegistry:

insert image description here

The method will be executed when the Activity is created onCreate(), and there is also a Bundle object of savedState. In ComponentActivity, onCreate()it will call the SaveStateRegistryController here and forward the Bundle to SaveStateRegistry. It actually performRestore(Bundle)takes out the storeBundle data object it just stored from this savedState , and save it. This step is just to take out the data, which is called data recovery.

3. SavedState data reuse

Step 2: The above is only to complete the data recovery when the Activity is recreated, but at this time the data has not been reused, and the reuse needs to go to the Activity:

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

Assuming onCreate()that it was executed because it was destroyed and rebuilt by the system:

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

owner.getViewModelStore()This will not only obtain the ViewModelStore in the Activity cache, but also determine whether the host has implemented HasDefaultViewModelProviderFactorythe interface, which has already been implemented in 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;
    }
}

What is returned here is a SavedStateViewModelFactory, that is to say, SavedStateViewModel instances are created using this Factory by default. What is the difference between this Factory? The difference is that when defining a ViewModel, you can specify a parameter in the constructor :

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

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

SavedStateViewModelFactory will judge whether your constructor has parameters when creating a new ViewModel. If there are no parameters, it will reflect and create its instance object in a normal form. If there are parameters, it will judge whether it is of type SavedStateHandle. Just now the SavedStateRegistry retrieves the data it caches, and constructs a SavedStateHandle object, which is passed in .

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

getDefaultViewModelProviderFactory()Actually 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;
    }
}

When creating, judge whether the modelClass has two constructors:

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

If neither of the above two is available, then when constructing an instance, the instance AndroidViewModelFactory will be constructed in a normal form, actually through reflection .

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

If the above constructor constructor != null, it will go to the bottom, and create a specific instance object through the constructor constructor just obtained, and pass the specified parameters:

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()In fact, what you get is SavedStateHandle, and the controller is SavedStateHandleController.create()created through. This class has three functions:

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;
}

Get the previously saved data in the registry, get a bundle object through the key (the name of the ViewModel), then create a SavedStateHandle object, and pass the bundle data in, and call controller.attachToLifecycle(registry, lifecycle):

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

Register a SavedStateProvider with the SavedStateRegistry data storage center to implement data storage. Then after the SavedStateHandle is created, the previously stored data is restored, and then passed to the and completes the data SavedStateViewModelFactoryreuse controller.getHandle().

Summary of SavedState data reuse process

Create a ViewModel and pass the restored SavedStateHandle:

insert image description here

The reuse of this data occurs in the creation of the ViewModel. To reuse the previous data, it needs to complete the creation of the instance through the SavedStateViewModelFactory, because when it instantiates the ViewModel, it will query the Bundle stored before the ViewModel from the SaveStateRegistry. Data, and create a SaveStateHandel , Bundle will be passed in when creating SaveStateHandel, and then passed to the constructor of ViewModel, so as to complete the data reuse work.

Four. Summary

  1. The essence of SavedState is to use the timing of onSaveIntanceState. The data of each ViewModel is stored in a Bundle separately, and each SavedStateHandle is called one by one to save its own data, which is aggregated into a total Bundle and stored in the outBundle of the Activity.

  2. Restore all ViewModel data from savedState in Activity's onCreate() method to SaveStateRegistry, and save the data.

  3. Create a ViewModel through SavedStateViewModelFactory, create a SaveStateHand based on the stored Bundle data, and then pass it to the constructor of the ViewModel to complete the data reuse work.

This is the principle of SavedState to realize data reuse.

A large-scale Android project architecture best practice , based on Jetpack组件 + MVVM架构模式, join 组件化, 模块化, 协程, Flow, 短视频. Project address: https://github.com/suming77/SumTea_Android

Pay attention, don't get lost


Well everyone, the above is the whole content of this article, thank you very much for reading this article. I am suming, thank you for your support and recognition, your praise is the biggest motivation for my creation. Mountains and rivers meet again , see you in the next article!

My level is limited, and there will inevitably be mistakes in the article. Please criticize and correct me, thank you very much!

Reference link:

Hope we can become friends, share knowledge and encourage each other on Github and blog ! Keep Moving!

Guess you like

Origin blog.csdn.net/m0_37796683/article/details/131325501