web-dev-qa-db-fra.com

Comment enregistrer un état d'activité Android à l'aide de l'état d'instance de sauvegarde?

Je travaille sur la plate-forme Android SDK, et il est difficile de savoir comment enregistrer l'état d'une application. Donc, étant donné cette réorganisation mineure de l'exemple 'Hello, 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);
  }
}

Je pensais que cela suffirait dans le cas le plus simple, mais il répond toujours avec le premier message, peu importe la façon dont je navigue loin de l'application.

Je suis sûr que la solution est aussi simple que de remplacer onPause ou quelque chose du genre, mais cela fait 30 minutes que je fouille dans la documentation et je n'ai rien trouvé d'évident.

2359
Bernard

Vous devez remplacer onSaveInstanceState(Bundle savedInstanceState) et écrire les valeurs d'état de l'application que vous souhaitez modifier comme paramètre Bundle:

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

Le Bundle est essentiellement un moyen de stocker une carte NVP ("paire paire nom-valeur"). Elle sera transmise à onCreate() et à onRestoreInstanceState() où vous pourrez extraire les valeurs suivantes:

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

Vous utiliseriez généralement cette technique pour stocker des valeurs d'instance pour votre application (sélections, texte non enregistré, etc.).

2377
Reto Meier

La savedInstanceState sert uniquement à enregistrer l'état associé à une instance actuelle d'une activité, par exemple les informations de navigation ou de sélection actuelles. Ainsi, si Android détruit et recrée une activité, elle peut revenir telle qu'elle était auparavant. Voir la documentation de onCreate et onSaveInstanceState

Pour un état plus long, envisagez d'utiliser une base de données SQLite, un fichier ou des préférences. Voir Sauvegarde d'un état persistant .

396
Dave L.

Notez qu'il est PAS sûr d'utiliser onSaveInstanceState et onRestoreInstanceStatepour les données persistantes, selon la documentation sur les états d'activité dans http://developer.Android.com /reference/Android/app/Activity.html .

Le document indique (dans la section 'Cycle de vie de l'activité'):

Notez qu'il est important de sauvegarder données persistantes dans onPause() à la place de onSaveInstanceState(Bundle) parce que ce dernier ne fait pas partie de la callbacks de cycle de vie, donc ne sera pas appelé dans chaque situation comme décrit dans sa documentation.

En d'autres termes, placez votre code de sauvegarde/restauration pour les données persistantes dans onPause() et onResume()!

EDIT: Pour plus de précisions, voici la documentation de onSaveInstanceState():

Cette méthode est appelée avant qu'une activité puisse être tuée de sorte que quand. revient dans le futur, il peut restaurer son état. Pour Par exemple, si l’activité B est lancée devant l’activité A et à un moment donné l'activité ponctuelle A est tuée pour récupérer les ressources, l'activité A aura. une chance de sauvegarder l’état actuel de son interface utilisateur via ceci méthode de sorte que lorsque l'utilisateur retourne à l'activité A, l'état du fichier L’interface utilisateur peut être restaurée via onCreate(Bundle) ou onRestoreInstanceState(Bundle).

383
Steve Moseley

Mon collègue a écrit un article expliquant l'état des applications sur les appareils Android, y compris des explications sur le cycle de vie d'une activité et des informations d'état, comment stocker des informations d'état et enregistrer les états Bundle et SharedPreferences et à regarder ici .

L'article couvre trois approches:

Stocker les données de contrôle de variable/interface utilisateur locale pendant la durée de vie de l'application (c'est-à-dire temporairement) à l'aide d'un ensemble d'états d'instance

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

Stocker des données de contrôle de variable/interface utilisateur locale entre des instances d’application (c’est-à-dire de manière permanente) à l’aide de préférences partagées

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

Conserver des instances d'objet actives en mémoire entre les activités de la durée de vie de l'application à l'aide d'une instance non configurée conservée

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

Ceci est un «gotcha» classique du développement Android. Il y a deux problèmes ici:

  • Il existe un bogue subtil dans Android Framework qui complique grandement la gestion de la pile d'applications pendant le développement, du moins sur les versions héritées (je ne suis pas tout à fait sûr si/quand/comment il a été corrigé). Je vais discuter de ce bug ci-dessous.
  • Le moyen "normal" ou prévu de gérer ce problème est, en soi, assez compliqué avec la dualité onPause/onResume et onSaveInstanceState/onRestoreInstanceState

