Sauvegarde de L'État D'Activité Android en utilisant L'État D'Instance de sauvegarde

j'ai travaillé sur Android SDK plate-forme, et il est un peu difficile de savoir comment sauver l'état d'une application. Ainsi, étant donné ce réoutillage mineur 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);
  }
}

j'ai pensé que ce serait suffisant pour le cas le plus simple, mais il répond toujours avec le premier message, peu importe comment je navigue loin de l'application.

je suis sûr que la solution est aussi simple que de remplacer onPause ou quelque chose comme ça, mais j'ai été en fouillant dans la documentation pendant environ 30 minutes et n'ont rien trouvé d'évident.

2285
demandé sur Taryn 2008-09-30 08:41:15

27 réponses

vous devez remplacer onSaveInstanceState(Bundle savedInstanceState) et écrire les valeurs de l'état de l'application que vous voulez changer au paramètre Bundle comme ceci:

@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 ("Name-Value Pair"), et il sera passé à onCreate() et aussi onRestoreInstanceState() où vous extrairiez les valeurs comme ceci:

@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 utilisez habituellement cette technique pour stocker les valeurs d'instance pour votre application (les sélections, non enregistrées de texte, etc.).

2316
répondu Reto Meier 2014-08-07 10:11:08

le savedInstanceState est uniquement pour sauvegarder l'état associé à une instance courante d'une activité, par exemple les informations de navigation ou de sélection actuelles, de sorte que si Android détruit et recrée une activité, elle peut revenir comme elle était avant. Voir la documentation pour 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 État Persistant .

385
répondu Dave L. 2012-03-13 00:24:16

notez qu'il s'agit de et non de " sûr à utiliser onSaveInstanceState et onRestoreInstanceState pour 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 du onSaveInstanceState(Bundle) parce que le postérieur ne fait pas partie du callbacks, et ne sera donc pas appelé dans chaque situation que décrit dans sa documentation.

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

EDIT : pour plus de clarification, voici la onSaveInstanceState() documentation:

cette méthode est appelée avant qu'une activité puisse être tuée de sorte que lorsqu'elle il reviendra dans le futur, il pourra restaurer son état. Pour par exemple, si l'activité B est lancée devant l'activité A, et point d'activité est tué pour récupérer des ressources, l'activité aura une chance de sauver l'état actuel de son interface utilisateur via ce méthode de sorte que lorsque l'utilisateur revient à l'activité, l'état de la l'interface utilisateur peut être restaurée via onCreate(Bundle) ou onRestoreInstanceState(Bundle) .

373
répondu Steve Moseley 2015-06-24 23:04:27

mon collègue a écrit un article expliquant L'État de L'Application sur les appareils Android, y compris des explications sur le cycle de vie de L'activité et les informations de L'État, Comment stocker les informations de L'état, et de sauvegarder à L'État Bundle et SharedPreferences et jetez un oeil ici .

l'article couvre trois approches:

stocker les données locales variables / de contrôle de L'UI pour la durée de vie de l'application (c'est-à-dire temporairement) en utilisant le faisceau D'État de L'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 les données locales variables / de contrôle de L'UI entre les instances d'application (c'est-à-dire en permanence) en utilisant les 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();
}

Garder les instances d'objet vivant dans la mémoire entre les activités à l'intérieur de vie de l'application utilisant des bénéfices non Non Configuration de l'Instance

[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();
}
174
répondu Martin Belcher - Eigo 2013-06-18 09:58:31

c'est un classique 'gotcha' du développement Android. Il y a deux questions ici:

  • il y a un bug subtil de cadre Android qui complique grandement la gestion de la pile d'applications pendant le développement, au moins sur les versions anciennes (pas entièrement sûr si/quand/comment il a été corrigé). Je vais discuter de ce bug ci-dessous.
  • la façon "normale" ou voulue de gérer cette question Est, en soi, plutôt compliquée avec la dualité onPause/onResume et onSaveInstanceState / onRestoreInstanceState

en parcourant tous ces fils, je soupçonne que la plupart du temps les développeurs parlent de ces deux problèmes différents simultanément ... d'où toute la confusion et les rapports de "ça ne marche pas pour moi".

