Pregunta

He estado trabajando en la plataforma Android SDK, y no está claro cómo guardar el estado de una aplicación. Entonces, dada esta pequeña herramienta 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 navegue fuera 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.

¿Fue útil?

Solución

Debe anular onSaveInstanceState (Bundle savedInstanceState) y escribir los valores de estado de la aplicación que desea cambiar al parámetro Bundle de 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-Valor Par "), y se pasará a onCreate () y también onRestoreInstanceState () donde luego extraerías 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");
}

Usualmente usaría esta técnica para almacenar valores de instancia para su aplicación (selecciones, texto no guardado, etc.).

Otros consejos

El 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 volver a aparecer. Fue antes. Consulte la documentación para onCreate y onSaveInstanceState

Para un estado más longevo, considere usar una base de datos SQLite, un archivo o preferencias. Consulte Guardar estado persistente .

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

El documento establece (en la sección 'Ciclo de vida de la actividad'):

  

Tenga en cuenta que es importante guardar   datos persistentes en onPause () en su lugar   de onSaveInstanceState (Bundle)   porque el último no es parte de la   devoluciones de llamada del ciclo de vida, por lo que no será   llamado en cada situación como se describe   en su documentación.

En otras palabras, coloque 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 () :

  

Este método se llama antes de que una actividad pueda ser eliminada para que cuando   vuelve en algún momento en el futuro, puede restaurar su estado. por   ejemplo, si la actividad B se inicia frente a la actividad A, y en algún momento   la actividad de punto A se elimina para recuperar recursos, la actividad A tendrá   una oportunidad para 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 del   La interfaz de usuario se puede restaurar a través de onCreate (Bundle) o    onRestoreInstanceState (Bundle) .

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

El artículo cubre tres enfoques:

Almacenar datos de control de UI / variables locales para la vida útil de la aplicación (es decir, temporalmente) utilizando un 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);
}

Almacenar datos de control de UI / variable local entre instancias de la aplicación (es decir, permanentemente) usando 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 instancias de objetos vivas en la memoria entre actividades dentro de la vida útil de la aplicación utilizando una instancia de no configuración 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. Aquí hay dos problemas:

  • Hay un sutil error de Android Framework que complica enormemente la administración de la pila de aplicaciones durante el desarrollo, al menos en versiones heredadas (no del todo seguro si / cuándo / cómo se solucionó). Discutiré este error a continuación.
  • La forma 'normal' o prevista de gestionar 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 hablan de estos dos problemas diferentes simultáneamente ... 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 la recreación de la 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 reinstalada 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 a un segundo plano. Sí, quizás la memoria de su Actividad sobrevivirá a su viaje a un segundo plano y escapará de GC, pero esto no es confiable (ni es predecible).

Entonces, si tiene un escenario donde 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/ cuestiones / detalles? id = 2373

http://code.google.com/p/android/ cuestiones / detalles? id = 5277

Básicamente, si su aplicación se inicia con el indicador SingleTask, y luego la inicia desde la pantalla de inicio o el menú del iniciador, esa invocación posterior creará una NUEVA tarea ... efectivamente tendrá dos instancias diferentes de tu aplicación que habita en la misma pila ... lo cual se vuelve muy extraño muy rápido. Esto parece suceder cuando inicia su aplicación durante el desarrollo (es decir, desde Eclipse o Intellij), por lo que los desarrolladores se encuentran mucho con esto. 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 problema principal era este error, no el comportamiento del marco previsto. Parece que el usuario @kaciula en esta respuesta parece una excelente escritura y solución alternativa (ACTUALIZACIÓN: ver más abajo):

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 ningún indicador de aplicación de inicio con estado, puede detectarlo desde el marco y rescatarlo 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 elimina una aplicación. No se llama cuando el usuario simplemente cierra la aplicación. Por lo tanto, creo que el estado de la aplicación también debe guardarse en onPause . Debe guardarse en un 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 de aplicación y luego regresa al original y quiere retomar donde lo dejó - guardar y restaurar los datos del paquete (como los datos del estado de la aplicación) en onSaveInstanceState () y onRestoreInstanceState () suele ser adecuado.

