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

2620

J'ai travaillé sur la plate-forme Android SDK, et il n'est pas clair 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 la façon dont je m'éloigne de l'application.

Je suis sûr que la solution est aussi simple que de remplacer onPauseou quelque chose comme ça, mais je fouille dans la documentation depuis environ 30 minutes et je n'ai rien trouvé d'évident.

Bernard
la source
9
Quand est saveInstanceState == null et quand n'est-il pas nul?
Trojan.ZBOT
90
Vous détruisez explicitement votre activité en - comme vous l'avez dit, en vous éloignant d'elle, par exemple en appuyant en arrière. En fait, le scénario dans lequel ce «savedInstanceState» est utilisé, c'est quand Android détruit votre activité pour les loisirs. Par exemple: si vous changez la langue de votre téléphone pendant que l'activité était en cours (et donc des ressources différentes de votre projet doivent être chargées). Un autre scénario très courant consiste à faire pivoter votre téléphone sur le côté afin que l'activité soit recréée et affichée en paysage.
villoren
16
Pour obtenir le deuxième message, activez «Ne pas conserver les activités» dans les options de développement. Appuyez sur un bouton d'accueil et revenez des recents.
Yaroslav Mytkalyk
6
vous pouvez le faire avec: onSaveInstanceState (Bundle savedInstanceState)
VahidHoseini

Réponses:

2568

Vous devez remplacer onSaveInstanceState(Bundle savedInstanceState)et écrire les valeurs d'état d'application que vous souhaitez modifier dans le Bundleparamè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 ("paire nom-valeur"), et il sera transmis à onCreate()et également onRestoreInstanceState()où vous extrairez les valeurs d'une activité comme celle-ci:

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

Ou d'un fragment.

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(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.).

Reto Meier
la source
24
Y a-t-il une chance que cela fonctionne sur le téléphone, mais pas dans l'émulateur? Je n'arrive pas à obtenir une instance de sauvegarde non nulle.
Adam Jack
491
ATTENTION: vous devez appeler super.onSaveInstanceState (savedInstanceState) avant d'ajouter vos valeurs au Bundle, sinon elles seront effacées lors de cet appel (Droid X Android 2.2).
jkschneider
121
Attention: la documentation officielle stipule que vous devez enregistrer des informations importantes dans la méthode onPause car la méthode onsaveinstance ne fait pas partie du cycle de vie Android. developer.android.com/reference/android/app/Activity.html
schlingel
32
Ce fait rend onSaveInstanceStatepratiquement inutile presque sauf pour le cas de changements d'orientation d'écran. Dans presque tous les autres cas, vous ne pouvez jamais vous y fier et devrez enregistrer manuellement votre état d'interface utilisateur ailleurs. Ou empêcher votre application d'être supprimée en remplaçant le comportement du bouton RETOUR. Je ne comprends pas pourquoi ils l'ont même implémenté comme ça en premier lieu. Totalement peu intuitif. Et vous ne pouvez pas avoir cet ensemble que le système vous donne pour enregistrer des choses, sauf dans cette méthode très particulière.
chakrit
12
Notez que l'enregistrement / la restauration de l'état de l'interface utilisateur vers / depuis le bundle est automatiquement pris en charge pour les Views auxquels des ID ont été attribués . À partir des onSaveInstanceStatedocuments: "L'implémentation par défaut prend en charge la plupart de l'état d'interface utilisateur pour vous en appelant onSaveInstanceState()chaque vue de la hiérarchie qui a un identifiant et en enregistrant l'identifiant de la vue actuellement concentrée (qui est entièrement restaurée par l'implémentation par défaut de onRestoreInstanceState(Bundle)) "
Vicky Chijwani
433

L' savedInstanceStateoption sert uniquement à enregistrer l'état associé à une instance actuelle 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 puisse revenir comme avant. Voir la documentation pour onCreateetonSaveInstanceState

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