tout d'abord, pour clarifier le comportement "voulu": onssaveinstance et onRestoreInstance sont fragiles et seulement pour l'état transitoire. L'utilisation prévue (afaict) est de gérer l'Activité loisirs lors de la rotation du téléphone (changement d'orientation). En d'autres termes, l'utilisation prévue est quand votre Activité est toujours logiquement 'en haut', mais encore doit être réintroduit par le système. Le paquet sauvegardé ne persiste pas à l'extérieur du processus/mémoire/gc, donc vous ne pouvez pas vraiment compter sur cela si votre activité passe à l'arrière-plan. Oui, peut-être que la mémoire de votre activité survivra à son voyage à l'arrière-plan et échappera au GC, mais ce n'est pas fiable (ni prévisible).

donc si vous avez un scénario où il y a un 'progrès utilisateur' significatif ou un État qui devrait être persisté entre les 'lancements' de votre application, le Conseil est d'utiliser onPause et onResume. Vous devez choisir et préparer un magasin permanent vous-même.

mais - il y a un bug très confus 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 le drapeau SingleTask, et puis plus tard vous le lancez à partir de l'écran d'accueil ou le menu de lancement, alors que l'invocation subséquente 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 rapide. Cela semble se produire lorsque vous lancez votre application lors de le développement (i.e. D'Eclipse ou Intellij), donc les développeurs exécuter dans ce beaucoup. Mais aussi à travers certains des mécanismes de mise à jour de l'app store (donc cela affecte aussi vos utilisateurs).

j'ai lutté à travers ces threads pendant des heures avant de réaliser que mon principal problème était ce bug, pas le comportement de framework prévu. Un grand writeup et solution (mise à jour: voir ci-dessous) semble être de l'utilisateur @kaciula dans cette réponse:

La clé de la maison de la presse comportement

mise à jour juin 2013 : des mois plus tard, j'ai enfin trouvé la solution "correcte". Vous n'avez pas besoin de gérer des drapeaux startedApp stateful vous-même, vous pouvez le détecter à partir du cadre et le bail de manière appropriée. Je l'utilise au début de mon activité de lancement.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;
    }
}
129
répondu Mike Repass 2017-05-23 12:26:34

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 l'application. Donc je pense que l'état d'application devrait aussi être sauvegardé dans onPause il devrait être sauvegardé dans un stockage persistant comme Preferences ou Sqlite

70
répondu Fedor 2012-06-20 14:07:53

les deux méthodes sont utiles et valides et conviennent le mieux à différents scénarios:

  1. l'Utilisateur termine l'application et la ré-ouvre à une date ultérieure, mais l'application doit recharger les données de la dernière session – cela nécessite une approche de stockage persistante telle que L'utilisation de SQLite.
  2. L'utilisateur passe de l'application, puis revient à l'original et veut reprendre là où ils l'avaient laissée - pour enregistrer et restaurer bundle les données (telles que les données de l'état de l'application) dans onSaveInstanceState() et onRestoreInstanceState() sont généralement adéquates.

si vous enregistrez les données d'état de manière persistante, elles peuvent être rechargées dans un onResume() ou onCreate() (ou en fait sur n'importe quel appel du cycle de vie). Ce comportement peut être souhaitable ou non. Si vous le stockez dans un paquet dans un InstanceState , alors il est transitoire et est seulement adapté pour stocker des données pour une utilisation dans la même session de l'utilisateur ‘(j'utilise le terme session vaguement) mais pas entre "sessions".

Ce n'est pas qu'une approche est meilleure que l'autre, c'est comme tout, il est juste important de comprendre quels comportements vous avez besoin et sélectionnez l'approche la plus appropriée.

59
répondu David 2011-07-12 23:34:47

L'État de sauvetage est un kludge au mieux en ce qui me concerne. Si vous avez besoin de sauvegarder des données persistantes, utilisez simplement une base de données SQLite . Android le 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 que

dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;
50
répondu Mike A. 2014-06-21 22:08:12

je pense que j'ai trouvé la réponse. Laissez-moi vous dire ce que j'ai fait en termes simples:

supposons que j'ai deux activités, activity1 et activity2 et que je navigue de activity1 à activity2 (j'ai fait quelques travaux dans activity2) et de nouveau à l'activité 1 en cliquant sur un bouton dans activity1. Maintenant, à ce stade, j'ai voulu revenir à activity2 et je veux voir mon activity2 dans le même état quand je l'ai laissé activity2.