En parcourant toutes ces discussions, je soupçonne que la plupart du temps, les développeurs parlent simultanément de ces deux problèmes différents ... d’où toute la confusion et les rapports selon lesquels "cela ne fonctionne pas pour moi".

Tout d'abord, pour clarifier le comportement «prévu»: onSaveInstance et onRestoreInstance sont fragiles et uniquement pour un état transitoire. L’utilisation prévue (afaict) consiste à gérer la création d’activités lors de la rotation du téléphone (changement d’orientation). En d’autres termes, l’utilisation prévue est celle où votre activité est toujours logiquement «au-dessus», mais doit quand même être réinitialisée par le système. Le paquet sauvegardé n'est pas conservé en dehors du processus/mémoire/gc, vous ne pouvez donc pas vraiment vous fier à cela si votre activité passe en arrière-plan. Oui, peut-être que la mémoire de votre activité survivra à son voyage en arrière-plan et échappera au GC, mais cela n’est pas fiable (ni prévisible).

Ainsi, si vous avez un scénario où il existe une «progression de l'utilisateur» significative ou un état qui devrait être conservé entre les «lancements» de votre application, il est conseillé d'utiliser onPause et onResume. Vous devez choisir et préparer vous-même un magasin persistant.

MAIS - il y a un bug très déroutant qui complique tout cela. Les détails sont ici:

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

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

Fondamentalement, si votre application est lancée avec l'indicateur SingleTask et que vous la lancez ensuite à partir de l'écran d'accueil ou du menu du lanceur, cette invocation ultérieure créera une NOUVELLE tâche ... vous aurez effectivement deux instances différentes de votre application. habitant la même pile ... ce qui devient très étrange très vite. Cela semble se produire lorsque vous lancez votre application au cours du développement (c’est-à-dire à partir d’Eclipse ou d’Intellij). Les développeurs doivent donc souvent faire face à cette situation. Mais aussi par le biais de certains mécanismes de mise à jour de l'App Store (cela impacte également vos utilisateurs).

Je me suis battu dans ces discussions pendant des heures avant de comprendre que mon problème principal était ce bogue, et non le comportement du framework souhaité. Un excellent article et workaround (UPDATE: voir ci-dessous) semble provenir de l'utilisateur @kaciula dans cette réponse:

Comportement de la touche Accueil

UPDATE Juin 2013: Quelques mois plus tard, j'ai finalement trouvé la solution "correcte". Vous n'avez pas besoin de gérer vous-même les drapeaux startedApp stateful, vous pouvez le détecter à partir du framework et le libérer correctement. J'utilise ceci près du début de mon 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;
    }
}
132
Mike Repass

onSaveInstanceState est appelé lorsque le système a besoin de mémoire et tue une application. Il n'est pas appelé lorsque l'utilisateur ferme simplement l'application. Je pense donc que l'état de l'application doit également être enregistré dans onPause Il doit être enregistré dans un stockage persistant tel que Preferences ou Sqlite

73
Fedor

Les deux méthodes sont utiles et valides et conviennent parfaitement à différents scénarios:

  1. L'utilisateur met fin à l'application et la rouvre ultérieurement, mais elle doit recharger les données de la dernière session. Cela nécessite une approche de stockage persistant, telle que l'utilisation de SQLite.
  2. L'utilisateur change d'application, puis revient à l'original et souhaite reprendre là où il l'a laissé - la sauvegarde et la restauration des données d'ensemble (telles que les données d'état de l'application) dans onSaveInstanceState() et onRestoreInstanceState() sont généralement suffisantes.

Si vous enregistrez les données d'état de manière persistante, elles peuvent être rechargées dans un onResume() ou un onCreate() (ou lors d'un appel de cycle de vie). Ce comportement peut ou peut ne pas être souhaité. Si vous la stockez dans une liasse dans une InstanceState, elle est transitoire et ne convient que pour stocker des données à utiliser dans la même «session» d’utilisateur (j’utilise le terme session de manière vague) mais pas entre les «sessions».

Ce n’est pas qu’une approche soit meilleure que l’autre, comme tout, il est simplement important de comprendre le comportement que vous désirez et de choisir l’approche la plus appropriée.

62
David

Sauver l'état est au mieux un bécasse en ce qui me concerne. Si vous avez besoin de sauvegarder des données persistantes, utilisez simplement une base de données SQLite . Android rend SOOO facile.

