Pregunta 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.


2234
2017-09-30 04:41


origen


Respuestas:


Tienes que anular onSaveInstanceState(Bundle savedInstanceState) y escriba los valores de estado de la aplicación que desea cambiar a Bundle parámetro 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.
}

El paquete es esencialmente una forma de almacenar un mapa NVP ("Name-Value Pair"), y se pasará a onCreate() y también onRestoreInstanceState() donde 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");
}

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


2271
2017-09-30 06:12



los savedInstanceState es solo 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. Ver la documentación para onCreate y onSaveInstanceState

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


375
2017-09-30 05:03



Tenga en cuenta que es NO seguro de usar onSaveInstanceState y onRestoreInstanceState  para datos persistentes, según 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() en lugar   de onSaveInstanceState(Bundle)   porque el último no es parte del   las devoluciones de llamada del ciclo de vida, entonces no serán   llamado en cada situación 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á el onSaveInstanceState() documentación:

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


365
2018-05-25 23:22



Mi colega escribió un artículo que explica el estado de la aplicación en dispositivos con 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 el guardado al estado. Bundle y SharedPreferences y mira 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();
}

171
2017-08-27 13:54



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 pila 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 pila ... 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 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;
    }
}

126
2017-10-19 23:47



onSaveInstanceStatese invoca 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 un almacenamiento persistente como Preferences o Sqlite


70
2018-05-07 00:21



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ó: guarde y restaure los datos del paquete (como los datos de estado de la aplicación) en onSaveInstanceState() y onRestoreInstanceState() por lo general es adecuado.

Si guarda los datos de estado de forma 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.


59
2018-06-27 16:17



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 un SQLite base de datos. 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;

50
2018-06-23 17:07



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:

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

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.


50
2018-02-05 11:35



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

onSaveInstanceState () Android lo llama 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 En resumen() 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.


36
2018-01-17 18:28



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 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 tu activity se destruye porque el usuario presiona Volver o el activity se termina, el concepto del sistema de eso Activity instancia 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 era 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 devolución de llamada 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 ambos onRestoreInstanceState() y onCreate() métodos. enter image description here

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

Guarde su Activity Estado

A medida que su actividad comienza a detenerse, el sistema llama 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 una EditText widget o la posición de desplazamiento de un ListView.

Para guardar información de estado adicional para su actividad, debe implementar onSaveInstanceState() y agrega 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 la superclase de onSaveInstanceState()por lo que la implementación predeterminada puede guardar el estado de la jerarquía de vistas.

Restaura tu Activity Estado

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. Ambos onCreate() y onRestoreInstanceState() los métodos de devolución de llamada reciben el mismo Bundle que contiene la información de estado de la instancia.

Porque el onCreate() El método se llama si 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, así es 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() puedes elegir implementar onRestoreInstanceState(), que el sistema llama después del onStart() método. El sistema llama 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 restore the view hierarchy
  super.onRestoreInstanceState(savedInstanceState);

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

36
2018-04-15 04:13