Si guarda los datos de estado de manera persistente, puede volver a cargarlos en un onResume () o onCreate () (o en realidad en cualquier llamada del 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' del usuario (uso el término sesión libremente) pero no entre ' sesiones '.

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

El estado de ahorro es un error en el mejor de los casos. Si necesita guardar datos persistentes, simplemente use una base de datos SQLite . Android lo hace SOOO 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 la actividad2 y quiero ver mi actividad2 en la misma condición la última vez que dejé la actividad2.

Para el escenario anterior, lo que he hecho es que en el manifiesto hice algunos cambios como este:

<activity android:name=".activity2"
          android:alwaysRetainTaskState="true"      
          android:launchMode="singleInstance">
</activity>

Y en la actividad1 en el evento de clic de botón, 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 la actividad2 en el evento de clic de botón, he hecho así:

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

Ahora, lo que sucederá es que, independientemente de los cambios que hayamos realizado en la actividad2, no se perderán, y podemos ver la actividad2 en el mismo estado en que nos fuimos anteriormente.

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

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

  ¡Android llama a

onSaveInstanceState () si se detiene la actividad y se puede eliminar antes de que se reanude! Esto significa que debe almacenar cualquier estado necesario para reinicializar a la misma condición cuando se reinicia la Actividad. Es la contraparte del método onCreate () y, de hecho, el paquete savedInstanceState pasado a onCreate () es el mismo paquete que construyes como outState en el método onSaveInstanceState ().

     

onPause () y onResume () también son métodos complementarios. onPause () siempre se llama cuando finaliza la actividad, incluso si instigamos eso (con una llamada de acabado (), por ejemplo). Usaremos esto para guardar la nota actual en la base de datos. Una buena práctica es liberar cualquier recurso que pueda liberarse durante un onPause () también, para tomar menos recursos cuando está en estado pasivo.

Realmente onSaveInstance estado callen cuando la actividad pasa a segundo plano

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

Para ayudar a reducir las repeticiones utilizo la siguiente interfaz y class para leer / escribir en un Bundle para guardar el estado de la instancia.


Primero, cree una interfaz que se utilizará 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 el < a href = "https://raw.githubusercontent.com/CardinalNow/AndroidAutowire/master/LICENSE"> licencia MIT .

Mientras tanto, en general 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 afirma a sí mismo, ni siquiera es confiable.

Mi manera es guardar los cambios de inmediato en las preferencias:

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

De alguna manera, SharedPreferences funciona de manera similar a los Bundles. Y, naturalmente, al principio, dichos valores deben leerse desde las preferencias.

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

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

Para responder 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 de estado cuando:

  • Cambios de configuración, como cambiar la orientación o el idioma del teléfono, lo que puede requerir que se cree una nueva instancia de actividad.
  • Vuelve a la aplicación desde el fondo una vez que el sistema operativo ha 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 su ejemplo de hello world, hay algunas maneras de salir y volver a la Actividad.

  • Cuando presiona el botón Atrás, la actividad finaliza. Relanzar la aplicación es una nueva instancia. No está reanudando desde el fondo en absoluto.
  • Cuando presione el botón de inicio o use el selector de tareas, la Actividad pasará a segundo plano. Al navegar de regreso a la aplicación, solo se llamará a onCreate si la Actividad tuvo que ser destruida.

En la mayoría de los casos, si solo 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 llamadas "No mantener actividades". Cuando está habilitado, Android siempre destruirá actividades y las recreará cuando estén en segundo plano. Esta es una gran opción para dejar habilitada cuando se desarrolla porque simula el peor de los casos. (Un dispositivo con poca memoria que recicla sus actividades todo el tiempo).

Las otras respuestas son valiosas porque te enseñan las formas correctas de almacenar el estado, pero no sentí que realmente respondieran POR QUÉ tu código no funcionaba de la manera que esperabas.

Los métodos onSaveInstanceState (bundle) y onRestoreInstanceState (bundle) son útiles para la persistencia de datos simplemente al girar la pantalla (cambio de orientación).
Ni siquiera son buenos al cambiar entre aplicaciones (ya que se llama al método onSaveInstanceState () pero onCreate (bundle) y onRestoreInstanceState (bundle) es no invocado nuevamente.
Para mayor persistencia, use las preferencias compartidas. lea este artículo

Mi problema era que necesitaba persistencia solo durante la vida útil de la aplicación (es decir, una sola ejecución que incluye iniciar otras sub-actividades dentro de la misma aplicación y rotar el dispositivo, etc.). Intenté varias combinaciones de las respuestas anteriores, pero no obtuve lo que quería en todas las situaciones. Al final, lo que funcionó para mí fue obtener una referencia al salvadoInstanceState durante onCreate:

mySavedInstanceState=savedInstanceState;

y úselo para obtener el contenido de mi variable cuando lo necesite, en la línea de:

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

Uso onSaveInstanceState y onRestoreInstanceState como se sugirió anteriormente, pero supongo que también podría usar mi método para guardar la variable cuando cambie (por ejemplo, usando putBoolean )

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 usando una biblioteca llamada Icepick . Icepick es un procesador de anotaciones que se encarga de todo el código repetitivo utilizado para guardar y restaurar el estado.

Hacer algo como esto 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 funcionará con cualquier objeto que guarde su estado con un Bundle .

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 vuelve a crear. 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 " outState " Agrupe objetos como outState.putString (" key ", " Welcome Back ") y guarde llamando a super. Cuando se destruye la actividad, su estado se guarda 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");
    }

Básicamente, hay dos formas de implementar este cambio.

  1. usando onSaveInstanceState () y onRestoreInstanceState () .
  2. En manifiesto android: configChanges = " oriente | screenSize " .

Realmente no recomiendo usar el segundo método. Dado que, según mi experiencia, estaba causando que la mitad de la pantalla del dispositivo se oscureciera al girar de vertical a horizontal y viceversa.

Usando el primer método mencionado anteriormente, podemos conservar los datos cuando se cambia la orientación o se produce cualquier cambio de configuración. Sé una forma en que puede almacenar cualquier tipo de datos dentro del objeto de estado salvadoInstance.

Ejemplo: considere un caso si desea persistir el objeto Json. crear una clase de modelo con captadores y establecedores.

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

Ahora, en su actividad en los métodos onCreate y onSaveInstanceState, haga lo siguiente. Se verá más o menos así:

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

}

