Guardar el estado de la actividad de Android usando Guardar estado de instancia

He estado trabajando en la plataforma Android SDK, y no está claro cómo guardar el estado de una aplicación. Entonces, dado este pequeño retoque del ejemplo de ‘Hola, Android’:

package com.android.hello; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class HelloAndroid extends Activity { private TextView mTextView = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTextView = new TextView(this); if (savedInstanceState == null) { mTextView.setText("Welcome to HelloAndroid!"); } else { mTextView.setText("Welcome back."); } setContentView(mTextView); } } 

Pensé que sería suficiente para el caso más simple, pero siempre responde con el primer mensaje, sin importar cómo me alejo de la aplicación.

Estoy seguro de que la solución es tan simple como anular onPause o algo así, pero he estado hurgando en la documentación durante 30 minutos más o menos y no he encontrado nada obvio.

onSaveInstanceState(Bundle savedInstanceState) sobrescribir onSaveInstanceState(Bundle savedInstanceState) y escribir los valores de estado de la aplicación que desea cambiar en el parámetro Bundle como este:

 @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. } 

Bundle es esencialmente una forma de almacenar un mapa NVP (“Name-Value Pair”), y se pasará a onCreate() y también onRestoreInstanceState() donde extraerá los valores de esta manera:

 @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"); } 

Por lo general, utilizará esta técnica para almacenar valores de instancia para su aplicación (selecciones, texto no guardado, etc.).

savedInstanceState solo sirve para guardar el estado asociado a una instancia actual de una actividad, por ejemplo, la navegación actual o la información de selección, de modo que si Android destruye y recrea una actividad, puede volver a funcionar como antes. Consulte la documentación de onCreate y onSaveInstanceState

Para un estado más longevo, considere usar una base de datos SQLite, un archivo o preferencias. Consulte Cómo guardar el estado persistente .

Tenga en cuenta que NO es seguro usar onSaveInstanceState y onRestoreInstanceState para datos persistentes , de acuerdo con la documentación sobre estados de actividad en http://developer.android.com/reference/android/app/Activity.html .

El documento indica (en la sección ‘Ciclo de vida de la actividad’):

Tenga en cuenta que es importante guardar datos persistentes en onPause() lugar de onSaveInstanceState(Bundle) porque el último no forma parte de las devoluciones de llamada del ciclo de vida, por lo que no se onSaveInstanceState(Bundle) en todas las situaciones tal como se describe en su documentación.

En otras palabras, ponga su código de guardar / restaurar para datos persistentes en onPause() y onResume() !

EDITAR : Para mayor aclaración, aquí está la documentación onSaveInstanceState() :

Se llama a este método antes de que se mate una actividad, de modo que cuando regrese en algún momento en el futuro pueda restaurar su estado. Por ejemplo, si la actividad B se lanza al frente de la actividad A, y en algún momento se mata la actividad A para reclamar recursos, la actividad A tendrá la oportunidad de guardar el estado actual de su interfaz de usuario a través de este método para que cuando el usuario regrese para la actividad A, el estado de la interfaz de usuario puede restaurarse mediante onCreate(Bundle) o onRestoreInstanceState(Bundle) .

Mi colega escribió un artículo explicando el estado de la aplicación en los dispositivos Android, que incluye explicaciones sobre el ciclo de vida de la actividad y la información del estado, cómo almacenar la información del estado, y guardar en Bundle estatal y SharedPreferences y aquí .

El artículo cubre tres enfoques:

Almacene los datos de control de varilla / UI locales para la vida útil de la aplicación (es decir, temporalmente) utilizando el paquete de estado de instancia

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

Almacene los datos de control de varilla / UI locales entre las instancias de la aplicación (es decir, permanentemente) utilizando Preferencias compartidas

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

Mantener activas las instancias de objetos en la memoria entre las actividades dentro de la vida útil de la aplicación utilizando Instancia no configurada retenida

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

