android - How can I save an activity state using the save instance state?

ID : 224

viewed : 98

Tags : androidandroid-activityapplication-stateandroid

Top 5 Answer for android - How can I save an activity state using the save instance state?

vote vote

98

You need to override onSaveInstanceState(Bundle savedInstanceState) and write the application state values you want to change to the Bundle parameter like this:

@Override public void onSaveInstanceState(Bundle savedInstanceState) {   super.onSaveInstanceState(savedInstanceState);   // Save UI state changes to the savedInstanceState.   // This bundle will be passed to onCreate if the process is   // killed and restarted.   savedInstanceState.putBoolean("MyBoolean", true);   savedInstanceState.putDouble("myDouble", 1.9);   savedInstanceState.putInt("MyInt", 1);   savedInstanceState.putString("MyString", "Welcome back to Android");   // etc. } 

The Bundle is essentially a way of storing a NVP ("Name-Value Pair") map, and it will get passed in to onCreate() and also onRestoreInstanceState() where you would then extract the values from activity like this:

@Override public void onRestoreInstanceState(Bundle savedInstanceState) {   super.onRestoreInstanceState(savedInstanceState);   // Restore UI state from the savedInstanceState.   // This bundle has also been passed to onCreate.   boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");   double myDouble = savedInstanceState.getDouble("myDouble");   int myInt = savedInstanceState.getInt("MyInt");   String myString = savedInstanceState.getString("MyString"); } 

Or from a fragment.

@Override public void onViewStateRestored(@Nullable Bundle savedInstanceState) {     super.onViewStateRestored(savedInstanceState);     // Restore UI state from the savedInstanceState.     // This bundle has also been passed to onCreate.     boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");     double myDouble = savedInstanceState.getDouble("myDouble");     int myInt = savedInstanceState.getInt("MyInt");     String myString = savedInstanceState.getString("MyString"); } 

You would usually use this technique to store instance values for your application (selections, unsaved text, etc.).

vote vote

87

The savedInstanceState is only for saving state associated with a current instance of an Activity, for example current navigation or selection info, so that if Android destroys and recreates an Activity, it can come back as it was before. See the documentation for onCreate and onSaveInstanceState

For more long lived state, consider using a SQLite database, a file, or preferences. See Saving Persistent State.

vote vote

76

Note that it is not safe to use onSaveInstanceState and onRestoreInstanceState for persistent data, according to the documentation on Activity.

The document states (in the 'Activity Lifecycle' section):

Note that it is important to save persistent data in onPause() instead of onSaveInstanceState(Bundle) because the later is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.

In other words, put your save/restore code for persistent data in onPause() and onResume()!

For further clarification, here's the onSaveInstanceState() documentation:

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via onCreate(Bundle) or onRestoreInstanceState(Bundle).

vote vote

60

My colleague wrote an article explaining application state on Android devices, including explanations on activity lifecycle and state information, how to store state information, and saving to state Bundle and SharedPreferences. Take a look at it here.

The article covers three approaches:

Store local variable/UI control data for application lifetime (i.e. temporarily) using an instance state bundle

[Code sample – Store state in state bundle] @Override public void onSaveInstanceState(Bundle savedInstanceState) {   // Store UI state to the savedInstanceState.   // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);   String strName = txtName.getText().toString();    EditText txtEmail = (EditText)findViewById(R.id.txtEmail);   String strEmail = txtEmail.getText().toString();    CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);   boolean blnTandC = chkTandC.isChecked();    savedInstanceState.putString(“Name”, strName);   savedInstanceState.putString(“Email”, strEmail);   savedInstanceState.putBoolean(“TandC”, blnTandC);    super.onSaveInstanceState(savedInstanceState); } 

Store local variable/UI control data between application instances (i.e. permanently) using shared preferences

[Code sample – store state in SharedPreferences] @Override protected void onPause() {   super.onPause();    // Store values between instances here   SharedPreferences preferences = getPreferences(MODE_PRIVATE);   SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI   EditText txtName = (EditText)findViewById(R.id.txtName);   String strName = txtName.getText().toString();    EditText txtEmail = (EditText)findViewById(R.id.txtEmail);   String strEmail = txtEmail.getText().toString();    CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);   boolean blnTandC = chkTandC.isChecked();    editor.putString(“Name”, strName); // value to store   editor.putString(“Email”, strEmail); // value to store   editor.putBoolean(“TandC”, blnTandC); // value to store   // Commit to storage   editor.commit(); } 

Keeping object instances alive in memory between activities within application lifetime using a retained non-configuration instance

[Code sample – store object instance] private cMyClassType moInstanceOfAClass; // Store the instance of an object @Override public Object onRetainNonConfigurationInstance() {   if (moInstanceOfAClass != null) // Check that the object exists       return(moInstanceOfAClass);   return super.onRetainNonConfigurationInstance(); } 
vote vote

60

This is a classic 'gotcha' of Android development. There are two issues here:

  • There is a subtle Android Framework bug which greatly complicates application stack management during development, at least on legacy versions (not entirely sure if/when/how it was fixed). I'll discuss this bug below.
  • The 'normal' or intended way to manage this issue is, itself, rather complicated with the duality of onPause/onResume and onSaveInstanceState/onRestoreInstanceState

Browsing across all these threads, I suspect that much of the time developers are talking about these two different issues simultaneously ... hence all the confusion and reports of "this doesn't work for me".

First, to clarify the 'intended' behavior: onSaveInstance and onRestoreInstance are fragile and only for transient state. The intended usage (as far as I can tell) is to handle Activity recreation when the phone is rotated (orientation change). In other words, the intended usage is when your Activity is still logically 'on top', but still must be reinstantiated by the system. The saved Bundle is not persisted outside of the process/memory/GC, so you cannot really rely on this if your activity goes to the background. Yes, perhaps your Activity's memory will survive its trip to the background and escape GC, but this is not reliable (nor is it predictable).

So if you have a scenario where there is meaningful 'user progress' or state that should be persisted between 'launches' of your application, the guidance is to use onPause and onResume. You must choose and prepare a persistent store yourself.

But - there is a very confusing bug which complicates all of this. Details are here:

Basically, if your application is launched with the SingleTask flag, and then later on you launch it from the home screen or launcher menu, then that subsequent invocation will create a NEW task ... you'll effectively have two different instances of your app inhabiting the same stack ... which gets very strange very fast. This seems to happen when you launch your app during development (i.e. from Eclipse or IntelliJ), so developers run into this a lot. But also through some of the app store update mechanisms (so it impacts your users as well).

I battled through these threads for hours before I realized that my main issue was this bug, not the intended framework behavior. A great write-up and workaround (UPDATE: see below) seems to be from user @kaciula in this answer:

Home key press behaviour

UPDATE June 2013: Months later, I have finally found the 'correct' solution. You don't need to manage any stateful startedApp flags yourself. You can detect this from the framework and bail appropriately. I use this near the beginning of my LauncherActivity.onCreate:

if (!isTaskRoot()) {     Intent intent = getIntent();     String action = intent.getAction();     if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {         finish();         return;     } } 

Top 3 video Explaining android - How can I save an activity state using the save instance state?

Related QUESTION?