pour ce qui précède scénario ce que j'ai fait est que dans le manifeste j'ai fait quelques changements comme ceci:

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

et dans l'activity1 sur le bouton Click event j'ai fait comme ceci:

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

et en activity2 sur le bouton Click event j'ai fait comme ceci:

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

maintenant ce qui va se passer est que quels que soient les changements que nous avons faits dans l'activity2 ne seront pas perdus, et nous pouvons voir l'activity2 dans le même état que nous avons laissé précédemment.

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

49
répondu roy mathew 2014-10-31 19:01:15

recréer une activité

il existe quelques scénarios dans lesquels votre activité est détruite en raison du comportement normal de l'application, comme lorsque l'utilisateur appuie sur le bouton arrière ou votre activité signale sa propre destruction en appelant finish() . Le système peut également détruire votre activité si elle est actuellement arrêtée et n'a pas été utilisée depuis longtemps ou l'activité de premier plan nécessite plus de ressources de sorte que le système doit arrêter arrière-plan les processus pour récupérer de la mémoire.

lorsque votre activity est détruit parce que l'utilisateur appuie en arrière ou le activity se termine, le concept du système de cette instance Activity est parti pour toujours parce que le comportement indique l'activité n'est plus nécessaire. Cependant, si le système détruit l'activité en raison des contraintes du système (plutôt que le comportement normal de l'application), alors bien que l'instance D'activité réelle est partie, le système se souvient qu'il existait tel que si l'utilisateur navigue en arrière, le système crée une nouvelle instance de l'activité à l'aide d'un ensemble de données enregistrées qui décrit l'état de l'activité quand il a été destroyed . Les données sauvegardées que le système utilise pour restaurer l'état précédent sont appelées "état de l'instance" et sont une collection de paires de valeurs clés stockées dans un objet Bundle.

pour enregistrer des données supplémentaires sur l'état d'activité, vous devez outrepasser la méthode de rappel onSaveInstanceState (). Système appelle cette méthode lorsque l'utilisateur quitte votre activité et lui passe L'objet Bundle qui sera enregistré dans le cas où votre activité est détruite de manière inattendue. Si le système doit recréer l'instance d'activité plus tard, il passe le même objet Bundle aux deux méthodes onRestoreInstanceState() et onCreate() . enter image description here

comme le système commence à arrêter votre activité, il appelle onSaveInstanceState() (1) de sorte que vous pouvez spécifier des les données d'état que vous souhaitez enregistrer au cas où l'instance D'activité doit être recréée. Si l'activité est détruite et que la même instance doit être recréée, le système transmet les données d'état définies au paragraphe (1) à la fois à la méthode onCreate() (2) et à la méthode onRestoreInstanceState() (3).

Enregistrer Activity État

lorsque votre activité commence à s'arrêter, le système appelle onSaveInstanceState() de sorte que votre activité peut enregistrer des informations d'état avec une collection de paires de valeurs clés. L'implémentation par défaut de cette méthode enregistre des informations sur l'état de la hiérarchie de la vue de l'activité, comme le texte dans un widget EditText ou la position de défilement d'un ListView .

pour enregistrer des informations d'état supplémentaires pour votre activité, vous devez implémenter onSaveInstanceState() et ajouter des paires clé-valeur à l'objet Bundle. Par exemple:

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

attention: toujours appeler la superclasse implémentation de onSaveInstanceState() pour que l'implémentation par défaut puisse sauver l'état de la hiérarchie de la vue.

Restaurez Votre Activity État

lorsque votre activité est recréée après avoir été préalablement détruite, vous pouvez récupérer votre état enregistré à partir du faisceau que le système passe votre activité. Les méthodes de rappel onCreate() et onRestoreInstanceState() reçoivent toutes les deux le même Bundle qui contient l'instance les informations d'état.

parce que la méthode onCreate() est appelée si le système crée une nouvelle instance de votre activité ou en recréant une précédente, vous devez vérifier si le paquet d'état est nul avant de tenter de le lire. Si elle est nulle, alors le système crée une nouvelle instance de l'activité, au lieu de restaurer une précédente qui a été détruite.

Par exemple, voici comment vous pouvez restaurer certaines données d'état 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
 }

 }