Aquí hay un comentario de la respuesta de Steve Moseley (por ToolmakerSteve ) que pone las cosas en perspectiva (en toda la saga onSaveInstanceState vs onPause, coste este frente a coste oeste) )

  

@VVK - Estoy parcialmente en desacuerdo. Algunas formas de salir de una aplicación no se activan   onSaveInstanceState (oSIS). Esto limita la utilidad de oSIS. Sus   vale la pena apoyar, para recursos mínimos del sistema operativo, pero si una aplicación quiere   devolver al usuario al estado en el que se encontraba, sin importar cómo estaba la aplicación   salido, es necesario utilizar un enfoque de almacenamiento persistente en su lugar.    Uso onCreate para verificar si hay un paquete, y si falta, verifique    almacenamiento persistente. Esto centraliza la toma de decisiones. puedo   recuperarse de un bloqueo, o salir del botón Atrás o Salir del elemento de menú personalizado, o   volver a la pantalla el usuario estuvo encendido muchos días después. & # 8211; ToolmakerSteve Sep   19 '15 a las 10:38

Código Kotlin:

guardar:

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

y luego en onCreate () o onRestoreInstanceState ()

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

Agregue valores predeterminados si no desea tener Opcionales

Para obtener datos de estado de actividad almacenados en onCreate () , primero debe guardar los datos en salvadoInstanceState anulando el método SaveInstanceState (Bundle savedInstanceState) .

Cuando la actividad destruye el método SaveInstanceState (Bundle savedInstanceState) y allí guarda los datos que desea guardar. Y obtiene lo mismo en onCreate () cuando se reinicia la actividad. (SavedInstanceState no será nulo ya que ha guardado algunos datos antes de que la actividad se destruya)

Simple y rápido para resolver este problema es usar IcePick

Primero, configure la biblioteca en app/build.gradle

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

Ahora, revisemos este ejemplo a continuación sobre cómo guardar el estado en la Actividad

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

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

Funciona para Actividades, Fragmentos o cualquier objeto que necesite serializar su estado en un Paquete (por ejemplo, ViewPresenters de mortero)

Icepick también puede generar el código de estado de instancia para Vistas personalizadas:

class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(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/restored
}

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 lo recupera de una base de datos SQLite depende de sus requisitos. Esto es lo que hacen los servicios de cualquier tipo, proporcionan servicios tales como el mantenimiento del estado de la aplicación y la lógica empresarial común abstracta.

