android - vida - savedinstancestate




Guardar el estado de la actividad de Android utilizando Guardar estado de instancia (18)

He estado trabajando en la plataforma de Android SDK, y no está nada claro cómo guardar el estado de una aplicación. Entonces, dado este pequeño cambio de herramientas del ejemplo '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 aleje 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 aproximadamente y no he encontrado nada obvio.


Agregar LiveData (componentes de la arquitectura de Android) a su proyecto

agregue la siguiente dependencia

implementation "android.arch.lifecycle:extensions:1.1.0"

LiveData toma un observador y le notifica sobre los cambios en los datos solo cuando está en estado INICIADO o REANUDADO. La ventaja con LiveData es que cuando su actividad entra en cualquier estado distinto de INICIADO o REANUDADO , no llamará al método onChanged en el observador .

private TextView mTextView;
private MutableLiveData<String> mMutableLiveData;

@Override
protected void onCreate(Bundle savedInstanceState) {
    mTextView = (TextView) findViewById(R.id.textView);
    mMutableLiveData = new MutableLiveData<>();
    mMutableLiveData.observe(this, new Observer<String>() {
        @Override
        public void onChanged(@Nullable String s) {
            mTextView.setText(s);
        }
    });

}

Ambos métodos son útiles y válidos, y ambos son los 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 de aplicación y luego regresa al original y desea continuar donde lo dejó: guardar y restaurar los datos del paquete (como los datos del estado de la aplicación) en onSaveInstanceState() y onRestoreInstanceState() es generalmente adecuado.

Si guarda los datos de estado de forma persistente, puede volver a cargarlos en onResume() o onCreate() (o en realidad en cualquier llamada de ciclo de vida). Este comportamiento puede o no ser deseado. Si lo almacena en un paquete en un estado de InstanceState , entonces es transitorio y solo es adecuado para almacenar datos para su uso en la misma 'sesión' del usuario (uso el término sesión de manera suelta) pero no entre 'sesiones'.

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


El estado de ahorro es, en el mejor de los casos, un gran problema en lo que a mí respecta. Si necesita guardar datos persistentes, solo use una SQLite datos SQLite . Android lo hace tan 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;

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

  • Existe un error sutil de Android Framework que complica enormemente la administración de la pila de aplicaciones durante el desarrollo, al menos en versiones heredadas (no estoy completamente seguro de si / cuándo / cómo se solucionó). Voy a discutir este error a continuación.
  • La forma "normal" o prevista para gestionar este problema es, en sí misma, bastante complicada con la dualidad de onPause / onResume y onSaveInstanceState / onRestoreInstanceState

Al examinar todos estos hilos, sospecho que la mayoría de los desarrolladores de tiempo hablan de estos dos temas diferentes simultáneamente ... por lo tanto, toda la confusión y los informes de "esto no funciona para mí".

Primero, para aclarar el comportamiento "deseado": onSaveInstance y onRestoreInstance son frágiles y solo para el estado transitorio. El uso previsto (afaict) es manejar la recreación de actividad cuando se gira el teléfono (cambio de orientación). En otras palabras, el uso previsto es cuando su Actividad todavía está lógicamente "en la parte superior", pero aún debe ser reenlazada por el sistema. El paquete guardado no se conserva fuera del proceso / memoria / gc, por lo que realmente no puede confiar en esto si su actividad pasa al fondo. Sí, tal vez la memoria de su Actividad sobrevivirá a su viaje al fondo y escape al GC, pero esto no es confiable (ni es previsible).

Entonces, si tiene un escenario en el que hay un 'progreso' o estado significativo que debe persistir entre los 'lanzamientos' de su aplicación, la guía es usar onPause y onResume. Debes elegir y preparar una tienda persistente tú 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 lanza desde la pantalla de inicio o desde el menú de inicio, entonces esa invocación posterior creará una NUEVA tarea ... efectivamente tendrá dos instancias diferentes de su aplicación habitando la misma pila ... lo que 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 encuentran 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 sus usuarios).

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

Tecla de inicio con el comportamiento