Dave L.
la source
3
Quand est saveInstanceState == null et quand n'est-il pas nul?
Trojan.ZBOT
6
savedInstanceState est null lorsque le système crée une nouvelle instance de votre activité et non null lors de sa restauration.
Gabriel Câmara
7
... ce qui soulève la question de savoir quand le système doit créer une nouvelle instance d'activité. Certaines façons de quitter une application ne créent pas de bundle, donc une nouvelle instance doit être créée. Tel est le problème fondamental; cela signifie que l'on ne peut pas s'appuyer sur l'existence d'un bundle et que l'on doit utiliser d'autres moyens de stockage persistant. L'avantage de onSave / onRestoreInstanceState est que c'est un mécanisme que le système peut exécuter brusquement , sans consommer beaucoup de ressources système. Il est donc bon de prendre en charge cela, ainsi que d'avoir un stockage persistant pour une sortie plus élégante de l'application.
ToolmakerSteve
415

Notez qu'il n'est PAS sûr d'utiliser onSaveInstanceStateet 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 des activités»):

Notez qu'il est important d'enregistrer les données persistantes au onPause()lieu de onSaveInstanceState(Bundle) car ces dernières ne font pas partie des rappels du cycle de vie, elles ne seront donc pas appelées dans toutes les situations comme 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 précisions, voici la onSaveInstanceState()documentation:

Cette méthode est appelée avant qu'une activité ne soit supprimée afin que lorsqu'elle revienne dans le futur, elle puisse restaurer son état. Par exemple, si l'activité B est lancée devant l'activité A, et à un moment donné, l'activité A est supprimée pour récupérer des ressources, l'activité A aura la possibilité de sauvegarder l'état actuel de son interface utilisateur via cette méthode de sorte que lorsque l'utilisateur revient à l'activité A, l'état de l'interface utilisateur peut être restauré viaonCreate(Bundle) ou onRestoreInstanceState(Bundle).

Steve Moseley
la source
55
Juste pour pinailler: ce n'est pas dangereux non plus. Cela dépend simplement de ce que vous souhaitez conserver et de la durée, ce sur quoi @Bernard n'est pas tout à fait clair dans sa question d'origine. InstanceState est parfait pour préserver l'état actuel de l'interface utilisateur (données entrées dans les contrôles, positions actuelles dans les listes, etc.), tandis que Pause / Resume est la seule possibilité de stockage persistant à long terme.
Pontus Gagge
30
Cela devrait être rétrogradé. Il n'est pas sûr d'utiliser sur (Save | Restore) InstanceState comme les méthodes de cycle de vie (c'est-à-dire faire autre chose que d'enregistrer / restaurer l'état). Ils sont parfaitement bons pour sauvegarder / restaurer l'état. De plus, comment voulez-vous enregistrer / restaurer l'état dans onPause et onResume? Vous n'obtenez pas de bundles dans ces méthodes que vous pouvez utiliser, vous devez donc utiliser d'autres sauvegardes d'état, dans des bases de données, des fichiers, etc., ce qui est stupide.
Felix
141
Nous ne devons pas voter contre cette personne au moins, il a fait des efforts pour parcourir la documentation et je pense que nous sommes ici pour construire une communauté bien informée et nous entraider pour ne pas FAIRE DE VOTE. Je vote donc pour l'effort et je vous demanderais de ne pas voter contre plutôt de voter ou de ne pas voter ... cette personne dissipe la confusion que l'on aimerait avoir en parcourant la documentation. 1 vote :)
AZ_
21
Je ne pense pas que cette réponse mérite un downvote. Au moins, il fit un effort pour répondre et avait cité une section du doco.
GSree
34
Cette réponse est absolument correcte et mérite un vote UP, pas down! Permettez-moi de clarifier la différence entre les États pour les gars qui ne le voient pas. Un état de l'interface graphique, comme les boutons radio sélectionnés et du texte dans le champ de saisie, est beaucoup moins important que l'état des données, comme les enregistrements ajoutés à une liste affichée dans un ListView. Ce dernier doit être stocké dans la base de données dans onPause car c'est le seul appel garanti. Si vous le placez dans onSaveInstanceState à la place, vous risquez de perdre des données si elles ne sont pas appelées. Mais si la sélection des boutons radio n'est pas enregistrée pour la même raison - ce n'est pas grave.
JBM
206

Mon collègue a écrit un article expliquant l' état d'application sur les appareils Android , y compris des explications sur le cycle de vie des activités et des informations d'état, comment stocker les informations d'état et d' économie à l'autre Bundleet SharedPreferenceset jeter un oeil à ici .