Debido a las limitaciones de memoria y procesamiento inherentes a los dispositivos móviles, trato las vistas de Android de forma 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 de Modelo, Vista, Controlador (MVC), donde la página es la Vista, los datos de dominio son el modelo y el controlador se encuentra detrás de un servicio web. El mismo patrón puede emplearse en Android con la Vista, bueno ... la Vista, el modelo son sus datos de dominio, y el Controlador se implementa como un servicio vinculado a Android. Siempre que desee que una vista interactúe con el controlador, vincúlela al iniciar / reanudar y desvincula al detener / pausar.

Este enfoque le brinda la ventaja adicional de hacer cumplir el principio de diseño de Separación de preocupaciones en el sentido de que toda la lógica empresarial de su aplicación se puede trasladar a su servicio, lo que reduce la lógica duplicada en varias vistas y permite que la vista imponga otro principio de diseño importante, Responsabilidad única.

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

¿Qué guardar y qué no?

¿Alguna vez se preguntó por qué el texto en EditText se guarda automáticamente mientras se cambia la orientación? Bueno, esta respuesta es para ti.

Cuando una instancia de una Actividad se destruye y el Sistema recrea una nueva instancia (por ejemplo, cambio de configuración). Intenta recrearlo utilizando un conjunto de datos guardados del estado de actividad anterior ( estado de instancia ).

El estado de instancia es una colección de pares clave-valor almacenados en un objeto Bundle .

  

Por defecto, el Sistema guarda los objetos Ver en el Paquete, por ejemplo.

  • Texto en EditText
  • Posición de desplazamiento en un ListView , etc.

Si necesita guardar otra variable como parte del estado de instancia, debe OVERRIDE onSavedInstanceState (Bundle savedinstaneState) .

Por ejemplo, int currentScore en una GameActivity

Más detalles sobre onSavedInstanceState (Bundle savedinstaneState) al guardar datos

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}
  

Entonces, por error, si olvida llamar    super.onSaveInstanceState (savedInstanceState); el comportamiento predeterminado   no funcionará, es decir, el texto en EditText no se guardará.

¿Cuál elegir para restaurar el estado de la actividad?

 onCreate(Bundle savedInstanceState)

OR

onRestoreInstanceState(Bundle savedInstanceState)

Ambos métodos obtienen el mismo objeto Bundle, por lo que realmente no importa dónde escriba su lógica de restauración. La única diferencia es que en el método onCreate (Bundle savedInstanceState) deberá realizar una comprobación nula mientras no sea necesario en este último caso. Otras respuestas ya tienen fragmentos de código. Puedes referirlos.

Más detalles sobre onRestoreInstanceState (Bundle savedinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}
  

Siempre llame a super.onRestoreInstanceState (savedInstanceState); para que el sistema restaure la jerarquía de vistas de forma predeterminada

Bonus

El onSaveInstanceState (Bundle savedInstanceState) es invocado por el sistema solo cuando el usuario tiene la intención de volver a la Actividad. Por ejemplo, está utilizando la aplicación X y de repente recibe una llamada. Se mueve a la aplicación de llamada y vuelve a la aplicación X. En este caso, se invocará el método onSaveInstanceState (Bundle savedInstanceState) .

Pero considere esto si un usuario presiona el botón Atrás. Se supone que el usuario no tiene la intención de volver a la Actividad, por lo tanto, en este caso, el sistema no invocará onSaveInstanceState (Bundle savedInstanceState) . Señale que debe considerar todos los escenarios al guardar los datos.

Enlaces relevantes:

Demostración sobre comportamiento predeterminado
Documentación oficial de Android .

Kotlin

Debe anular onSaveInstanceState y onRestoreInstanceState para almacenar y recuperar las variables que desea que sean persistentes

Gráfico del ciclo de vida

Almacenar variables

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

Recuperar variables

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}

Tengo una mejor idea. Sería mejor guardar sus datos sin volver a llamar a Create. Puede deshabilitarlo de la actividad cuando la orientación está cambiando.

En tu manifiesto:

<activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize">

Editar: Esta solución no funcionará cuando Android mate el proceso debido a la poca memoria. si necesita mantener datos con seguridad, debe usar SavedInstanceState en este caso; de lo contrario, sugiero usarlo de esta manera. genial y fácil de usar!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top