Quelque chose comme ça:

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) {
        }
    }
}

Un simple appel après ça

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

Je pense avoir trouvé la réponse. Permettez-moi de dire ce que j'ai fait avec des mots simples:

Supposons que j’ai deux activités, activité1 et activité2 et que je navigue d’activité1 à activité2 (j’ai effectué quelques travaux dans activité2) puis que je retourne à l’activité 1 en cliquant sur un bouton de l’activité1. Maintenant, à ce stade, je voulais revenir à activity2 et je veux voir mon activité2 dans le même état que la dernière fois que j'ai quitté activity2.

Pour le scénario ci-dessus, ce que j'ai fait est que, dans le manifeste, j'ai apporté des modifications comme celle-ci:

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

Et dans le activity1 sur l'événement de clic de bouton, j'ai fait comme ça:

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

Et dans activity2 sur l'événement de clic de bouton, je l'ai fait comme ceci:

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

Maintenant, ce qui va arriver, c’est que, quels que soient les changements que nous avons apportés à l’activité2, ils ne seront pas perdus et que nous pourrons voir l’activité2 dans le même état que nous avons quitté précédemment.

Je crois que c'est la réponse et que cela fonctionne bien pour moi. Corrigez-moi si je me trompe.

51
roy mathew

onSaveInstanceState() pour les données transitoires (restauré dans onCreate()/onRestoreInstanceState()), onPause() pour les données persistantes (restauré dans onResume()). À partir des ressources techniques Android:

onSaveInstanceState () est appelé par Android si l'activité est en cours d'arrêt et peut être éliminée avant d'être reprise! Cela signifie qu'il devrait stocker tout état nécessaire pour se réinitialiser dans la même condition lorsque l'activité est redémarrée. Il s'agit de la contrepartie de la méthode onCreate () et le bundle savedInstanceState transmis à onCreate () est le même Bundle que vous construisez comme outState dans la méthode onSaveInstanceState ().

onPause () et onResume () sont également des méthodes complémentaires. onPause () est toujours appelé à la fin de l'activité, même si nous l'initiions (avec un appel finish () par exemple). Nous allons l'utiliser pour enregistrer la note actuelle dans la base de données. Une bonne pratique consiste à libérer toutes les ressources pouvant être libérées au cours d'une opération onPause (), afin de consommer moins de ressources dans l'état passif.

37
Ixx

Vraiment onSaveInstance état appelant lorsque l'activité passe à l'arrière-plan

Citation tirée de la documentation: "La méthode onSaveInstanceState(Bundle) est appelée avant de placer l'activité dans un tel état d'arrière-plan"

34
u-foka

Pour aider à réduire les problèmes, j'utilise les interface et class suivants pour lire/écrire dans un Bundle afin de sauvegarder l'état de l'instance.


Tout d’abord, créez une interface qui sera utilisée pour annoter vos variables d’instance:

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 {

}

Ensuite, créez une classe où la réflexion sera utilisée pour enregistrer des valeurs dans le bundle:

import Android.app.Activity;
import Android.app.Fragment;
import Android.os.Bundle;
import Android.os.Parcelable;
import Android.util.Log;

import Java.io.Serializable;
import Java.lang.reflect.Field;

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</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();
        }
    }

}

Exemple d'utilisation:

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

}

Remarque: Ce code a été adapté à partir d'un projet de bibliothèque nommé AndroidAutowire , sous licence MIT .

29
Jared Rummler

En attendant, en général, je n’utilise plus

Bundle savedInstanceState & Co

Le cycle de vie est trop compliqué et inutile pour la plupart des activités.

Et Google se dit, ce n'est même pas fiable.

Mon moyen est d’enregistrer immédiatement tout changement dans les préférences:

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

D'une certaine manière, les préférences partagées fonctionnent de la même manière que Bundles ..__ Et, naturellement et au début, de telles valeurs doivent être lues à partir des préférences.

Dans le cas de données complexes, vous pouvez utiliser SQLite au lieu d'utiliser des préférences.

Lorsque vous appliquez ce concept, l'activité continue simplement d'utiliser le dernier état enregistré, qu'il s'agisse d'une ouverture initiale avec redémarrage intermédiaire ou d'une réouverture en raison de la pile arrière.

29
stefan bachert