au lieu de restaurer l'état pendant onCreate() vous pouvez choisir d'implémenter onRestoreInstanceState() , que le système appelle après la méthode onStart() . Le système appelle onRestoreInstanceState() seulement s'il y a un état sauvegardé à restaurer, donc vous n'avez pas besoin de vérifier si le paquet est null:

  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);
}
37
répondu Ravi Vaghela 2017-09-28 08:16:39

onSaveInstanceState() pour les données transitoires (restaurées en onCreate() / onRestoreInstanceState() ), onPause() pour les données persistantes (rétablies dans onResume() ). De Android de ressources techniques:

onSaveInstanceState () est appelé par Android si l'activité est arrêtée et peut être tué avant qu'elle ne soit reprise! Cela signifie qu'il doit stocker tout état nécessaire pour ré-initialiser le même état lorsque l'Activité est redémarré. Il est la contrepartie de la méthode onCreate (), et en fait le faisceau savedInstanceState passé à onCreate() est le même faisceau que vous construisez comme outState dans la méthode onSaveInstanceState ().

onPause () et onResume () sont également des méthodes complémentaires. onPause () est toujours appelé lorsque l'activité se termine, même si nous l'avons instigué (avec un finish () call par exemple). Nous utiliserons ceci pour sauvegarder la note actuelle à la base de données. La bonne pratique est de libérer toutes les ressources qui peuvent être libérées pendant une onPause () ainsi que de prendre moins de ressources lorsque dans l'état passif.

35
répondu Ixx 2015-12-17 21:29:07

Vraiment onSaveInstance état callen lorsque l'Activité se passe à l'arrière-plan

citation du docs: "la méthode onSaveInstanceState(Bundle) est appelée avant de placer l'activité dans un tel état de fond "

31
répondu u-foka 2012-06-20 14:08:10

en attendant, je n'utilise plus en général

Bundle savedInstanceState & Co

le cycle de vie est pour la plupart des activités trop compliqué et pas nécessaire. Et google se déclare, Il N'est même pas fiable.

à Ma façon est d'enregistrer les modifications immédiatement dans les préférences

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

d'une certaine façon, les références partagées fonctionnent comme des faisceaux. Et naturellement et d'abord de telles valeurs doivent être rouges des préférences.

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

en appliquant ce concept, l'activité continue d'utiliser le dernier état sauvegardé, qu'il s'agisse d'une ouverture initiale avec redémarrage entre ou d'une réouverture due à la pile arrière.

27
répondu stefan bachert 2012-03-31 13:36:59

pour aider à réduire le boilerplate j'utilise les interface et class suivants pour lire/écrire dans un Bundle pour 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éer une classe où la réflexion sera utilisée pour sauver des valeurs au faisceau:

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'usage:

public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icicle.load(savedInstanceState, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icicle.save(outState, this);
    }

}

Note: ce code a été adapté d'un projet de bibliothèque appelé AndroidAutowire qui est sous la licence MIT license .

26
répondu Jared Rummler 2015-08-20 02:04:11

pour répondre directement à la question initiale. savedInstancestate est null parce que votre activité n'est jamais recréée.

votre activité ne sera recréée avec un paquet d'état que lorsque:

  • modifications de Configuration telles que la modification de l'orientation ou du langage téléphonique qui peut nécessiter la création d'une nouvelle instance d'activité.
  • vous retournez à l'application de l'arrière-plan après que le système D'exploitation a détruit le activité.

Android va détruire les activités de fond lorsque sous la pression de la mémoire ou après qu'ils ont été dans l'arrière-plan pendant une période prolongée.

en testant votre exemple hello world, il y a plusieurs façons de partir et de retourner à l'activité.

  • lorsque vous appuyez sur le bouton arrière, L'activité est terminée. Re-lancement de l'app est une marque nouvelle instance. Tu ne reprends pas depuis l'arrière-plan. à tous.
  • lorsque vous appuyez sur le bouton Accueil ou utilisez le commutateur de tâches, L'activité passe en arrière-plan. Lors de la navigation de retour à l'application onCreate ne sera appelé que si l'activité a dû être détruite.

dans la plupart des cas, si vous appuyez simplement à la maison et puis le lancement de l'application à nouveau l'activité n'aura pas besoin d'être recréé. Il existe déjà dans la mémoire donc onCreate () ne sera pas appelé.

