State saving and recovery in Android
Android state saving and recovery, including activities and fragments and View state processing. The state of an Activity requires the user to manually save some member variables in addition to its View and Fragment states. The Fragment state has its own instance state and the View state within it, which is more so because of the flexibility of its lifecycle and actual needs. According to the source code, listed in the Fragment instance state and View state save and restore several entrances, easy to analyze and View. Finally, WebView state saving and recovery, problems and processing. There is also an introduction to the utility class Icepick.
The state of the Activity is saved and restored
As a warm-up, let’s talk about Activity state saving and recovery.
When do I need to resume my Activity
An Activity can be destroyed completely, or it can leave data behind.
A clean destroy is when the user actively shuts down or exits the Activity. State recovery is not required because the next time the user comes back, the new instance will be created again. The destruction of data left behind is when the system destroys the activity, but recreates it when the user returns, giving the user the impression that it was there all along.
Screen rotation reconstruction can be attributed to the second situation, which will also occur when you turn on the Do not keep Activities switch and switch activities. The Do Not Keep Activities switch is turned on to simulate the system behavior when memory is low. Here is an analysis
How to restore
In fact, the system has done the basic restoration work for us at the View level, mainly relying on the following two methods:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // call before onStop(), } @override protected void onRestoreInstanceState(Bundle) {Override protected void onRestoreInstanceState(Bundle) savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // after onStart()}Copy the code
The Bundle contains the view and fragment information of the activity, so calling the methods of the base class can do the basic view level restoration. Note that these two methods are not lifecycle callbacks to the activity; they do not necessarily happen to the activity. It is also important to note that a View must have an ID to be restored.
For example: Activity A start B, A’s onSaveInstanceState() is called before onStop() to prevent A from being destroyed. OnSaveInstanceState () is not called when B is destroyed after pressing the back key to finish() itself, because B is not pushed into the back stack of the task, i.e. the system knows that B does not need to store its state. Normally, when you return to A, A has not been destroyed and onRestoreInstanceState() is not called because all the states are still there and do not need to be rebuilt.
If we turn on the Do Not Keep Activities switch to simulate the behavior of A running out of memory, from A to B, we can see that A walks all the way to onDestroy() when B resumes, and starts onCreate() when B is turned off. OnStart () will call onRestoreInstanceState(), and its bundle will look something like this:
Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=272]}]Copy the code
FragmentManagerState contains the state of the View and the state of the FragmentManagerState.
Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@bc382e7, 2131492950=CompoundButton.SavedState{4034f96 checked=true}, 2131492951=android.view.AbsSavedState$1@bc382e7}}], android:fragments=android.app.FragmentManagerState@bacc717}]Copy the code
For the example above, when does B call onSaveInstanceState()? After opening B from A, press the Home button and B will call onSaveInstanceState(). Because the system does not know when the user will return at this time, it may destroy B, so save its state. OnRestoreInstanceState () will not be called if it hasn’t been rebuilt the next time it comes back, onRestoreInstanceState() will only be called if it has been rebuilt.
The Activity holds when the method is called
The activity ofonSaveInstanceState()
andonRestoreInstanceState()
The method is called when:
- Screen rotation reconstruction: Save then Restore.
- Start another activity: The current activity saves before leaving, and when it returns, if it needs to rebuild because it was killed by the system, it restarts its life cycle from onCreate(), calling onRestoreInstanceState(); If there is no rebuild, onCreate() is not called, and onRestoreInstanceState() is not called, and the life cycle starts with onRestart(), followed by onStart() and onResume().
- Pressing the Home button is the same as starting another activity. The current activity is saved before leaving, and when the user clicks the application icon again to return, onCreate() and onRestoreInstanceState() are called if a rebuild occurs. If the activity does not need to rebuild and just onRestart(), onRestoreInstanceState() is not called.
Activity Resumes the call time of the method
The activity ofonSaveInstanceState()
andonRestoreInstanceState()
The method is not called when:
- OnSaveInstanceState () will not be called if the user finishes (), including if the user presses back to exit.
- A new activity, starting with onCreate(), does not call onRestoreInstanceState().
What else do I need to manually restore in my Activity
As mentioned above, the system has restored various views and fragments in the activity for us. What do we need to save and restore ourselves? The answer is the value of the member variable.
Since the system doesn’t know what your various member variables are used for or which values need to be saved, you need to override the above two methods and add the values you need to save to the bundle. I won’t repeat the example of recreating an Activity. It’s important not to forget to call the super method, where the system does its job for us to recover.
This section describes the Icepick tool
Icepick is a tool that saves calls to put and get methods when you want to save and recreate your own member variable data. It also saves you from having to create a constant key for each field. All you need to do is simply add an @state annotation to the field where you want to save the State. Then add a sentence to save and restore:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}Copy the code
Then your member variable has the value it should have, DONE!
Fragment state is saved and restored
The Fragment state is more complex than the Activity state because it has more life-cycle states.
Methods to save and restore Fragment states
I’m going to look for callback methods to save and restore in the Fragment. The Fragment state preservation callback is this method:
public void onSaveInstanceState(Bundle outState) {
// may be called any time before onDestroy()
}Copy the code
OnSaveInstanceState () occurs when the activity calls onSaveInstanceState(). The bundle saved by the.onSaveInstancEstate () method returns several lifecycle callbacks: OnCreate (), onCreateView(), onViewCreated() and onActivityCreated().
Fragment has no corresponding onRestoreInstanceState() method. That is, there is no instance state recovery callback.
Fragment has only one callback method onViewStateRestored() :
Public void onViewStateRestored(@nullable Bundle savedInstanceState) {// mCalled = between onActivityCreated() and onStart() true; }Copy the code
OnViewStateRestored () occurs every time a new Fragment is created. It is not a method of instance state recovery, just a callback to View state recovery.
Note that there are two types of Fragment state: instance state and View state. Here is a Best practice: The Real Best Practices to Save/Restore Activity’s and Fragment’s state Do not mix The Fragment instance state with The View state.
Fragment state: Fragment state: Fragment state: Fragment state: Fragment state: Fragment state: Fragment state: Fragment state: Fragment state
Fragment state save entry:
There are three entries for Fragment state saving:
- The state of the Activity is saved in the Activity
onSaveInstanceState()
Call FragmentMangersaveAllState()
Method, which saves the instance state and View state of each Fragment in mActive. - The FragmentManager also provides public methods:
saveFragmentInstanceState()
, can save the state of a single Fragment, which is provided for our use, there will be examples to introduce this later. Which callssaveFragmentBasicState()
The method is used in case 1 and is marked in the figure. - The FragmentManager
moveToState()
Method, when the state falls back toACTIVITY_CREATED
To callsaveFragmentViewState()
Method to save the state of the View.
The moveToState() method has a long switch case with no break in the middle, which is divided into two directions: forward create and reverse destroy, based on the comparison between the new state and the current state.
Fragment state restoration entry:
The three restored entrances correspond to the three saved entrances.
- Restore all Fragment states when the Activity is recreated.
- If the FragmentManager method is called:
saveFragmentInstanceState()
, return the worth to the state can be used FragmentsetInitialSavedState()
Method to the new Fragment instance as its initial state. - The FragmentManager
moveToState()
Method when the state forward is created toCREATED
The Fragment itself restores the state of the View.
The three entries correspond to the following situations: Entry 1 corresponds to system destruction and reconstruction of new instances. Entry 2 corresponds to state passing for user-defined destruction and creation of new Fragment instances. Entry 3 corresponds to View state reconstruction of the same Fragment instance itself.
Fragment state stores the association between recovery and Activity
This corresponds to entry 1. When an Activity is doing state saving and recovery, the fragment within it naturally needs to do state saving and recovery as well. So Fragment onSaveInstanceState() must occur when the activity calls onSaveInstanceState(). Similarly, if the Fragment has some member variable values that need to be saved at this point, you can use the @state flag. That is, when the Activity needs to save state, the instance state of the Fragments is automatically processed and saved.
Fragment Restores the View state of the same instance
This corresponds to entry 3. As mentioned earlier, when an activity saves state, it stores all View and Fragment states to be used when rebuilding. However, if a single Activity is constructed with multiple Fragments, the Activity will always resume. How can the state of an Activity be saved without the help of the Activity during the process of switching between multiple Fragments?
First, it depends on how your multiple Fragments are initialized. I did an experiment to initialize two fragments in the activity’s onCreate() :
private void initFragments() { tab1Fragment = getFragmentManager().findFragmentByTag(Tab1Fragment.TAG); if (tab1Fragment == null) { tab1Fragment = new Tab1Fragment(); } tab2Fragment = getFragmentManager().findFragmentByTag(Tab2Fragment.TAG); if (tab2Fragment == null) { tab2Fragment = new Tab2Fragment(); }}Copy the code
Then click the two buttons to switch them, replace(), and not add them to the back stack:
@OnClick(R.id.tab1)
void onTab1Clicked() {
getFragmentManager().beginTransaction()
.replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG)
.commit();
}
@OnClick(R.id.tab2)
void onTab2Clicked() {
getFragmentManager().beginTransaction()
.replace(R.id.content_container, tab2Fragment, Tab2Fragment.TAG)
.commit();
}Copy the code
As you can see, each switch is a complete destroy of one Fragment, detach of the other, attach of the other Fragment, create,
But when I add EditText to each fragment, I find that as long as the EditText has an ID, the contents of the EditText are saved during the switch.
Who saved and restored it and when?
I set a breakpoint in the TextChange callback and found the call stack as follows:
在FragmentManagerImpl
,moveToState()
Method in case fragment.created:
The call:f.restoreViewState(f.mSavedFragmentState);
I’m not doing anything to save the state at this point, but you can see from the breakpoint:
Although mSavedFragmentState is null, mSavedViewState has a value.
Therefore, the corresponding entry for saving and restoring the View state is the third entry in the two figures above.
This is because my two fragments only new once and then save the member variables, even if the fragment is re-onCreate (), the corresponding instance is still the same. This is different from an Activity because you can’t create a new Activity.
In the example above, if a reference to the Fragment is new every time without saving it, the state of the View will not be saved because state transfer between different instances will only occur if the system is destroyed and restored (entry 1). If we need to pass state between different instances, we need to use the following method.
Save and restore state between different Fragment instances
This corresponds to entry 2, which is automatic, unlike entry 1 and 3, where the user actively saves and restores. Save your own Fragment state by calling the FragmentManager method:
public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);Copy the code
Its implementation looks like this:
@Override public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { if (fragment.mIndex < 0) { throwException(new IllegalStateException("Fragment " + fragment + " is not currently in the FragmentManager")); } if (fragment.mState > Fragment.INITIALIZING) { Bundle result = saveFragmentBasicState(fragment); return result ! = null ? new Fragment.SavedState(result) : null; } return null; }Copy the code
SavedState, which can be set to itself using the Fragment method:
public void setInitialSavedState(SavedState state) { if (mIndex >= 0) { throw new IllegalStateException("Fragment already active"); } mSavedFragmentState = state ! = null && state.mState ! = null ? state.mState : null; }Copy the code
Note, however, that this can only be set before the Fragment is added, which is an initial state. Using these two methods allows you to save and restore state more freely, independent of the Activity. After this process, there is no need to save the Fragment reference. Although a new instance is created during each switch, the state of the old instance can be set to the new instance.
Example code:
@State SparseArray savedStateSparseArray = new SparseArray<>(); void onTab1Clicked() { // save current tab Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(Tab2Fragment.TAG); if (tab2Fragment ! = null) { saveFragmentState(1, tab2Fragment); } // restore last state Tab1Fragment tab1Fragment = new Tab1Fragment(); restoreFragmentState(0, tab1Fragment); // show new tab getSupportFragmentManager().beginTransaction() .replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG) .commit(); } private void saveFragmentState(int index, Fragment fragment) { Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment); savedStateSparseArray.put(index, savedState); } private void restoreFragmentState(int index, Fragment fragment) { Fragment.SavedState savedState = savedStateSparseArray.get(index); fragment.setInitialSavedState(savedState); }Copy the code
Note that SparseArray is used to store the Fragment State and @state is added so that its contents can be restored when the Activity is rebuilt.
Fragment in the Back Stack
What’s special about the Fragment is that when it returns from the back stack, it actually goes through a View destruction and reconstruction, but it’s not rebuilt. That is, the View state needs to be rebuilt, but the instance state does not need to be rebuilt.
Here’s an example of a situation where a Fragment is replaced () by another Fragment and pushed into the back stack. Its View is destroyed, but it is not destroyed. That is, it goes to onDestroyView() without onDestroy() and onDetact(). When the back comes back, its view will be rebuilt, starting with onCreateView() again. During this process, member variables in the Fragment remain unchanged, and only the View is recreated. In this process, instance State saving does not happen.
Therefore, many times a Fragment needs to consider preserving its own View state without the help of an Activity (which is not possible to recreate). At this point, beware of errors that are not easy to spot, such as the need to reset the setAdapter for a new instance of the List.
Fragment setRetainInstance
There is a related Fragment method: setRetainInstance This method, when set to true, means that even if the activity is rebuilt, the Fragment instance is not rebuilt. Note that this method only applies to fragments that are not in the back stack. When do you use this method? When handling configuration change: Handling Configuration Changes with Fragments so that when the screen rotates, the Activity rebuilds, but the fragment and the task that the fragment is performing do not need to be rebuilt. More explanation can see: stackoverflow.com/questions/1… Stackoverflow.com/questions/1…
Note that this method is only for configuration changes and does not affect active shutdown and system destruction: when the activity is actively finished by the user, all fragments in it will still be destroyed. When the activity is not at the top and memory is running low, the system may still destroy the activity and fragments within it.
View state is saved and restored
View the status of the save and restore is mainly dependent on the following methods: save: saveHierarchyState () – > dispatchSaveInstanceState () – > onSaveInstanceState () recovery: RestoreHierarchyState () – > dispatchRestoreInstanceState () – > onRestoreInstanceState () there are two important precondition is the View must have the id, And setSavedEnabled() is true.(This value defaults to true.) In system widgets (TextView, EditText, Checkbox, etc.), these are already handled, we just need to give the View id, When an Activity or Fragment is rebuilt, its state is automatically restored.
But if you’re using third-party custom views, you need to make sure they have code for state saving and recovery. If not, you need to inherit the custom View and implement these two methods:
//
// Assumes that SomeSmartButton is a 3rd Party view that
// View State Saving/Restoring are not implemented internally
//
public class SomeBetterSmartButton extends SomeSmartButton {
...
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Save current View's state here
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
// Restore View's state here
}
...
}Copy the code
WebView state is saved and restored
WebView state saving and restoration is not automatic like other native views. Webviews don’t inherit from views. If we place the WebView in the layout and leave it unprocessed, the state of the WebView will be lost and changed to its original state during Activity or Fragment reconstruction.
Add the following code to the Fragment onSaveInstanceState() to save the state of the WebView:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
}Copy the code
Then add judgment when initializing, without opening the initial link every time:
if (savedInstanceState ! = null) { webView.restoreState(savedInstanceState); } else { webView.loadUrl(TEST_URL); }Copy the code
After this is done, the WebView state can be restored to where it left off when it is rebuilt. This method works whether the WebView is in an Activity or Fragment.
But there is another case of a Fragment being pushed into the back stack where it is not destroyed (), so the onSavedInstanceState() method is not called. When this happens, onCreateView() is returned, and savedInstanceState is null, so the WebView’s previous state is lost. Add a bundle member variable to the Fragment instance to save the state of the WebView.
private Bundle webViewState; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ButterKnife.bind(this, view); initWebView(); if (webViewState ! RestoreState (webViewState) = null) {//Fragment instance is not destroyed, recreate the view webView.restoreState(webViewState); } else if (savedInstanceState ! Webview. restoreState(savedInstanceState); } else {// new Fragment webView.loadUrl(TEST_URL); } } @Override public void onPause() { super.onPause(); webView.onPause(); WebViewState = new Bundle(); webViewState = new Bundle(); webView.saveState(webViewState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Use outState to save the WebView state if (WebView! = null) { webView.saveState(outState); }}Copy the code
HelloActivityAndFragment State Restore Demo
Android Fragment using (3) Activity, Fragment, WebView state save and restore
The resources
Developer Android:
Android Fragment Reference
Android FragmentManager Reference
Posts: Recreating an Activity The Real Best Practices to Save/Restore a Fragment Activity’s and Fragment’s State The best way to save and restore the Fragment state in Android
Handling Configuration Changes with Fragments
Saving Android View state correctly
Tools:
icepick