Pour répondre directement à la question initiale. savedInstancestate est null car votre activité n'est jamais recréée.

Votre activité ne sera recréée avec un lot d’états que:

  • Les modifications de configuration, telles que la modification de l'orientation ou de la langue du téléphone, pouvant nécessiter la création d'une nouvelle instance d'activité.
  • Vous revenez à l'application à partir de l'arrière-plan après que le système d'exploitation a détruit l'activité. 

Android détruira les activités en arrière-plan en cas de pression de la mémoire ou après une longue période en arrière-plan.

Lorsque vous testez votre exemple hello world, il existe plusieurs façons de quitter et de revenir à l'activité.

  • Lorsque vous appuyez sur le bouton de retour, l'activité est terminée. La relance de l'application est une toute nouvelle instance. Vous ne reprenez pas du fond du tout.
  • Lorsque vous appuyez sur le bouton d'accueil ou utilisez le sélecteur de tâches, l'activité passe à l'arrière-plan. Lors de la navigation vers l'application, onCreate ne sera appelé que si l'activité doit être détruite. 

Dans la plupart des cas, si vous appuyez simplement sur la touche d'accueil puis relancez l'application, l'activité n'aura pas besoin d'être recréée. Il existe déjà en mémoire, donc onCreate () ne sera pas appelé.

Il y a une option sous Paramètres -> Options du développeur appelée "Ne pas garder les activités". Lorsqu'il est activé, Android détruira toujours les activités et les recréera lorsqu'elles seront en arrière-plan. C’est une excellente option à laisser activée lors du développement car elle simule le pire des scénarios. (Un périphérique à faible mémoire recyclant vos activités tout le temps).

Les autres réponses sont précieuses car elles vous apprennent les bonnes manières de stocker l'état, mais je ne pensais pas qu'elles répondaient vraiment POURQUOI votre code ne fonctionnait pas comme prévu.

28
Jared Kells

Les méthodes onSaveInstanceState(bundle) et onRestoreInstanceState(bundle) sont utiles pour la persistance des données simplement en faisant pivoter l'écran (changement d'orientation).
Ils ne sont même pas bons lors du basculement entre applications (puisque la méthode onSaveInstanceState() est appelée mais que onCreate(bundle) et onRestoreInstanceState(bundle) ne sont plus invoqués.
Pour plus de persistance, utilisez les préférences partagées. lire cet article

24
Mahorad

Mon problème était que je n'avais besoin de persistance que pendant la durée de vie de l'application (c'est-à-dire une exécution unique comprenant le démarrage d'autres sous-activités au sein de la même application et la rotation de l'appareil, etc.). J'ai essayé diverses combinaisons des réponses ci-dessus mais je n'ai pas obtenu ce que je voulais dans toutes les situations. En fin de compte, ce qui a fonctionné pour moi a été d'obtenir une référence à savedInstanceState lors de onCreate:

mySavedInstanceState=savedInstanceState;

et l'utiliser pour obtenir le contenu de ma variable quand j'en avais besoin, comme suit:

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

J'utilise onSaveInstanceStateet onRestoreInstanceState comme suggéré ci-dessus, mais je suppose que je pourrais aussi ou alternativement utiliser ma méthode pour enregistrer la variable lorsqu'elle change (par exemple, en utilisant putBoolean)

16
torwalker

Bien que la réponse acceptée soit correcte, il existe une méthode plus rapide et plus simple pour enregistrer l’état de l’activité sur Android à l’aide d’une bibliothèque appelée Icepick . Icepick est un processeur d'annotation qui prend en charge tout le code standard utilisé pour la sauvegarde et la restauration de l'état pour vous. 

Faire quelque chose comme ça avec 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);
  }
}

Est-ce la même chose que faire ceci:

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 fonctionnera avec tout objet qui enregistre son état avec un Bundle.

15
Kevin Cronly

Quand une activité est créée, sa méthode onCreate () est appelée.

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

savedInstanceState est un objet de la classe Bundle qui est null pour la première fois, mais il contient des valeurs lorsqu’il est recréé. Pour enregistrer l'état de l'activité, vous devez remplacer onSaveInstanceState ().

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

mettez vos valeurs dans un objet "outState" Bundle, comme outState.putString ("clé", "Welcome Back") et sauvegardez en appelant super . Lorsque l'activité sera détruite, son état sera enregistré dans l'objet Bundle et pourra être restauré après la récréation dans onCreate () ou onRestoreInstanceState (). Les lots reçus dans onCreate () et onRestoreInstanceState () sont identiques.

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