L'article couvre trois approches:

Stocker les données de contrôle des variables / UI locales pour 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);
}

Stockez les données de contrôle des variables / UI locales 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();
}

Conservation des instances d'objet en mémoire entre les activités pendant la durée de vie de l'application à l'aide d'une 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();
}
Martin Belcher - AtWrk
la source
3
@ MartinBelcher-Eigo L'article dit à propos des données dans SharedPreferences que "Ces données sont écrites dans la base de données sur l'appareil .." Je pense que les données sont stockées dans un fichier dans le répertoire de l'application du système de fichiers.
Tom
2
Les données @Tom SharefPrefs sont écrites dans un fichier xml. Le xml est-il une sorte de base de données? Je dirais que c'est;)
MaciejGórski
148

Il s'agit d'un «piège» classique du développement Android. Il y a deux problèmes ici:

  • Il existe un subtil bogue Android Framework qui complique considérablement 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 bogue ci-dessous.
  • La façon «normale» ou voulue de gérer ce problème est, en soi, plutôt compliquée avec la dualité de 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 "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) est de gérer les activités récréatives 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 «au sommet», mais doit toujours être confirmée par le système. Le bundle enregistré n'est pas conservé en dehors du processus / mémoire / gc, vous ne pouvez donc pas vraiment vous y fier 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 ce n'est pas fiable (ni prévisible).

Donc, si vous avez un scénario où il y a une «progression utilisateur» significative ou un état qui devrait persister entre les «lancements» de votre application, les conseils sont 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, puis que vous la lancez à 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 rapidement. Cela semble se produire lorsque vous lancez votre application pendant le développement (c'est-à-dire depuis Eclipse ou Intellij), donc les développeurs s'y heurtent beaucoup. Mais aussi à travers certains des mécanismes de mise à jour de l'App Store (cela a donc un impact sur vos utilisateurs également).

J'ai lutté à travers ces threads pendant des heures avant de réaliser que mon problème principal était ce bug, pas le comportement du framework prévu. Un grand résumé etsolution de contournement (MISE À JOUR: voir ci-dessous) semble provenir de l'utilisateur @kaciula dans cette réponse:

Comportement de la touche Accueil

MISE À JOUR Juin 2013 : Des mois plus tard, j'ai enfin trouvé la «bonne» solution. Vous n'avez pas besoin de gérer vous-même les indicateurs de démarrage avec état, vous pouvez les détecter à partir du framework et les mettre en liberté sous caution de manière appropriée. J'utilise cela au 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;
    }
}
Mike Repass
la source
87

onSaveInstanceStateest 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 onPauseIl doit être enregistré dans un stockage persistant comme PreferencesouSqlite

