life Enregistrement de l'état d'activité Android à l'aide de l'option Enregistrer l'état de l'instance




onsaveinstancestate example (18)

Je travaille sur la plate-forme Android SDK et il est difficile de savoir comment enregistrer l'état d'une application. Donc, compte tenu de cette réorganisation mineure de l'exemple 'Hello, Android':

package com.android.hello;

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

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

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

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

https://code.i-harness.com


Ajout de LiveData (composants d'architecture Android) à votre projet

ajouter la dépendance suivante

implementation "android.arch.lifecycle:extensions:1.1.0"

LiveData prend un observateur et l'informe des modifications de données uniquement lorsqu'il est dans l'état STARTED ou RESUMED. L'avantage avec LiveData est que lorsque votre activité va dans un État autre que COMMENCÉ ou REPRENDRE il ne sera pas appeler la onChanged méthode de l' 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);
        }
    });

}

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

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

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

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

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

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

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

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

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

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

Comportement de la touche Home

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

if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}

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

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

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

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


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 sur l’état, la SharedPreferences informations sur l’état, l’enregistrement dans un SharedPreferences état et des SharedPreferences .

L'article couvre trois approches:

Stocker des données de contrôle varible / UI locales pendant la durée de vie de l'application (c.-à-d. Temporairement) à l'aide d'instance State 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);
}

Stocker des données de contrôle varible / UI locales entre les instances d’application (c’est-à-dire de manière 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();
}

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

[Code sample – store object instance]
private cMyClassType moInstanceOfAClass;// Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance() 
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}

Sauver l'état est au mieux un bécasse en ce qui me concerne. Si vous devez enregistrer des données persistantes, utilisez simplement une SQLite 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;

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

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}

Le Bundle est essentiellement un moyen de stocker une carte NVP ("paire paire nom-valeur"). Elle sera transmise à onCreate() et à onRestoreInstanceState() où vous 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 utiliseriez généralement cette technique pour stocker des valeurs d'instance pour votre application (sélections, texte non enregistré, etc.).


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


savedInstanceState sert uniquement à 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 sur 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 Enregistrement d'un état persistant .


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

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


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

Bundle savedInstanceState & Co

le cycle de vie est pour la plupart des activités trop compliqué et pas nécessaire. Et Google se dit, ce n'est même pas fiable.

Mon moyen est d’enregistrer immédiatement les modifications dans les préférences

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

D'une certaine manière, les préférences partagées fonctionnent de la même manière que les offres groupées. Et naturellement et au début, ces valeurs doivent être définies par préférence.

Dans le cas de données complexes, vous pouvez utiliser Sqlite au lieu d’utiliser vos préférences.

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


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 lorsqu'il est recréé. Pour enregistrer l'état de l'activité, vous devez remplacer onSaveInstanceState ().

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

mettez vos valeurs dans un objet "outState" Bundle tel que outState.putString ("key", "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 une recréation dans onCreate () ou onRestoreInstanceState (). Les lots reçus dans onCreate () et onRestoreInstanceState () sont identiques.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

ou

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

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

Faire quelque chose comme ça avec Icepick:

class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

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

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

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

class MainActivity extends Activity {
  String username;
  String password;
  int age;

  @Override
  public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("MyString", username);
    savedInstanceState.putString("MyPassword", password);
    savedInstanceState.putInt("MyAge", age); 
    /* remember you would need to actually initialize these variables before putting it in the
    Bundle */
  }

  @Override
  public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    username = savedInstanceState.getString("MyString");
    password = savedInstanceState.getString("MyPassword");
    age = savedInstanceState.getInt("MyAge");
  }
}

Icepick fonctionnera avec tout objet qui enregistre son état avec a Bundle.


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


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

mySavedInstanceState=savedInstanceState;

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

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 sauvegarder la variable quand elle change (par exemple en utilisant putBoolean)


Pour aider à réduire le risque, j'utilise ce qui suit interfaceet classà lire / écrire dans un état Bundlepour sauvegarder l’instance.

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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD
})
public @interface SaveInstance {

}

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

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

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

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</p>
 */
public class Icicle {

    private static final String TAG = "Icicle";

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #load(Bundle, Object)
     */
    public static void save(Bundle outState, Object classInstance) {
        save(outState, classInstance, classInstance.getClass());
    }

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #load(Bundle, Object, Class)
     */
    public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
        if (outState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    field.setAccessible(true);
                    String key = className + "#" + field.getName();
                    try {
                        Object value = field.get(classInstance);
                        if (value instanceof Parcelable) {
                            outState.putParcelable(key, (Parcelable) value);
                        } else if (value instanceof Serializable) {
                            outState.putSerializable(key, (Serializable) value);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not added to the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #save(Bundle, Object)
     */
    public static void load(Bundle savedInstanceState, Object classInstance) {
        load(savedInstanceState, classInstance, classInstance.getClass());
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #save(Bundle, Object, Class)
     */
    public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
        if (savedInstanceState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    String key = className + "#" + field.getName();
                    field.setAccessible(true);
                    try {
                        Object fieldVal = savedInstanceState.get(key);
                        if (fieldVal != null) {
                            field.set(classInstance, fieldVal);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}

Exemple d'utilisation:

public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

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

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

}

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


Pour que les données sur l’activité soient stockées onCreate(), vous devez d’abord enregistrer les données dans savedInstanceState en surchargeant 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 sauvegarder. Et vous obtenez la même chose onCreate()quand l'activité redémarre.


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

Tout d’abord, installez la bibliothèque dans app/build.gradle

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

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

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

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

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

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

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

class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}

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

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







application-state