ou

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

Il existe fondamentalement deux manières de mettre en œuvre ce changement.

  1. en utilisant onSaveInstanceState() et onRestoreInstanceState().
  2. Dans le manifeste Android:configChanges="orientation|screenSize".

Je ne recommande vraiment pas d'utiliser la deuxième méthode. D'après l'une de mes expériences, la moitié de l'écran de l'appareil était noir lors du basculement d'un portrait à l'autre et inversement. 

En utilisant la première méthode mentionnée ci-dessus, nous pouvons conserver les données lorsque l'orientation est modifiée ou en cas de modification de la configuration ..___ Je connais un moyen de stocker tout type de données dans l'objet state savedInstance.

Exemple: si vous souhaitez conserver un objet Json, créez une classe de modèle avec des getters et des setters.

class MyModel extends Serializable{
JSONObject obj;

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

JSONObject getJsonObject()
return this.obj;
} 
}

Maintenant, dans votre activité dans les méthodes onCreate et onSaveInstanceState, procédez comme suit. Cela ressemblera à quelque chose comme ça:

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

}
12
Krishna

Voici un commentaire de la réponse de Steve Moseley (par ToolmakerSteve) qui met les choses en perspective (dans l'ensemble onSaveInstanceState vs onPause, saga East Cost vs West Cost

@VVK - Je suis partiellement en désaccord. Certaines manières de quitter une application ne se déclenchent pas onSaveInstanceState (oSIS). Cela limite l'utilité de oSIS. Ses Cela vaut la peine d’être soutenu, pour des ressources minimales du système d’exploitation, mais si une application le souhaite ramène l'utilisateur à l'état dans lequel il se trouvait, quelle que soit l'application à la place, il est nécessaire d’utiliser une approche de stockage persistant à la place . J'utilise onCreate pour rechercher un ensemble. S'il est manquant, cochezstockage persistant. Cela centralise la prise de décision. Je peux récupérer après un plantage, ou quitter le bouton Précédent ou l’élément de menu personnalisé Quitter, ou revenir à l'écran utilisateur était sur plusieurs jours plus tard. - ToolmakerSteve Sep 19 '15 à 10h38

8
samis

Code Kotlin:

enregistrer:

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

puis dans onCreate() ou 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

Ajouter des valeurs par défaut si vous ne voulez pas avoir d'options

7
Rafols

Simple rapide pour résoudre ce problème utilise IcePick

Commencez par configurer la bibliothèque dans app/build.gradle

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

Maintenant, regardons cet exemple ci-dessous comment enregistrer l’état dans Activity

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

Cela fonctionne pour les activités, les fragments ou tout objet devant sérialiser son état sur un paquet (par exemple, ViewPresenters de mortar)

Icepick peut également générer le code d'état d'instance pour des vues personnalisées:

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
}
5
THANN Phearum

Pas sûr que ma solution soit mal vue ou non, mais j'utilise un service lié pour conserver l'état ViewModel. Que vous le stockiez en mémoire dans le service ou persistiez et récupériez-le à partir d'une base de données SQLite, cela dépend de vos besoins. C’est ce que font les services de toutes les saveurs, ils fournissent des services tels que la gestion de l’état des applications et la logique métier commune abstraite. 

En raison des contraintes de mémoire et de traitement inhérentes aux appareils mobiles, je traite les vues Android de la même manière qu'une page Web. La page ne conserve pas l'état, il s'agit uniquement d'un composant de la couche de présentation dont le seul but est de présenter l'état de l'application et d'accepter les entrées de l'utilisateur. Les tendances récentes en matière d’architecture d’applications Web utilisent le modèle très ancien Modèle, Vue, Contrôleur (MVC), dans lequel la page correspond à la Vue, les données de domaine au modèle, et le contrôleur se cache derrière un service Web. Le même modèle peut être utilisé dans Android, la vue étant, ainsi ... la vue, le modèle correspond aux données de votre domaine et le contrôleur est implémenté en tant que service lié à Android. Chaque fois que vous souhaitez qu'une vue interagisse avec le contrôleur, liez-le au démarrage/reprise et dissociez-le à l'arrêt/la pause.

