2686 votes

Enregistrement de l’état de l’activité dans Android

J'ai été jouer avec le SDK Android, et je suis un peu incertain sur l'enregistrement d'une demande de l'état. Alors au vu de cette mineure ré-outillage de l' "Bonjour, Android' exemple:

package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {
    /** 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);
    }

    private TextView mTextView = null;
}

J'ai pensé que peut-être tout ce qu'il fallait faire pour le cas le plus simple, mais il me donne toujours le premier message, peu importe comment je naviguez à partir de l'application. Je suis sûr que c'est probablement quelque chose de simple comme primordial onPause ou quelque chose comme ça, mais j'ai été fouiller loin dans les docs pour 30 minutes ou plus, et n'ai pas trouvé quelque chose d'évident, de sorte que serais reconnaissant de toute aide.

Cue en me regardant d'un fou dans trois, deux, un...

2621voto

Reto Meier Points 55904

Vous avez besoin de redéfinir onSaveInstanceState(Bundle savedInstanceState) et écrire l'état de l'application des valeurs que vous souhaitez changer le Faisceau paramètre 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 un NVP ("Paire Nom-Valeur") de la carte, et il sera adopté en onCreate et également onRestoreInstanceState où vous auriez extraire 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 n'avez généralement utiliser cette technique pour stocker les valeurs d'instance de votre application (sélections, non enregistrées de texte, etc.).

440voto

Dave L. Points 19623

L' savedInstanceState est seulement pour la sauvegarde de l'état associé à une instance en cours d'une Activité, par exemple actuel de la navigation ou de la sélection de l'info, de sorte que si Android détruit et recrée une Activité, il peut revenir comme il était avant. Voir la documentation de l' onCreate et onSaveInstanceState

Pour plus longue durée de vie de l'état, envisagez l'utilisation d'une base de données SQLite, un fichier, ou de vos préférences. Voir L' Économie D'Un État Persistant.

427voto

Steve Moseley Points 2004

Notez qu'il n'est PAS sûr à utiliser, onSaveInstanceState et onRestoreInstanceState, selon la documentation sur l'Activité dans les états http://developer.android.com/reference/android/app/Activity.html.

Le document (dans l'Activité de Cycle de vie " de la section):

Notez qu'il est important d'enregistrer les données persistantes en onPause()au lieu d' onSaveInstanceState(Bundle) parce que le plus tard ne fait pas partie de la callbacks, et ne sera donc pas appelé dans chaque situation que décrit dans sa documentation.

En d'autres termes, mettre votre sauvegarde/restauration de code en onPause() et onResume() à la place!

211voto

Mon collègue a écrit un article expliquant l'État de l'Application sur les appareils Android, y compris des explications sur l'Activité du Cycle de vie et des Informations d'État, Comment faire pour Stocker des Informations d'État, et de l'économie d'État Bundle et SharedPreferences et de prendre un coup d'oeil à ici.

L'article traite de trois approches:

Magasin local varible/UI données de contrôle pour la vie de l'application (c'est à dire temporairement) à l'aide de l'État de l'Instance Bundle

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

Magasin local varible/contrôle d'INTERFACE utilisateur de données entre les instances d'une application (c'est à dire en permanence) de l'utilisation Partagée des Préférences

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

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

152voto

Mike Repass Points 2302

C'est un classique de la "chasse aux sorcières" de développement Android. Il y a deux problèmes ici:

  • Il y a une subtile Android Cadre d'un bug qui complique grandement application de gestion de la pile au cours du développement, au moins sur les anciennes versions (pas tout à fait sûr si/quand/comment il a été résolu). Je vais discuter de ce bug ci-dessous.
  • Le "normal" ou destinés à cet égard, lui, est plutôt compliqué avec la dualité de onPause/onResume et onSaveInstanceState/onRestoreInstanceState

La navigation à travers tous ces fils, je soupçonne que la plupart du temps, les développeurs sont en train de parler à propos de ces deux problèmes en même temps ... d'où la confusion et les rapports de "cela ne fonctionne pas pour moi".

Tout d'abord, pour clarifier le " but " de comportement: onSaveInstance et onRestoreInstance est fragile et passagère de l'état. L'utilisation prévue (afaict) est de gérer les Activités de loisirs lorsque le téléphone est mis en rotation (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 enregistré le Bundle n'est pas conservée en dehors du processus/mémoire/gc, de sorte que vous ne peut pas vraiment compter sur cette si votre activité passe à l'arrière plan. Oui, peut-être que votre Activité du mémoire survivra à son voyage à l'arrière-plan et d'échapper à la GC, mais ce n'est pas fiable (il n'est ni prévisible).

Donc, si vous avez un scénario où il est significatif d'utilisateur "progrès" ou de l'état qui doit être maintenu entre la "lance" de votre application, la direction est d'utiliser onPause et onResume. Vous devez choisir et préparer un magasin permanent vous-même.

MAIS - il y a un très déroutant bug 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

En fait, si votre application est lancée avec la SingleTask drapeau, et puis plus tard, vous le lancer à partir de l'écran d'accueil ou le menu du lanceur, alors qu'à la suite de l'invocation va créer une NOUVELLE tâche ... vous allez effectivement avoir deux différentes instances de votre application habiter dans le même stack ... qui est très étrange, très rapide. Cela semble se produire lorsque vous lancez votre application en cours de développement (c'est à dire à partir d'Eclipse ou Intellij), afin que les développeurs de cette beaucoup. Mais aussi à travers certains de l'app store mécanismes de mise à jour (si elle a des effets sur vos utilisateurs).

Je me suis bien battu au travers de ces fils pendant des heures avant de me rendre compte que mon principal problème a été de ce bug, pas l'intention cadre comportement. Un grand writeup et solution de contournement (mise à JOUR: voir ci-dessous) semble être de l'utilisateur @kaciula dans cette réponse:

La clé de la maison de la presse de comportement

Mise à JOUR juin 2013: Mois plus tard, j'ai enfin trouvé la "bonne" solution. Vous n'avez pas besoin de gérer tout stateful startedApp drapeaux vous-même, vous pouvez détecter ce à partir du cadre et de la caution de façon appropriée. J'utilise ce près le 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;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X