ACTUALIZACIÓN Junio ​​de 2013 : Meses después, finalmente encontré la solución 'correcta'. No es necesario que gestione ninguna marca de aplicación de inicio con estado, puede detectar esto desde el marco y rescatarla adecuadamente. 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;
    }
}

Se llama a onSaveInstanceState cuando el sistema necesita memoria y mata 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 debería guardarse en onPause Debería guardarse en un almacenamiento persistente como Preferences o Sqlite


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

Los estados del documento (en la sección 'Ciclo de vida de la actividad'):

Tenga en cuenta que es importante guardar los 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 llamará en todas las situaciones 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 de onSaveInstanceState() :

Se llama a este método antes de que una actividad pueda ser eliminada para que cuando vuelva en algún momento en el futuro pueda restaurar su estado. Por ejemplo, si la actividad B se inicia frente a la actividad A, y en algún momento se cancela 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 a la actividad A, el estado de la interfaz de usuario se puede restaurar a través de onCreate(Bundle) o onRestoreInstanceState(Bundle) .


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

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

El paquete es esencialmente una forma de almacenar un mapa NVP ("Nombre-Par de valores"), y se pasará a onCreate() y también a onRestoreInstanceState() donde extraerá los valores como este:

@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, usaría esta técnica para almacenar valores de instancia para su aplicación (selecciones, texto no guardado, etc.).


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

Para un estado más duradero, considere usar una base de datos SQLite, un archivo o preferencias. Ver Ahorro de estado persistente .


Código Kotlin:

salvar:

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

y luego en onCreate()oonRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

Agrega valores predeterminados si no quieres tener Opcionales


Cuando se crea una actividad, se llama al método onCreate ().

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

savedInstanceState es un objeto de la clase Bundle que es nulo por primera vez, pero contiene valores cuando se recrea. Para guardar el estado de la actividad, debe anular onSaveInstanceState ().

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

ponga sus valores en el objeto del paquete "outState" como outState.putString ("key", "Welcome Back") y guárdelo llamando a super. Cuando se destruye la actividad, se guarda el estado en el objeto Bundle y se puede restaurar después de la recreación en onCreate () o onRestoreInstanceState (). El paquete recibido en onCreate () y onRestoreInstanceState () es el mismo.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

o

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

Mientras tanto, en general no hago más uso.

Bundle savedInstanceState & Co

El ciclo en vivo es para la mayoría de las actividades demasiado complicado y no es necesario. Y el propio estado de Google, ni siquiera es confiable.

Mi manera es guardar cualquier cambio inmediatamente en las preferencias.

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

De alguna manera, las preferencias compartidas funcionan de manera similar a los paquetes. Y naturalmente, y al principio, tales valores tienen que ser rojos de las preferencias.

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

Al aplicar este concepto, la actividad simplemente continúa utilizando el último estado guardado, independientemente de si fue una apertura inicial con reinicios intermedios o una reapertura debida a la pila trasera.


Ahora Android proporciona ViewModels para guardar el estado, debe intentar usar eso en lugar de saveInstanceState.


Aunque la respuesta aceptada es correcta, existe un método más rápido y sencillo para guardar el estado de la actividad en Android mediante una biblioteca llamada Icepick . Icepick es un procesador de anotaciones que se encarga de todo el código de repetición utilizado para guardar y restaurar el estado.

Haciendo algo así con Icepick:

class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

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

Es lo mismo que hacer esto:

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 trabajará con cualquier objeto que guarde su estado con un Bundle.