Cette approche vous donne l’avantage supplémentaire d’appliquer le principe de conception Séparation de préoccupations en ce sens que toute votre logique métier d’application peut être déplacée vers votre service, ce qui réduit la duplication de plusieurs vues et permet à la vue d’appliquer un autre principe de conception important, la responsabilité unique.

5
ComeIn

Pour que les données sur l’état de l’activité soient stockées dans onCreate(), vous devez d’abord enregistrer les données dans savedInstanceState en remplaçant la méthode SaveInstanceState(Bundle savedInstanceState).

Lorsque la méthode activity destroy SaveInstanceState(Bundle savedInstanceState) est appelée et que vous enregistrez les données que vous souhaitez sauvegarder. Et vous obtenez la même chose dans onCreate() lorsque l'activité redémarre. (SavedInstanceState ne sera pas nul, car vous avez sauvegardé certaines données avant que l'activité ne soit détruite)

5
ascii_walker

Maintenant, Android fournit ViewModels pour sauvegarder l’état, vous devriez essayer de l’utiliser à la place de saveInstanceState.

2
M Abdul Sami

Que sauver et que ne pas faire?

Vous êtes-vous déjà demandé pourquoi le texte de la EditText est enregistré automatiquement pendant le changement d'orientation? Eh bien, cette réponse est pour vous.

Lorsqu'une instance d'une activité est détruite et que le système recrée une nouvelle instance (par exemple, modification de la configuration). Il essaie de le recréer en utilisant un ensemble de données sauvegardées de l'ancien état d'activité (instance state).

Instance state est une collection de paires key-value stockées dans un objet Bundle.

Par défaut, le système enregistre les objets View du lot par exemple.

  • Texte en EditText
  • Position de défilement dans une ListView, etc.

Si vous avez besoin qu'une autre variable soit sauvegardée en tant qu'élément de l'état de l'instance, vous devez REMPLACER LA M&EACUTE;THODEonSavedInstanceState(Bundle savedinstaneState).

Par exemple, int currentScore dans une GameActivity

Plus de détails sur onSavedInstanceState (Bundle savedinstaneState) lors de la sauvegarde des données

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

Donc par erreur si vous oubliez d'appeler super.onSaveInstanceState(savedInstanceState);le comportement par défaut ne fonctionnera pas, c'est-à-dire que le texte dans EditText ne sera pas sauvegardé.

Lequel choisir pour restaurer l'état d'activité?

 onCreate(Bundle savedInstanceState)

OU

onRestoreInstanceState(Bundle savedInstanceState)

Les deux méthodes obtiennent le même objet Bundle. Par conséquent, l'endroit où vous écrivez votre logique de restauration n'a pas vraiment d'importance. La seule différence est que, dans la méthode onCreate(Bundle savedInstanceState), vous devrez effectuer une vérification nulle alors que cela n'est pas nécessaire dans ce dernier cas. D'autres réponses ont déjà des extraits de code. Vous pouvez les référer.

Plus de détails sur 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);
}

Appelez toujours super.onRestoreInstanceState(savedInstanceState); pour que le système restaure la hiérarchie des vues par défaut

Prime

onSaveInstanceState(Bundle savedInstanceState) est appelé par le système uniquement lorsque l'utilisateur a l'intention de revenir à l'activité. Par exemple, vous utilisez App X et vous recevez un appel. Vous passez à l'application appelante et revenez à l'application X. Dans ce cas, la méthode onSaveInstanceState(Bundle savedInstanceState) sera invoquée.

Mais considérez ceci si un utilisateur appuie sur le bouton retour. Il est supposé que l'utilisateur n'a pas l'intention de revenir à l'activité, c'est pourquoi, dans ce cas, onSaveInstanceState(Bundle savedInstanceState) ne sera pas appelé par le système.

Liens pertinents:

Démo sur le comportement par défaut
Documentation officielle Android .

0
Rohit Singh

Kotlin

Vous devez remplacer onSaveInstanceState et onRestoreInstanceState pour stocker et récupérer les variables que vous souhaitez conserver.

Graphique du cycle de vie

Stocker les 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")
}

Récupérer des 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
}
0

J'ai une meilleure idée. Ce serait mieux de sauvegarder vos données sans appeler onCreate. Vous pouvez le désactiver d'activité lorsque l'orientation change.

Dans votre manifeste:

<activity Android:name=".MainActivity"
        Android:configChanges="orientation|screenSize">
0
Hossein Karami