il y a un option sous Paramètres - > Options du développeur appelée "ne pas conserver les activités". Quand il est activé Android détruira toujours les activités et les recréer quand ils sont contextualisés. C'est une excellente option pour laisser activé lors du développement parce qu'il simule le pire scénario. (Un dispositif de mémoire basse recyclant vos activités tout le temps ).

les autres réponses sont précieuses en ce qu'elles vous enseignent les bonnes façons de stocker l'état, mais je ne pense pas qu'ils ont vraiment répondu Pourquoi ton code ne marchait pas comme tu l'espérais.

24
répondu Jared Kells 2015-01-21 02:19:16

les méthodes onSaveInstanceState(bundle) et onRestoreInstanceState(bundle) sont utiles pour la persistance des données en faisant simplement tourner l'écran (changement d'orientation).

Ils ne sont même pas bons en basculant entre les applications (puisque la méthode onSaveInstanceState() est appelé mais onCreate(bundle) et onRestoreInstanceState(bundle) n'est pas invoqué à nouveau.

Pour plus de persistance, utilisez les préférences partagées. lire l'article

22
répondu Mahorad 2012-12-18 04:04:07

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 seule exécution comprenant le démarrage d'autres sous-activités dans la même application et la rotation du dispositif, etc.). J'ai essayé diverses combinaisons des réponses ci-dessus, mais ne pas obtenir 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 à la savedInstanceState pendant onCreate:

mySavedInstanceState=savedInstanceState;

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

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

j'utilise onSaveInstanceState et onRestoreInstanceState comme suggéré ci-dessus mais je suppose que je pourrais également ou alternativement utiliser ma méthode pour sauver la variable quand elle change (par exemple en utilisant putBoolean )

14
répondu torwalker 2014-04-16 17:11:47

bien que la réponse acceptée est correcte, il y a une méthode plus rapide et plus facile pour sauver l'état D'activité sur Android en utilisant une bibliothèque appelée Icepick . Icepick est un processeur d'annotation qui s'occupe de tout le code boilerplate utilisé pour sauvegarder et restaurer l'État pour vous.

Faire quelque chose comme cela 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 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 fonctionne avec n'importe quel objet qui enregistre son état avec un Bundle .

11
répondu Kevin Cronly 2016-01-26 03:07:39

il y a essentiellement deux façons de mettre en œuvre ce changement.

  1. utilisant onSaveInstanceState() et onRestoreInstanceState() .
  2. dans le manifeste android:configChanges="orientation|screenSize" .

Je ne recommande vraiment pas d'utiliser la deuxième méthode. Depuis dans une de mes expériences, il a été la cause de la moitié de l'écran de l'appareil noir tout en tournant du portrait au paysage et vice versa.

en utilisant la première méthode mentionnée ci-dessus , nous pouvons persist data lorsque l'orientation est modifiée ou que tout changement de configuration se produit. Je connais un moyen de stocker n'importe quel type de données dans un objet savedInstance state.

exemple: considérez un cas si vous voulez persister objet Json. créez une classe de modèles 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 la méthode onCreate et onSaveInstanceState faire ce qui suit. Il ressemblera à quelque chose comme ceci:

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

}
11
répondu Krishna 2016-04-22 05:48:59

Lorsqu'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 nul pour la première fois, mais il contient des valeurs quand il est recréé. Pour sauvegarder L'État de L'activité, vous devez passer outre onSaveInstanceState ().

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

mettez vos valeurs dans L'objet" Outstate " Bundle comme outState.putString("key","Welcome Back") et de les enregistrer par un appel à super. Lorsque l'activité sera détruit c'est l'état d'être sauvé dans Bundle object et peut être restauré après la récréation dans onCreate() ou onRestoreInstanceState(). Bundle reçu dans onCreate() et onRestoreInstanceState() sont les mêmes.

   @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");
    }
10
répondu Mansuu.... 2017-01-23 10:29:47

voici un commentaire de Steve Moseley ' S réponse (par ToolmakerSteve ) qui met les choses en perspective (dans l'ensemble onSaveInstanceState vs onPause, east cost vs West cost saga)