Fedor
la source
36
Désolé, ce n'est pas tout à fait correct. onSaveInstanceState est appelé avant que l'activité ne doive être refaite. c'est-à-dire chaque fois que l'utilisateur fait pivoter l'appareil. Il est destiné à stocker des états de vue transitoires. Lorsque Android force la fermeture de l'application, onSaveInstanceState n'est en fait PAS appelé (c'est pourquoi il n'est pas sûr de stocker des données d'application importantes). onPause est cependant garanti d'être appelé avant la fin de l'activité, il doit donc être utilisé pour stocker des informations permanentes dans les préférences ou Squlite. Bonne réponse, mauvaises raisons.
moveaway00
74

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

  1. L'utilisateur ferme l'application et la rouvre à 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 change d'application, puis revient à l'original et souhaite reprendre là où il s'était arrêté - enregistrer et restaurer les données de l'ensemble (telles que les données d'état de l'application) onSaveInstanceState()et onRestoreInstanceState()est généralement adéquat.

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 de cycle de vie). Cela peut ou non être un comportement souhaité. Si vous le stockez dans un bundle dans un InstanceState, il est transitoire et ne convient que pour stocker des données à utiliser dans la même `` session '' utilisateur (j'utilise le terme session de manière lâche) mais pas entre les `` sessions ''.

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

David
la source
70

Sauver l'état est un kludge au mieux en ce qui me concerne. Si vous devez enregistrer 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;
Mike A.
la source
9
Parce qu'il faut trop de temps pour charger une base de données SQLite, étant donné que cela se trouve sur le chemin critique pour montrer à l'utilisateur l'interface utilisateur de l'application. Je ne l'ai pas chronométré, donc je suis content d'être corrigé, mais le chargement et l'ouverture d'un fichier de base de données ne seront sûrement pas rapides?
Tom
5
Merci beaucoup d'avoir fourni une solution qu'un débutant peut copier-coller dans son application et utiliser immédiatement! @Tom En ce qui concerne la vitesse, il faut environ sept secondes pour stocker 1000 paires, mais vous pouvez le faire dans une AsyncTask. Cependant, vous devez ajouter enfin {cursor.close ()} ou il se bloquera suite à une fuite de mémoire.
Noumenon
3
Je suis tombé sur cela et même si cela semble soigné, j'hésite à essayer de l'utiliser sur Google Glass, qui est l'appareil sur lequel je travaille / avec récemment.
Stephen Tetreault
61

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

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

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

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

Et dans l'activité1 sur l'événement de clic de 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 activity2 on button click event j'ai fait comme ceci:

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

Maintenant, ce qui va arriver, c'est que quelles que soient les modifications que nous avons apportées à l'activité2 ne seront pas perdues, et nous pouvons 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 cela fonctionne très bien pour moi. Corrigez-moi si je me trompe.

roy mathew
la source
2
@bagusflyer se soucie d'être plus précis ??? Votre commentaire n'est pas utile et personne ne peut vous aider sur cette base.
Stephen Tetreault
2
Il s'agit d'une réponse à une situation différente: deux activités au sein de la même application. OP consiste à quitter l'application (par exemple, le bouton d'accueil ou d'autres moyens pour passer à une autre application).
ToolmakerSteve
44

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

onSaveInstanceState () est appelé par Android si l'activité est arrêtée et peut être tuée avant qu'elle ne reprenne! Cela signifie qu'il doit stocker tout état nécessaire pour réinitialiser à la même condition lorsque l'activité est redémarrée. Il s'agit de l'équivalent de la méthode onCreate (), et en fait 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'avons incité (avec un appel finish () par exemple). Nous l'utiliserons pour sauvegarder la note actuelle dans la base de données. La bonne pratique consiste également à libérer toutes les ressources qui peuvent être libérées pendant une onPause (), afin de consommer moins de ressources à l'état passif.

Ixx
la source
40

onSaveInstanceState()Est vraiment appelé lorsque l'activité passe en arrière-plan.

Citation de la documentation: "Cette méthode est appelée avant qu'une activité ne soit arrêtée afin que lorsqu'elle revienne dans le futur, elle puisse restaurer son état." La source

u-foka
la source
37

Pour aider à réduire le passe-partout, j'utilise ce qui suit interfaceet classpour lire / écrire dans un Bundleétat d'instance d'enregistrement.


Créez d'abord 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 les 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 qui est autorisé sous le licence MIT .

Jared Rummler
la source
34

En attendant je ne m'en sers 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 déclare lui-même, ce n'est même pas fiable.

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

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

D'une certaine manière, SharedPreferences fonctionne de la même manière que les bundles. Et naturellement et au début, ces 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.

Lors de l'application de ce concept, l'activité continue simplement d'utiliser le dernier état enregistré, qu'il s'agisse d'une ouverture initiale avec redémarrages entre les deux ou d'une réouverture en raison de la pile arrière.

Stefan Bachert
la source
31

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

Votre activité ne sera recréée avec un ensemble d'états que lorsque:

  • Modifications de configuration telles que la modification de l'orientation ou de la langue du téléphone, ce qui peut nécessiter la création d'une nouvelle instance d'activité.
  • Vous revenez à l'application depuis l'arrière-plan une fois que le système d'exploitation a détruit l'activité.

Android détruira les activités d'arrière-plan lorsqu'il est sous pression de la mémoire ou après avoir été en arrière-plan pendant une période prolongée.

Lorsque vous testez votre exemple de bonjour, 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 tout du fond.
  • Lorsque vous appuyez sur le bouton d'accueil ou utilisez le sélecteur de tâches, l'activité passe en arrière-plan. Lorsque vous revenez à l'application, onCreate ne sera appelé que si l'activité devait être détruite.

Dans la plupart des cas, si vous appuyez simplement sur 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. Il s'agit d'une excellente option pour laisser activé lors du développement, car il simule le pire des cas. (Un dispositif à faible mémoire recyclant tout le temps vos activités).

Les autres réponses sont précieuses dans la mesure où elles vous enseignent les bonnes façons de stocker l'état, mais je ne pense pas qu'elles aient vraiment répondu POURQUOI votre code ne fonctionnait pas comme vous vous y attendiez.

Jared Kells
la source
28

Les méthodes onSaveInstanceState(bundle)et onRestoreInstanceState(bundle)sont utiles pour la persistance des données simplement lors de la rotation de l'écran (changement d'orientation).
Ils ne sont même pas bon lors de la commutation entre les applications (car la onSaveInstanceState()méthode est appelée , mais onCreate(bundle)et onRestoreInstanceState(bundle)ne sera relancé.
Pour une utilisation plus persistance des préférences partagées. Lire cet article

Mahorad
la source
2
Dans votre cas onCreateet onRestoreInstanceStatene sont pas appelés car ils Activityne sont pas détruits du tout lorsque vous changez d'application, il n'est donc pas nécessaire de restaurer quoi que ce soit. Android appelle onSaveInstanceStatejuste au cas où l'activité serait détruite plus tard (ce qui se produit avec 100% de certitude lors de la rotation de l'écran car la configuration complète de l'appareil a changé et l'activité doit être recréée à partir de zéro).
Vicky Chijwani
20

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 de l'appareil, etc.). J'ai essayé différentes 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 à la sharedInstanceState pendant onCreate:

