As mentioned in the previous article, although the viewModel is simpler than onSaveInstanceState, the viewModel can only rebuild the page to maintain data after screen rotation and language switch (i.e. configuration change), and cannot recover the data when the page is accidentally destroyed (the viewModel will also rebuild). And that’s what onSaveInstanceState does. In terms of accidental destruction, we temporarily understand it as destruction reconstruction caused by non-configuration changes, such as out of memory and other scenarios.

Jetpack note code

The source of this article is based on SDK 29

Problem of repetition

Introducing dependencies:

def lifecycle_version = "2.2.0"
Extensions include Lifecycles, LiveData, ViewModel
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
Copy the code

Create a ViewModel,

class CommonViewModel extends ViewModel {
    public MutableLiveData<String> text = new MutableLiveData<>();
}
Copy the code

Used in layout files,

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="com.holiday.jetpackstudy.viewmodel_livedata.CommonViewModel" />
        <variable
            name="commonViewModel"
            type="CommonViewModel" />
    </data>

    <TextView
          android:id="@+id/tv_text"
          android:text="@{commonViewModel.text}"
          android:textSize="@dimen/tv_text_size" />
</layout>
Copy the code

Used in act,

class ViewModelActivity extends BaseActivity {
    CommonViewModel mCommonViewModel;
    String mTime;

    void onCreate(Bundle savedInstanceState) {
        // Pass this, create viewModel based on act
        mCommonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
        mBinding.setCommonViewModel(mCommonViewModel);
        // Observe data changes
        mCommonViewModel.text.observe(this.new Observer<String>() {
            @Override
            public void onChanged(String s) {
                / / update the UImBinding.tvText.setText(s); }});// After the page is accidentally destroyed, the ViewModel is rebuilt
        QrLog.e(String.valueOf(mCommonViewModel.hashCode()));

        if (null == savedInstanceState) {
            mTime = String.valueOf(System.currentTimeMillis() / 1000);
            QrLog.e("OnCreate get current time =" + mTime);
        } else {
            mTime = savedInstanceState.getString("test");
            QrLog.e("OnCreate restore last time ="+ mTime); }}
    void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Store the creation time of the act when the page was accidentally destroyed
        outState.putString("test", mTime); }}Copy the code

In the onCreate method, we added savedInstanceState and rewrote the onSaveInstanceState method to store the time. So how to simulate the accidental destruction of the page? We can select the developer option not to keep the activity – destroy each activity after the user leaves. Run the app and press the home button to cause the page to be accidentally destroyed. Then go back to the page and view the log:


As you can see, the viewModel does not hold data very well when the page is accidentally destroyed.

To solve

If you want the ViewModel to hold data in the event of an accidental page destruction, you can use it in combination with SavedStateHandle. Create a new ViewModel,

class SavedStateViewModel extends ViewModel {
    // SavedStateHandle needs to be referenced
    private SavedStateHandle mHandle;

    public SavedStateViewModel(SavedStateHandle handle) {
        mHandle = handle;

        Object text = mHandle.get("text");
        if (null == text) {
            String time = String.valueOf(System.currentTimeMillis() / 1000);
            mHandle.set("text", time);
            QrLog.e("SavedStateViewModel initializes data =" + time);
        } else {
            QrLog.e("SavedStateViewModel restore data ="+ text); }}}Copy the code

Then add to act:

//ViewModelActivity.java
class ViewModelActivity extends BaseActivity {
    SavedStateViewModel mSavedStateViewModel;
    void onCreate(Bundle savedInstanceState) {
        / / this way create incoming SavedStateViewModelFactory
        mSavedStateViewModel = ViewModelProviders
            .of(this.new SavedStateViewModelFactory(getApplication(), this))
            .get(SavedStateViewModel.class);
        QrLog.e("mSavedStateViewModel hashCode = "+ mSavedStateViewModel.hashCode()); }}Copy the code

Run to the page, hit the home button to trigger accidental destruction, and then go back to the page to view the logs,


It turns out that although mSavedStateViewModel is no longer the same instance, the data is recoverable.

As for the principle, general idea is in SavedStateViewModelFactory,

//SavedStateViewModelFactory.java
<T extends ViewModel> T create(String key, Class<T> modelClass) {
    / / with the aid of SavedStateHandleController SavedStateHandle storage
    SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    // Pass SavedStateHandle when creating the ViewModel
    T viewmodel = constructor.newInstance(controller.getHandle());
}
Copy the code

In SavedStateHandleController,

//SavedStateHandleController.java
static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle, String key, Bundle defaultArgs) {
    // Find the Bundle using the key generated by the act class name
    Bundle restoredState = registry.consumeRestoredStateForKey(key);
    // Restore data through the Bundle
    SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
    / / packaged into SavedStateHandleController returns
    SavedStateHandleController controller = new SavedStateHandleController(key, handle);
    return controller;
}

//SavedStateHandle.java
static SavedStateHandle createHandle(Bundle restoredState,Bundle defaultState) {
    if (restoredState == null && defaultState == null) {
        return new SavedStateHandle();
    }
    // Data recovery
    Map<String, Object> state = new HashMap<>();
    ArrayList keys = restoredState.getParcelableArrayList(KEYS);
    ArrayList values = restoredState.getParcelableArrayList(VALUES);
    for (int i = 0; i < keys.size(); i++) {
        state.put((String) keys.get(i), values.get(i));
    }
    // Although SavedStateHandle is no longer the same instance, the data has been recovered
    return new SavedStateHandle(state);
}
Copy the code

Essentially, data is recovered through serialization and deserialization of bundles.

reference

  • B – Jetpack course ViewModelSavedState

This article is formatted using MDNICE