@VVK-je suis en partie en désaccord. Certaines manières de quitter une application ne déclenchent pas onSaveInstanceState (oSIS). Cela limite l'utilité de tso. Son cela vaut la peine d'être supporté, pour des ressources de système d'exploitation minimales, mais si une application veut retourner l'utilisateur dans l'état où il était, peu importe comment l'application était sortie, il est nécessaire d'utiliser une approche de stockage persistante à la place. j'utilise onCreate pour vérifier le paquet, et si il est manquant, puis vérifier stockage persistant. cela centralise la prise de décision. Je peux récupérez d'un crash, ou d'une sortie de bouton de retour ou D'un élément de menu personnalisé de sortie, ou revenir à l'utilisateur d'écran était sur de nombreux jours plus tard. - ToolmakerSteve Sep 19 ' 15 à 10: 38

8
répondu samis 2017-02-07 16:02:51

code Kotlin:

enregistrer:

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

et ensuite 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
répondu Rafols 2018-02-12 19:42:32

Je ne suis pas sûr que ma solution soit désapprouvée ou pas, mais j'utilise un service lié pour maintenir L'état de ViewModel. Si vous le stockez en mémoire dans le service ou persist et récupérer à partir d'une base de données SqlLite dépend de vos exigences. C'est ce que les services de n'importe quelle saveur font, ils fournissent des services tels que le maintien de l'état d'application et la logique commerciale commune abstraite.

en raison des contraintes de mémoire et de traitement inhérentes aux appareils mobiles, je traite les vues Android d'une manière similaire à une page web. La page ne maintient pas l'état, il s'agit purement d'un composant de la couche de présentation dont le seul but est de présenter l'état de la demande et d'accepter la contribution de l'utilisateur. Les tendances récentes dans l'architecture d'application web emploient l'utilisation de L'âge vieux modèle, vue, contrôleur (MVC) modèle, où la page est la vue, les données de domaine est le modèle et le contrôleur se trouve derrière un service web. Le même modèle peut être employé dans android avec la vue étant bien ... la vue, le modèle est votre domaine data and the Controller est mis en œuvre sous la forme d'un service relié à Android. Chaque fois que vous voulez qu'une vue interagisse avec le controller, liez-la sur start/resume et unbind sur stop/pause.

cette approche vous donne le bonus supplémentaire d'appliquer le principe de séparation de la conception de L'application en ce sens que vous pouvez tous appliquer la logique d'affaires à votre service, ce qui réduit la logique dupliquée à travers plusieurs vues et permet à la vue d'appliquer un autre principe de conception important ., Une Seule Responsabilité.

5
répondu ComeIn 2016-08-09 12:57:37

Simple rapide pour résoudre ce problème est l'utilisation de IcePick

d'abord, 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, vérifions cet exemple ci-dessous comment sauvegarder l'état en activité

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

il travaille pour des activités, des Fragments ou tout objet qui a besoin de sérialiser son état sur un faisceau (par exemple les projecteurs de mortier)

peut également générer exemple de code d'État pour les 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
répondu THANN Phearum 2016-09-14 02:26:57

pour obtenir les données de l'état d'Activité stockées dans onCreate() , vous devez d'abord enregistrer les données dans savedInstanceState en remplaçant SaveInstanceState(Bundle savedInstanceState) méthode.

lorsque la méthode activity destroy SaveInstanceState(Bundle savedInstanceState) est appelée et que vous sauvegardez les données que vous voulez sauvegarder. Et vous obtenez la même chose dans onCreate() quand l'activité redémarre.(savedInstanceState ne sera pas null puisque vous avez enregistré quelques données avant que l'activité soit détruite)

5
répondu ascii_walker 2018-01-03 12:02:09

maintenant Android fournit ViewModels pour sauver l'état, vous devriez essayer d'utiliser cela à la place de saveInstanceState.

1
répondu M Abdul Sami 2018-08-27 11:51:27

ajouter LiveData (Android Architecture Components) à votre projet""

ajouter la dépendance suivante

implémentation " android.Arch.cycle de vie: extensions: 1.1.0"

LiveData accueille un observateur et l'informe des changements de données seulement lorsqu'il est en état de démarrage ou de reprise. L'avantage avec LiveData est que lorsque votre activité va dans un État autre que a commencé ou Reprise il ne sera pas appeler le onChanged méthode sur le observateur .

private TextView mTextView;
private MutableLiveData<String> mMutableLiveData;

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

}

0
répondu Shomu 2018-10-11 06:53:04