mySavedInstanceState=savedInstanceState;

et l'utiliser pour obtenir le contenu de ma variable quand j'en ai besoin, le long des lignes de:

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

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

torwalker
la source
19

Bien que la réponse acceptée soit correcte, il existe une méthode plus rapide et plus simple pour enregistrer l'état d'activité sur Android à l'aide d'une bibliothèque appelée Icepick . Icepick est un processeur d'annotation qui s'occupe de tout le code passe-partout utilisé pour enregistrer et restaurer 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);
  }
}

C'est la même chose que de 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 a Bundle.

kevinc
la source
16

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 null pour la première fois, mais il contient des valeurs lors de sa recréation. 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 l'objet Bundle "outState" comme outState.putString ("clé", "Welcome Back") et enregistrez en appelant super. Lorsque l'activité est détruite, son état est enregistré dans l'objet Bundle et peut être restauré après la recréation dans onCreate () ou onRestoreInstanceState (). Les bundles 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");
    }
Mansuu ....
la source
15

Il existe essentiellement deux façons de mettre en œuvre ce changement.

  1. en utilisant onSaveInstanceState()etonRestoreInstanceState() .
  2. En manifeste android:configChanges="orientation|screenSize".

Je ne recommande vraiment pas d'utiliser la deuxième méthode. Étant donné que dans une de mes expériences, la moitié de l'écran de l'appareil était noir lors de la rotation du portrait au paysage et vice versa.

En utilisant la première méthode mentionnée ci-dessus, nous pouvons conserver les données lorsque l'orientation est modifiée ou tout changement de configuration se produit. Je connais une manière dont vous pouvez stocker n'importe quel type de données à l'intérieur de l'objet d'état saveInstance.

Exemple: considérez un cas si vous souhaitez conserver un objet Json. créer une classe 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 à 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); 

}
Krishna
la source
11

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

@VVK - Je suis partiellement en désaccord. Certaines façons de quitter une application ne déclenchent pas onSaveInstanceState (oSIS). Cela limite l'utilité de l'oSIS. Cela vaut la peine d'être pris en charge, pour un minimum de ressources du système d'exploitation, mais si une application veut ramener l'utilisateur à l'état dans lequel il se trouvait, quelle que soit la façon dont l'application a été fermée, il est nécessaire d'utiliser une approche de stockage persistant à la place. J'utilise onCreate pour vérifier le bundle, et s'il est manquant, vérifiez le stockage persistant. Cela centralise la prise de décision. Je peux récupérer d'un crash, ou quitter le bouton de retour ou l'élément de menu personnalisé Quitter, ou revenir à l'utilisateur de l'écran était sur plusieurs jours plus tard. - ToolmakerSteve 19 sept. 15 à 10:38