Este es un clásico ‘gotcha’ del desarrollo de Android. Hay dos problemas aquí:

  • Existe un error sutil de Android Framework que complica en gran medida la administración de la stack de aplicaciones durante el desarrollo, al menos en las versiones heredadas (no del todo seguro de si / cuándo / cómo se solucionó). Discutiré este error a continuación.
  • La forma ‘normal’ o intencionada de manejar este problema es, en sí misma, bastante complicada con la dualidad de onPause / onResume y onSaveInstanceState / onRestoreInstanceState

Navegando a través de todos estos hilos, sospecho que la mayoría de las veces los desarrolladores están hablando de estos dos temas diferentes al mismo tiempo … de ahí toda la confusión y los informes de “esto no funciona para mí”.

Primero, para aclarar el comportamiento ‘previsto’: onSaveInstance y onRestoreInstance son frágiles y solo para el estado transitorio. El uso previsto (afaict) es manejar recreación de actividad cuando se gira el teléfono (cambio de orientación). En otras palabras, el uso previsto es cuando su actividad aún está lógicamente “en la parte superior”, pero el sistema debe volver a instalarla. El paquete guardado no se conserva fuera del proceso / memoria / gc, por lo que no puede confiar en esto si su actividad pasa al segundo plano. Sí, quizás la memoria de tu Actividad sobrevivirá a su viaje al fondo y escapará de GC, pero esto no es confiable (ni tampoco es predecible).

Por lo tanto, si tiene un escenario en el que hay un “progreso del usuario” significativo o un estado que debe persistir entre ‘lanzamientos’ de su aplicación, la guía es usar onPause y onResume. Debe elegir y preparar una tienda persistente usted mismo.

PERO – hay un error muy confuso que complica todo esto. Los detalles están aquí:

http://code.google.com/p/android/issues/detail?id=2373

http://code.google.com/p/android/issues/detail?id=5277

Básicamente, si su aplicación se inicia con la marca SingleTask, y luego la inicia desde la pantalla de inicio o desde el menú del iniciador, la siguiente invocación creará una NUEVA tarea … efectivamente tendrá dos instancias diferentes de su aplicación habitando en la misma stack … lo cual se vuelve muy extraño muy rápido. Esto parece suceder cuando inicias tu aplicación durante el desarrollo (es decir, desde Eclipse o Intellij), por lo que los desarrolladores se topan con esto mucho. Pero también a través de algunos de los mecanismos de actualización de la tienda de aplicaciones (por lo que también afecta a los usuarios).

Luché a través de estos hilos durante horas antes de darme cuenta de que mi problema principal era este error, no el comportamiento previsto del marco. Una gran reseña y solución alternativa (ACTUALIZACIÓN: ver a continuación) parece ser del usuario @kaciula en esta respuesta:

Comportamiento de la tecla de inicio

ACTUALIZACIÓN Junio ​​de 2013 : Meses después, finalmente encontré la solución ‘correcta’. No es necesario que administre ninguna bandera stateA startedApp usted mismo, puede detectar esto desde el marco y fianza de forma adecuada. Lo uso cerca del comienzo de mi 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; } } 

onSaveInstanceState se onSaveInstanceState cuando el sistema necesita memoria y mata a una aplicación. No se llama cuando el usuario simplemente cierra la aplicación. Así que creo que el estado de la aplicación también debe guardarse en onPause Se debe guardar en algún almacenamiento persistente como Preferences o Sqlite

Ambos métodos son útiles y válidos, y ambos son más adecuados para diferentes escenarios:

  1. El usuario finaliza la aplicación y la vuelve a abrir en una fecha posterior, pero la aplicación necesita volver a cargar los datos de la última sesión; esto requiere un enfoque de almacenamiento persistente como el uso de SQLite.
  2. El usuario cambia la aplicación y luego vuelve al original y desea continuar donde lo dejó: guardar y restaurar los datos del paquete (como los datos de estado de la aplicación) en onSaveInstanceState() y onRestoreInstanceState() suele ser adecuado.