Los métodos onSaveInstanceState(bundle)y onRestoreInstanceState(bundle)son útiles para la persistencia de datos simplemente mientras se gira la pantalla (cambio de orientación).
Ni siquiera son buenas, mientras que el cambio entre aplicaciones (ya que el onSaveInstanceState()se llama método, pero onCreate(bundle)y onRestoreInstanceState(bundle)no se invoca de nuevo.
Para un uso más persistencia preferencias compartidas. Leer este artículo


No estoy seguro de si mi solución está mal vista o no, pero uso un servicio vinculado para mantener el estado de ViewModel. Si lo almacena en la memoria en el servicio o si persiste y recupera de una base de datos SqlLite depende de sus requisitos. Esto es lo que hacen los servicios de cualquier tipo, brindan servicios como el mantenimiento del estado de la aplicación y la lógica común de negocios abstracta.

Debido a las limitaciones de memoria y procesamiento inherentes a los dispositivos móviles, trato las vistas de Android de manera similar a una página web. La página no mantiene el estado, es puramente un componente de la capa de presentación cuyo único propósito es presentar el estado de la aplicación y aceptar la entrada del usuario. Las tendencias recientes en la arquitectura de aplicaciones web emplean el uso del antiguo modelo Modelo, Vista, Controlador (MVC), donde la página es la Vista, los datos del Dominio son el modelo y el controlador se encuentra detrás de un servicio web. El mismo patrón se puede emplear en Android con la Vista en buen estado ... la Vista, el modelo son los datos de su dominio y el Controlador se implementa como un servicio vinculado a Android. Cuando quiera que una vista interactúe con el controlador, enlácela al iniciar / reanudar y desvincular en detener / pausar.

Este enfoque le brinda la ventaja adicional de imponer el principio de diseño de Separación de Preocupación en el sentido de que todas las aplicaciones de la lógica de negocios pueden moverse a su servicio, lo que reduce la lógica duplicada en múltiples vistas y permite que la vista aplique otro principio de diseño importante, la Responsabilidad Única.


Para ayudar a reducir la plantilla, uso lo siguiente interfacey classpara leer / escribir en un Bundleestado de instancia de guardado.

Primero, cree una interfaz que se usará para anotar sus variables de instancia:

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 {

}

Luego, cree una clase donde se usará la reflexión para guardar valores en el paquete:

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}.</p>
 */
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);
    }

}

Nota: este código fue adaptado de un proyecto de biblioteca llamado AndroidAutowire que está licenciado bajo la licencia MIT .


Para responder a la pregunta original directamente. savedInstancestate es nulo porque su actividad nunca se vuelve a crear.

Su actividad solo se volverá a crear con un paquete estatal cuando:

  • Los cambios de configuración, como cambiar la orientación o el idioma del teléfono, pueden requerir la creación de una nueva instancia de actividad.
  • Volverá a la aplicación desde el fondo después de que el sistema operativo haya destruido la actividad.

Android destruirá las actividades en segundo plano cuando esté bajo presión de memoria o después de haber estado en segundo plano durante un período prolongado de tiempo.

Al probar el ejemplo de "hola mundo", hay algunas formas de irse y volver a la actividad.

  • Cuando se presiona el botón Atrás, la actividad ha finalizado. Relanzar la aplicación es una nueva instancia. No estás reanudando desde el fondo en absoluto.
  • Cuando presiona el botón de inicio o usa el conmutador de tareas, la Actividad pasará a un segundo plano. Cuando se navegue de regreso a la aplicación, onCreate solo será llamado si la Actividad tuvo que ser destruida.

En la mayoría de los casos, si presiona Inicio y luego vuelve a iniciar la aplicación, no será necesario volver a crear la actividad. Ya existe en la memoria por lo que no se llamará a onCreate ().

Hay una opción en Configuración -> Opciones de desarrollador llamada "No guardar actividades". Cuando está habilitado, Android siempre destruirá las actividades y las volverá a crear cuando estén en segundo plano. Esta es una gran opción para dejarlo habilitado cuando se desarrolla porque simula el peor escenario. (Un dispositivo de memoria baja reciclando tus actividades todo el tiempo).

Las otras respuestas son valiosas porque le enseñan las formas correctas de almacenar el estado, pero no sentí que realmente respondieran POR QUÉ su código no estaba funcionando como esperaba.


Realmente decir onSaveInstancecallen cuando la actividad pasa a segundo plano

Cita de la documentación: "el método onSaveInstanceState(Bundle)se llama antes de colocar la actividad en ese estado de fondo"





application-state