Sam est
la source
10

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()ouonRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

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

Rafols
la source
9

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

Lorsque la SaveInstanceState(Bundle savedInstanceState)méthode de destruction d'activité est appelée et que vous enregistrez les données que vous souhaitez enregistrer. Et vous obtenez la même chose onCreate()lorsque l'activité redémarre. (SavedInstanceState ne sera pas nul puisque vous y avez enregistré des données avant que l'activité ne soit détruite)

ascii_walker
la source
6

Simple et rapide pour résoudre ce problème, utilisez IcePick

Tout d'abord, configurez 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 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);
  }
}

Il fonctionne pour les activités, les fragments ou tout autre objet qui doit sérialiser son état sur un bundle (par exemple, les ViewPresenters du mortier)

Icepick peut également générer le code d'état d'instance 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
}
THANN Phearum
la source
1
@ralphspoon oui, cela fonctionne pour Fragment et Custom View. Veuillez vérifier l'exemple de code. J'ai édité ma réponse. Je vous suggère de consulter les documents officiels ici github.com/frankiesardo/icepick pour trouver plus d'exemples de code.
THANN Phearum
@ChetanMehra vous voulez dire la classe de vue personnalisée, non? S'il s'agit d'une vue personnalisée, nous pouvons remplacer onSaveInstanceState et onRestoreInstanceState comme dans l'exemple ci-dessus de CustomView.
THANN Phearum
Je veux dire un objet de classe à l'intérieur de la classe de vue par exemple: classe CustomView étend View {@State ClassA a;} ou classe CustomView étend View {@ State Inner class {}}
Chetan Mehra
@THANNPhearum Dois-je le poser comme une autre question?
Chetan Mehra
Je vois. Si c'est le cas, votre ClassA doit être groupable. Comme il a mentionné qu'il fonctionne pour les activités, les fragments ou tout objet qui doit sérialiser son état sur un
bundle
6

Je ne sais pas si ma solution est désapprouvée ou non, mais j'utilise un service lié pour conserver l'état ViewModel. Que vous le stockiez en mémoire dans le service ou que vous le conserviez et le récupériez à partir d'une base de données SQLite, cela dépend de vos besoins. C'est ce que font des services de toutes sortes, ils fournissent des services tels que le maintien de l'état de l'application 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, elle est purement un composant de 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 de l'architecture des applications Web utilisent le modèle séculaire Modèle, Vue, Contrôleur (MVC), où la page est la vue, les données de domaine sont le modèle et le contrôleur se trouve derrière un service Web. Le même modèle peut être utilisé dans Android avec la vue étant, eh bien ... la vue, le modèle est vos données de 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-la au démarrage / reprise et dissociez-la à l'arrêt / pause.

Cette approche vous offre l'avantage supplémentaire d'appliquer le principe de conception de la séparation des préoccupations en ce que vous pouvez tous déplacer la logique métier de l'application dans votre service, ce qui réduit la duplication de la logique sur plusieurs vues et permet à la vue d'appliquer un autre principe de conception important, la responsabilité unique.

Entrez
la source
5

Kotlin

Vous devez remplacer onSaveInstanceStateet onRestoreInstanceStatepour stocker et récupérer vos variables que vous souhaitez être persistantes

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
}
Sazzad Hissain Khan
la source
2

Maintenant, Android fournit ViewModels pour enregistrer l'état, vous devriez essayer de l'utiliser au lieu de saveInstanceState.

M Abdul Sami
la source
3
Ce n'est pas vrai. D'après la documentation: "Contrairement à l'état d'instance enregistré, les ViewModels sont détruits lors d'un arrêt de processus initié par le système. C'est pourquoi vous devez utiliser les objets ViewModel en combinaison avec onSaveInstanceState () (ou une autre persistance du disque), en cachant les identifiants dans savedInstanceState pour aider à visualiser les modèles rechargent les données après la mort du système. "
Vyacheslav Martynenko
Je suis juste tombé sur cela avec des autorisations changeant en arrière-plan.
Brill Pappin
Je suis d'accord, à partir du document "si vous devez gérer la mort du processus initié par le système, vous pouvez utiliser onSaveInstanceState () comme sauvegarde."
Zhar
2