Si guarda los datos de estado de manera persistente, puede volver a cargarse en un onResume() o onCreate() (o en realidad en cualquier llamada de ciclo de vida). Esto puede o no ser un comportamiento deseado. Si lo almacena en un paquete en un InstanceState , entonces es transitorio y solo es adecuado para almacenar datos para usar en la misma ‘sesión’ de usuario (utilizo el término sesión libremente) pero no entre ‘sesiones’.

No es que un enfoque sea mejor que el otro, como todo, simplemente es importante entender qué comportamiento requiere y seleccionar el enfoque más apropiado.

El estado de ahorro es, en el mejor de los casos, un desafío en lo que a mí respecta. Si necesita guardar datos persistentes, solo use una base de datos SQLite . Android lo hace MUY fácil.

Algo como esto:

 import java.util.Date; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class dataHelper { private static final String DATABASE_NAME = "autoMate.db"; private static final int DATABASE_VERSION = 1; private Context context; private SQLiteDatabase db; private OpenHelper oh ; public dataHelper(Context context) { this.context = context; this.oh = new OpenHelper(this.context); this.db = oh.getWritableDatabase(); } public void close() { db.close(); oh.close(); db = null; oh = null; SQLiteDatabase.releaseMemory(); } public void setCode(String codeName, Object codeValue, String codeDataType) { Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+ codeName + "'", null); String cv = "" ; if (codeDataType.toLowerCase().trim().equals("long") == true) { cv = String.valueOf(codeValue); } else if (codeDataType.toLowerCase().trim().equals("int") == true) { cv = String.valueOf(codeValue); } else if (codeDataType.toLowerCase().trim().equals("date") == true) { cv = String.valueOf(((Date)codeValue).getTime()); } else if (codeDataType.toLowerCase().trim().equals("boolean") == true) { String.valueOf(codeValue); } else { cv = String.valueOf(codeValue); } if(codeRow.getCount() > 0) //exists-- update { db.execSQL("update code set codeValue = '" + cv + "' where codeName = '" + codeName + "'"); } else // does not exist, insert { db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" + "'" + codeName + "'," + "'" + cv + "'," + "'" + codeDataType + "')" ); } } public Object getCode(String codeName, Object defaultValue) { //Check to see if it already exists String codeValue = ""; String codeDataType = ""; boolean found = false; Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+ codeName + "'", null); if (codeRow.moveToFirst()) { codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue")); codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType")); found = true; } if (found == false) { return defaultValue; } else if (codeDataType.toLowerCase().trim().equals("long") == true) { if (codeValue.equals("") == true) { return (long)0; } return Long.parseLong(codeValue); } else if (codeDataType.toLowerCase().trim().equals("int") == true) { if (codeValue.equals("") == true) { return (int)0; } return Integer.parseInt(codeValue); } else if (codeDataType.toLowerCase().trim().equals("date") == true) { if (codeValue.equals("") == true) { return null; } return new Date(Long.parseLong(codeValue)); } else if (codeDataType.toLowerCase().trim().equals("boolean") == true) { if (codeValue.equals("") == true) { return false; } return Boolean.parseBoolean(codeValue); } else { return (String)codeValue; } } private static class OpenHelper extends SQLiteOpenHelper { OpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS code" + "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } } 

Una simple llamada después de eso

 dataHelper dh = new dataHelper(getBaseContext()); String status = (String) dh.getCode("appState", "safetyDisabled"); Date serviceStart = (Date) dh.getCode("serviceStartTime", null); dh.close(); dh = null; 

Creo que encontré la respuesta. Déjame decirte lo que he hecho en palabras simples:

Supongamos que tengo dos actividades, actividad1 y actividad2 y estoy navegando de la actividad1 a la actividad2 (he realizado algunos trabajos en la actividad2) y de nuevo a la actividad 1 haciendo clic en un botón en la actividad1. Ahora, en esta etapa, quería volver a activity2 y quiero ver mi activity2 en la misma condición cuando me fui de activity2 por última vez.

Para el escenario anterior, lo que hice fue que en el manifiesto realicé algunos cambios como este:

   

Y en la actividad1 en el botón, haga clic en evento que he hecho así:

 Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.setClassName(this,"com.mainscreen.activity2"); startActivity(intent); 

Y en activity2 en el botón clic evento he hecho esto:

 Intent intent=new Intent(); intent.setClassName(this,"com.mainscreen.activity1"); startActivity(intent); 

Ahora lo que sucederá es que cualquiera que sean los cambios que hayamos hecho en la actividad2 no se perderá, y podemos ver activity2 en el mismo estado que dejamos anteriormente.

Creo que esta es la respuesta y esto funciona bien para mí. Corrígeme si estoy equivocado.

onSaveInstanceState() para datos transitorios (restaurado en onCreate() / onRestoreInstanceState() ), onPause() para datos persistentes (restaurado en onResume() ). De los recursos técnicos de Android:

onSaveInstanceState () es llamado por Android si la actividad se detiene y puede ser eliminada antes de que se reanude. Esto significa que debe almacenar cualquier estado necesario para reiniciarse en la misma condición cuando se reinicie la actividad. Es la contraparte del método onCreate (), y de hecho el paquete savedInstanceState pasado a onCreate () es el mismo paquete que construye como outState en el método onSaveInstanceState ().

onPause () y onResume () también son métodos complementarios. onPause () siempre se invoca cuando termina la actividad, incluso si lo instigamos (con una llamada a finish () por ejemplo). Usaremos esto para guardar la nota actual en la base de datos. Una buena práctica es liberar todos los recursos que se pueden liberar durante un OnPause () también, para tomar menos recursos en estado pasivo.

Recreando una actividad

Hay algunos escenarios en los que su actividad se destruye debido al comportamiento normal de la aplicación, como cuando el usuario presiona el botón Atrás o su actividad señala su propia destrucción al llamar a finish() . El sistema también puede destruir su actividad si actualmente está detenido y no se ha utilizado durante mucho tiempo o si la actividad en primer plano requiere más recursos, por lo que el sistema debe cerrar los procesos en segundo plano para recuperar la memoria.

Cuando se destruye su activity porque el usuario presiona Atrás o la activity finaliza, el concepto del sistema de esa instancia de Activity se ha ido para siempre porque el comportamiento indica que la actividad ya no es necesaria. Sin embargo, si el sistema destruye la actividad debido a restricciones del sistema (en lugar del comportamiento normal de la aplicación), aunque la instancia de Actividad real haya desaparecido, el sistema recuerda que existió de manera que si el usuario navega de regreso a ella, el sistema crea una nueva instancia de la actividad que utiliza un conjunto de datos guardados que describe el estado de la actividad cuando se destroyed . Los datos guardados que el sistema utiliza para restaurar el estado anterior se denomina “estado de instancia” y es una colección de pares clave-valor almacenados en un objeto Bundle.

Para guardar datos adicionales sobre el estado de la actividad, debe anular el método de callback onSaveInstanceState (). El sistema llama a este método cuando el usuario abandona su actividad y le pasa el objeto Bundle que se guardará en caso de que su actividad se destruya inesperadamente. Si el sistema debe volver a crear la instancia de actividad más tarde, pasa el mismo objeto Bundle a los onRestoreInstanceState() y onCreate() . enter image description here

A medida que el sistema comienza a detener su actividad, llama a onSaveInstanceState() (1) para que pueda especificar los datos de estado adicionales que desea guardar en caso de que la instancia de Activity deba recrearse. Si la actividad se destruye y se debe recrear la misma instancia, el sistema pasa los datos de estado definidos en (1) al método onCreate() (2) y al método onRestoreInstanceState() (3).

Guarde su estado de Activity

Cuando su actividad comienza a detenerse, el sistema llama a onSaveInstanceState() para que su actividad pueda guardar información de estado con una colección de pares clave-valor. La implementación predeterminada de este método guarda información sobre el estado de la jerarquía de vistas de la actividad, como el texto en un widget EditText o la posición de desplazamiento de un ListView .

Para guardar información de estado adicional para su actividad, debe implementar onSaveInstanceState() y agregar pares clave-valor al objeto Bundle. Por ejemplo:

  static final String STATE_SCORE = "playerScore"; static final String STATE_LEVEL = "playerLevel"; @Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save the user's current game state savedInstanceState.putInt(STATE_SCORE, mCurrentScore); savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel); // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(savedInstanceState); } 

Precaución: llame siempre a la implementación de superclase de onSaveInstanceState() para que la implementación predeterminada pueda guardar el estado de la jerarquía de vistas.

Restaure su estado de Activity

Cuando se recrea su actividad después de que se destruyó previamente, puede recuperar su estado guardado del paquete para que el sistema pase su actividad. Los métodos de callback onCreate() y onRestoreInstanceState() reciben el mismo Bundle que contiene la información de estado de la instancia.

Debido a que se llama al método onCreate() ya sea que el sistema esté creando una nueva instancia de su actividad o recreando una anterior, debe verificar si el paquete de estado es nulo antes de intentar leerlo. Si es nulo, el sistema está creando una nueva instancia de la actividad, en lugar de restaurar una anterior que se destruyó.

Por ejemplo, aquí le onCreate() cómo puede restaurar algunos datos de estado en onCreate() :

  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Always call the superclass first // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { // Restore value of members from saved state mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } else { // Probably initialize members with default values for a new instance } } 

En lugar de restaurar el estado durante onCreate() puede optar por implementar onRestoreInstanceState() , que el sistema llama después del método onStart() . El sistema llama a onRestoreInstanceState() solo si hay un estado guardado para restaurar, por lo que no necesita verificar si el paquete es nulo:

  public void onRestoreInstanceState(Bundle savedInstanceState) { // Always call the superclass so it can restre the view hierarchy super.onRestoreInstanceState(savedInstanceState); // Restore state members from saved instance mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } 

Realmente onSaveInstance indica callen cuando la actividad va al fondo

Cita de los documentos: “se llama al método onSaveInstanceState(Bundle) antes de colocar la actividad en dicho estado de fondo”

Mientras tanto, en general, ya no uso más

 Bundle savedInstanceState & Co 

el ciclo de vida es para la mayoría de las actividades demasiado complicado y no necesario. Y Google se declara a sí mismo, ni siquiera es confiable.

Mi manera es guardar cualquier cambio inmediatamente en las preferencias

  SharedPreferences p; p.edit().put(..).commit() 

de alguna manera las SharedPreferences funcionan de manera similar a Bundles. Y, naturalmente, al principio, dichos valores deben ser rojos a partir de las preferencias.

En el caso de datos complejos, puede usar Sqlite en lugar de usar preferencias.

When applying this concept, the activity just continues to use the last saved state, regardless whether it was an initial open with reboots in between or a reopen due to the back stack.

To help reduce boilerplate I use the following interface and class to read/write to a Bundle for saving instance state.


First, create an interface that will be used to annotate your instance variables:

 import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) public @interface SaveInstance { } 

Then, create a class where reflection will be used to save values to the bundle:

 import android.app.Activity; import android.app.Fragment; import android.os.Bundle; import android.os.Parcelable; import android.util.Log; import java.io.Serializable; import java.lang.reflect.Field; /** * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link * SaveInstance}. */ public class Icicle { private static final String TAG = "Icicle"; /** * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}. * * @param outState * The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link * Fragment#onSaveInstanceState(Bundle)} * @param classInstance * The object to access the fields which have the {@link SaveInstance} annotation. * @see #load(Bundle, Object) */ public static void save(Bundle outState, Object classInstance) { save(outState, classInstance, classInstance.getClass()); } /** * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}. * * @param outState * The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link * Fragment#onSaveInstanceState(Bundle)} * @param classInstance * The object to access the fields which have the {@link SaveInstance} annotation. * @param baseClass * Base class, used to get all superclasses of the instance. * @see #load(Bundle, Object, Class) */ public static void save(Bundle outState, Object classInstance, Class baseClass) { if (outState == null) { return; } Class clazz = classInstance.getClass(); while (baseClass.isAssignableFrom(clazz)) { String className = clazz.getName(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(SaveInstance.class)) { field.setAccessible(true); String key = className + "#" + field.getName(); try { Object value = field.get(classInstance); if (value instanceof Parcelable) { outState.putParcelable(key, (Parcelable) value); } else if (value instanceof Serializable) { outState.putSerializable(key, (Serializable) value); } } catch (Throwable t) { Log.d(TAG, "The field '" + key + "' was not added to the bundle"); } } } clazz = clazz.getSuperclass(); } } /** * Load all saved fields that have the {@link SaveInstance} annotation. * * @param savedInstanceState * The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}. * @param classInstance * The object to access the fields which have the {@link SaveInstance} annotation. * @see #save(Bundle, Object) */ public static void load(Bundle savedInstanceState, Object classInstance) { load(savedInstanceState, classInstance, classInstance.getClass()); } /** * Load all saved fields that have the {@link SaveInstance} annotation. * * @param savedInstanceState * The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}. * @param classInstance * The object to access the fields which have the {@link SaveInstance} annotation. * @param baseClass * Base class, used to get all superclasses of the instance. * @see #save(Bundle, Object, Class) */ public static void load(Bundle savedInstanceState, Object classInstance, Class baseClass) { if (savedInstanceState == null) { return; } Class clazz = classInstance.getClass(); while (baseClass.isAssignableFrom(clazz)) { String className = clazz.getName(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(SaveInstance.class)) { String key = className + "#" + field.getName(); field.setAccessible(true); try { Object fieldVal = savedInstanceState.get(key); if (fieldVal != null) { field.set(classInstance, fieldVal); } } catch (Throwable t) { Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle"); } } } clazz = clazz.getSuperclass(); } } } 

Ejemplo de uso:

 public class MainActivity extends Activity { @SaveInstance private String foo; @SaveInstance private int bar; @SaveInstance private Intent baz; @SaveInstance private boolean qux; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icicle.load(savedInstanceState, this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icicle.save(outState, this); } } 

Note: This code was adapted from a library project named AndroidAutowire which is licensed under the MIT license .

To answer the original question directly. savedInstancestate is null because your Activity is never being re-created.

Your Activity will only be re-created with a state bundle when:

  • Configuration changes such as changing the orientation or phone language which may requires a new activity instance to be created.
  • You return to the app from the background after the OS has destroyed the activity.

Android will destroy background activities when under memory pressure or after they’ve been in the background for an extended period of time.

When testing your hello world example there are a few ways to leave and return to the Activity.

  • When you press the back button the Activity is finished. Re-launching the app is a brand new instance. You aren’t resuming from the background at all.
  • When you press the home button or use the task switcher the Activity will go into the background. When navigating back to the application onCreate will only be called if the Activity had to be destroyed.

In most cases if you’re just pressing home and then launching the app again the activity won’t need to be re-created. It already exists in memory so onCreate() won’t be called.

There is an option under Settings -> Developer Options called “Don’t keep activities”. When it’s enabled Android will always destroy activities and recreate them when they’re backgrounded. This is a great option to leave enabled when developing because it simulates the worst case scenario. ( A low memory device recycling your activities all the time ).

The other answers are valuable in that they teach you the correct ways to store state but I didn’t feel they really answered WHY your code wasn’t working in the way you expected.

The onSaveInstanceState(bundle) and onRestoreInstanceState(bundle) methods are useful for data persistence merely while rotating the screen (orientation change).
They are not even good while switching between applications (since the onSaveInstanceState() method is called but onCreate(bundle) and onRestoreInstanceState(bundle) is not invoked again.
For more persistence use shared preferences. read this article

My problem was that I needed persistence only during the application lifetime (ie a single execution including starting other sub-activities within the same app and rotating the device etc). I tried various combinations of the above answers but did not get what I wanted in all situations. In the end what worked for me was to obtain a reference to the savedInstanceState during onCreate:

 mySavedInstanceState=savedInstanceState; 

and use that to obtain the contents of my variable when I needed it, along the lines of:

 if (mySavedInstanceState !=null) { boolean myVariable = mySavedInstanceState.getBoolean("MyVariable"); } 

I use onSaveInstanceState and onRestoreInstanceState as suggested above but I guess i could also or alternatively use my method to save the variable when it changes (eg using putBoolean )

Although the accepted answer is correct, there is a faster and easier method to save the Activity state on Android using a library called Icepick . Icepick is an annotation processor that takes care of all the boilerplate code used in saving and restring state for you.

Doing something like this with Icepick:

 class MainActivity extends Activity { @State String username; // These will be automatically saved and restred @State String password; @State int age; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icepick.restreInstanceState(this, savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } } 

Is the same as doing this:

 class MainActivity extends Activity { String username; String password; int age; @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putString("MyString", username); savedInstanceState.putString("MyPassword", password); savedInstanceState.putInt("MyAge", age); /* remember you would need to actually initialize these variables before putting it in the Bundle */ } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); username = savedInstanceState.getString("MyString"); password = savedInstanceState.getString("MyPassword"); age = savedInstanceState.getInt("MyAge"); } } 

Icepick will work with any object that saves its state with a Bundle .

There are basically two ways to implement this change.

  1. using onSaveInstanceState() and onRestoreInstanceState() .
  2. In manifest android:configChanges="orientation|screenSize" .

I really do not recommend to use second method. Since in one of my experience it was causing half of the device screen black while rotating from portrait to landscape and vice versa.

Using first method mentioned above , we can persist data when orientation is changed or any config change happens. I know a way in which you can store any type of data inside savedInstance state object.

Example: Consider a case if you want to persist Json object. create a model class with getters and setters .

 class MyModel extends Serializable{ JSONObject obj; setJsonObject(JsonObject obj) { this.obj=obj; } JSONObject getJsonObject() return this.obj; } } 

Now in your activity in onCreate and onSaveInstanceState method do the following. It will look something like this:

 @override onCreate(Bundle savedInstaceState){ MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey") JSONObject obj=data.getJsonObject(); //Here you have retained JSONObject and can use. } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //Obj is some json object MyModel dataToSave= new MyModel(); dataToSave.setJsonObject(obj); oustate.putSerializable("yourkey",dataToSave); } 

When an activity is created it’s onCreate() method is called.

  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } 

savedInstanceState is an object of Bundle class which is null for the first time, but it contains values when it is recreated. To save Activity’s state you have to override onSaveInstanceState().

  @Override protected void onSaveInstanceState(Bundle outState) { outState.putString("key","Welcome Back") super.onSaveInstanceState(outState); //save state } 

put your values in “outState” Bundle object like outState.putString(“key”,”Welcome Back”) and save by calling super. When activity will be destroyed it’s state get saved in Bundle object and can be restred after recreation in onCreate() or onRestoreInstanceState(). Bundle received in onCreate() and onRestoreInstanceState() are same.

  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //restre activity's state if(savedInstanceState!=null){ String reStoredString=savedInstanceState.getString("key"); } } 

o

  //restres activity's saved state @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { String restredMessage=savedInstanceState.getString("key"); } 

Here is a comment from Steve Moseley ‘s answer (by ToolmakerSteve ) that puts things into perspective (in the whole onSaveInstanceState vs onPause, east cost vs west cost saga)

@VVK – I partially disagree. Some ways of exiting an app don’t trigger onSaveInstanceState (oSIS). This limits the usefulness of oSIS. Its worth supporting, for minimal OS resources, but if an app wants to return the user to the state they were in, no matter how the app was exited, it is necessary to use a persistent storage approach instead. I use onCreate to check for bundle, and if it is missing, then check persistent storage. This centralizes the decision making. I can recover from a crash, or back button exit or custom menu item Exit, or get back to screen user was on many days later. – ToolmakerSteve Sep 19 ’15 at 10:38

Not sure if my solution is frowned upon or not but I use a bound service to persist ViewModel state. Whether you store it in memory in the service or persist and retrieve from a SqlLite database depends on your requirements. This is what services of any flavor do, they provide services such as maintaining application state and abstract common business logic.

Because of memory and processing constraints inherent on mobile devices, I treat Android views in a similar way to a web page. The page does not maintain state, it is purely a presentation layer component whose only purpose is to present application state and accept user input. Recent trends in web app architecture employ the use of the age old Model, View, Controller (MVC) pattern, where the page is the View, Domain data is the model and the controller sits behind a web service. The same pattern can be employed in android with the View being well … the View, the model is your domain data and the Controller is implemented as an Android bound service. Whenever you want a view to interact with the controller, bind to it on start/resume and unbind on stop/pause.

This approach gives you the added bonus of enforcing the Separation of Concern design principle in that all of you application business logic can be moved into your service which reduces duplicated logic across multiple views and allows the view to enforce another important design principle, Single Responsibility.

Simple quick to solve this problem is using IcePick

First, setup the library in app/build.gradle

 repositories { maven {url "https://clojars.org/repo/"} } dependencies { compile 'frankiesardo:icepick:3.2.0' provided 'frankiesardo:icepick-processor:3.2.0' } 

Now, let’s check this example below how to save state in Activity

 public class ExampleActivity extends Activity { @State String username; // This will be automatically saved and restred @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icepick.restreInstanceState(this, savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } } 

It works for Activities, Fragments or any object that needs to serialize its state on a Bundle (eg mortar’s ViewPresenters)

Icepick can also generate the instance state code for custom Views:

 class CustomView extends View { @State int selectedPosition; // This will be automatically saved and restred @Override public Parcelable onSaveInstanceState() { return Icepick.saveInstanceState(this, super.onSaveInstanceState()); } @Override public void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(Icepick.restreInstanceState(this, state)); } // You can put the calls to Icepick into a BaseCustomView and inherit from it // All Views extending this CustomView automatically have state saved/restred } 

To get activity state data stored in onCreate() , first you have to save data in savedInstanceState by overriding SaveInstanceState(Bundle savedInstanceState) method.

When activity destroy SaveInstanceState(Bundle savedInstanceState) method gets called and there you save data you want to save. And you get same in onCreate() when activity restart.(savedInstanceState wont be null since you have saved some data in it before activity get destroyed)

Kotlin code:

save:

 override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState.apply { putInt("intKey", 1) putString("stringKey", "String Value") putParcelable("plotbleKey", plotbleObject) }) } 

and then in onCreate() or onRestoreInstanceState()

  val restredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int val restredString = savedInstanceState?.getString("stringKey") ?: "default string" val restredParcelable = savedInstanceState?.getParcelable("plotbleKey") ?: ParcelableClass() //default plotble 

Add default values if you don’t want to have Optionals

Now Android provides ViewModels for saving state, you should try to use that instead of saveInstanceState.