Question Enregistrement de l'état de l'activité Android à l'aide de l'état Enregistrer l'instance


J'ai travaillé sur la plate-forme Android SDK, et il est un peu difficile de savoir comment enregistrer l'état d'une application. Donc, étant donné ce ré-outillage mineur de l'exemple «Bonjour, 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 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 déroger onPause ou quelque chose comme ça, mais j'ai été piquer dans la documentation pendant 30 minutes ou plus et je n'ai rien trouvé d'évident.


2234
2017-09-30 04:41


origine


Réponses:


Vous devez remplacer onSaveInstanceState(Bundle savedInstanceState) et écrivez les valeurs d'état de l'application que vous voulez changer à la Bundle 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 une carte NVP ("Name-Value Pair"), et il sera transmis à onCreate() et aussi onRestoreInstanceState() où vous extrayez 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 utiliserez généralement cette technique pour stocker des valeurs d'instance pour votre application (sélections, texte non enregistré, etc.).


2271
2017-09-30 06:12



le savedInstanceState est uniquement pour enregistrer l'état associé à une instance actuelle d'une activité, par exemple les informations de navigation ou de sélection en cours, de sorte que si Android détruit et recrée une activité, elle peut revenir telle qu'elle était auparavant. Voir la documentation pour onCreate et onSaveInstanceState

Pour un état plus long, pensez à utiliser une base de données SQLite, un fichier ou des préférences. Voir Enregistrement de l'état persistant.


375
2017-09-30 05:03



Notez que c'est NE PAS 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() au lieu   de onSaveInstanceState(Bundle)   parce que le dernier ne fait pas partie de la   rappels de cycle de vie, donc ne sera pas   appelé dans toutes les situations décrites   dans sa documentation.

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

MODIFIER: Pour plus de précisions, voici la onSaveInstanceState() Documentation:

Cette méthode est appelée avant qu'une activité puisse être tuée de sorte que quand elle   revient quelque temps dans le futur, il peut restaurer son état. Pour   exemple, si l'activité B est lancée devant l'activité A, et   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 cette   méthode de sorte que lorsque l'utilisateur retourne à l'activité A, l'état de la   l'interface utilisateur peut être restaurée via onCreate(Bundle) ou    onRestoreInstanceState(Bundle).


365
2018-05-25 23:22



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 des activités et les informations sur l'état, comment stocker les informations sur l'état et enregistrer dans l'état Bundle et SharedPreferences et Jetez un oeil ici.

L'article couvre trois approches:

Stocker les données de contrôle Varible / UI locales pour la durée de vie de l'application (c'est-à-dire temporairement) à l'aide de l'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 les données de contrôle Varible / UI locales entre les instances d'application (c'est-à-dire de façon permanente) à l'aide des 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();
}

Conservation des instances d'objet en mémoire entre les activités au cours de la durée de vie de l'application à l'aide de l'instance de non-configuration 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();
}

171
2017-08-27 13:54



C'est un 'getcha' classique du développement Android. Il y a deux problèmes ici:

  • Il existe un bogue subtil d'Android Framework qui complique grandement la gestion de la pile d'applications pendant le développement, au moins sur les versions héritées (pas tout à fait sûr si / quand / comment il a été corrigé). Je vais discuter de ce bug ci-dessous.
  • La façon «normale» ou prévue de gérer ce problème est elle-même plutôt compliquée avec la dualité de onPause / onResume et onSaveInstanceState / onRestoreInstanceState

En parcourant toutes ces discussions, je soupçonne que la plupart du temps les développeurs parlent de ces deux problèmes simultanément ... d'où toute la confusion et les rapports de "ça ne marche 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) est de gérer les activités de loisirs lorsque le téléphone est tourné (changement d'orientation). En d'autres termes, l'utilisation prévue est lorsque votre activité est toujours logiquement «en haut», mais doit encore être réinstancée par le système. L'ensemble sauvegardé n'est pas conservé en dehors du processus / memory / gc, vous ne pouvez donc pas vraiment compter dessus si votre activité passe en 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 (ce n'est pas prévisible).

Donc, si vous avez un scénario où il y a une «progression de l'utilisateur» significative ou un état qui devrait persister 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 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 l'indicateur SingleTask, et que vous la lancerez plus tard à partir de l'écran d'accueil ou du menu du lanceur, cette invocation suivante créera une NOUVELLE tâche ... vous aurez effectivement deux instances différentes de votre application habitant la même pile ... qui devient très étrange très vite. Cela semble se produire lorsque vous lancez votre application pendant le développement (c'est-à-dire depuis Eclipse ou Intellij), ce qui fait que les développeurs s'y confrontent souvent. Mais aussi grâce à certains mécanismes de mise à jour de l'app store (cela a donc un impact sur vos utilisateurs).

J'ai lutté à travers ces fils pendant des heures avant que je réalise que mon problème principal était ce bug, pas le comportement de cadre prévu. Un grand écrit et solution de contournement (UPDATE: voir ci-dessous) semble être de l'utilisateur @kaciula dans cette réponse:

Comportement de la touche principale

MISE À JOUR Juin 2013: Des mois plus tard, j'ai enfin trouvé la solution 'correcte'. Vous n'avez pas besoin de gérer vous-même les drapeaux startApp dynamiques, vous pouvez les détecter à partir du framework et les mettre en liberté sous caution. 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;
    }
}

126
2017-10-19 23:47



onSaveInstanceStateest appelée 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. Donc, je pense que l'état de l'application devrait également être enregistré dans onPause Il devrait être sauvegardé dans un stockage persistant comme Preferences ou Sqlite


70
2018-05-07 00:21



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

  1. L'utilisateur met fin à l'application et la rouvre à une date ultérieure, mais l'application doit recharger les données de la dernière session, ce qui nécessite une approche de stockage persistant, telle que l'utilisation de SQLite.
  2. L'utilisateur bascule l'application, puis revient à l'original et veut reprendre là où elle l'avait laissé - sauvegarder et restaurer les données de l'ensemble (telles que les données de l'état de l'application) dans onSaveInstanceState() et onRestoreInstanceState() est généralement adéquat.

Si vous enregistrez les données d'état de manière persistante, vous pouvez les recharger dans un onResume() ou onCreate() (ou en fait sur n'importe quel appel de cycle de vie). Cela peut ou peut ne pas être un comportement souhaité. Si vous le stockez dans un paquet dans un InstanceState, alors il est transitoire et ne convient que pour stocker des données à utiliser dans la même 'session' utilisateur (j'utilise le terme session vaguement) mais pas entre 'sessions'.

Ce n'est pas qu'une approche est meilleure que l'autre, comme tout, il est juste important de comprendre le comportement dont vous avez besoin et de choisir l'approche la plus appropriée.


59
2018-06-27 16:17



L'état d'épargne est au mieux un kludge en ce qui me concerne. Si vous devez enregistrer des données persistantes, utilisez simplement SQLite base de données. Android le fait 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;

50
2018-06-23 17:07



Je pense que j'ai trouvé la réponse. Laissez-moi vous dire ce que j'ai fait avec des mots 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, je voulais revenir à l'activité 2 et je veux voir mon activité2 dans le même état quand j'ai quitté l'activité2.

Pour le scénario ci-dessus 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'activité1 sur l'événement de clic sur le bouton, 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 dans l'activité2 sur l'événement clic de bouton, j'ai fait comme ceci:

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

Maintenant, ce qui se passera, c'est que les changements que nous avons faits dans l'activité2 ne seront pas perdus, et nous pouvons voir l'activité2 dans le même état que nous l'avions laissé précédemment.

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


50
2018-02-05 11:35