Il existe un moyen de faire en sorte qu'Android enregistre les états sans implémenter aucune méthode. Ajoutez simplement cette ligne à votre déclaration de manifeste d'activité:

android:configChanges="orientation|screenSize"

Ça devrait ressembler à ça:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

Ici vous pouvez trouver plus d'informations sur cette propriété.

Il est recommandé de laisser Android gérer cela pour vous plutôt que de le gérer manuellement.

IgniteCoders
la source
2
Cela n'a rien à voir avec la sauvegarde de l'état, vous abandonnez simplement les changements d'orientation, gardez à l'esprit que votre application peut être redémarrée et suspendue et reprise à tout moment pour différents événements
lord-ralf-adolf
1
Cette réponse est pour ceux qui veulent sauver l'état lorsque l'orientation a changé et veulent éviter la compréhension et la mise en œuvre d'une manière complexe
IgniteCoders
assez bien je vois votre point de vue, je pense que la plupart des gens qui luttent pour sauver l'état utilisent des fragments parce que les activités enregistrent réellement les statistiques des composants de l'interface utilisateur tant qu'ils ont un ID, mais les fragments sont plus spéciaux, j'ai utilisé des fragments une fois mais je n'utiliserai jamais à nouveau la stat de sauvegarde de l'instance a été
pénible
ça marche ... merci
Fanadez
1

Que sauver et quoi ne pas faire?

Vous êtes-vous déjà demandé pourquoi le texte du EditTextfichier est enregistré automatiquement lors d'un 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, un changement de configuration). Il essaie de le recréer à l'aide d'un ensemble de données enregistrées de l'ancien état d'activité ( état d'instance ).

L'état d'instance est une collection de paires clé-valeur stockées dans un Bundleobjet.

Par défaut, System enregistre les objets View dans le Bundle par exemple.

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

Si vous avez besoin d'une autre variable à enregistrer en tant que partie de l'état d'instance, vous devez remplacer onSavedInstanceState(Bundle savedinstaneState) méthode.

Par exemple, int currentScore dans une GameActivity

Plus de détails sur onSavedInstanceState (Bundle savedinstaneState) lors de l'enregistrement 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 enregistré.

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

 onCreate(Bundle savedInstanceState)

OU

onRestoreInstanceState(Bundle savedInstanceState)

Les deux méthodes obtiennent le même objet Bundle, donc peu importe où vous écrivez votre logique de restauration. La seule différence est que dans la onCreate(Bundle savedInstanceState)méthode, vous devrez donner une vérification nulle alors qu'elle 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

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

Mais considérez ceci si un utilisateur appuie sur le bouton de retour. Il est supposé que l'utilisateur n'a pas l'intention de revenir à l'activité, dans ce cas, onSaveInstanceState(Bundle savedInstanceState)il ne sera pas invoqué par le système. Il est important de considérer tous les scénarios lors de la sauvegarde des données.

Liens pertinents:

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

Rohit Singh
la source
1

Maintenant, il est logique de faire 2 façons dans le modèle de vue. si vous souhaitez enregistrer la première en tant qu'instance enregistrée: vous pouvez ajouter un paramètre d'état dans le modèle de vue comme celui-ci https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

ou vous pouvez enregistrer des variables ou des objets dans le modèle de vue, dans ce cas, le modèle de vue tiendra le cycle de vie jusqu'à ce que l'activité soit détruite.

public class HelloAndroidViewModel extends ViewModel {
   public Booelan firstInit = false;

    public HelloAndroidViewModel() {
        firstInit = false;
    }
    ...
}

public class HelloAndroid extends Activity {

  private TextView mTextView = null;
  HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    //Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroy
    if(!viewModel.firstInit){
        viewModel.firstInit = true
        mTextView.setText("Welcome to HelloAndroid!");
    }else{
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}
Umut ADALI
la source
vous avez raison, mais cette bibliothèque est toujours en cours de sortie, donc je pense que nous devrions attendre ...
Zhar