Je travaille sur la plate-forme Android SDK, et il est difficile de savoir comment enregistrer l'état d'une application. Donc, étant donné 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, peu importe la façon dont je navigue loin de l'application.
Je suis sûr que la solution est aussi simple que de remplacer onPause
ou quelque chose du genre, mais cela fait 30 minutes que je fouille dans la documentation et je n'ai rien trouvé d'évident.
Vous devez remplacer onSaveInstanceState(Bundle savedInstanceState)
et écrire les valeurs d'état de l'application que vous souhaitez modifier comme 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 pourrez extraire les valeurs suivantes:
@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.).
La savedInstanceState
sert uniquement à enregistrer l'état associé à une instance actuelle d'une activité, par exemple les informations de navigation ou de sélection actuelles. Ainsi, si Android détruit et recrée une activité, elle peut revenir telle qu'elle était auparavant. Voir la documentation de 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 Sauvegarde d'un état persistant .
Notez qu'il est PAS sûr d'utiliser onSaveInstanceState
et onRestoreInstanceState
pour les données persistantes, selon la documentation sur les états d'activité dans http://developer.Android.com /reference/Android/app/Activity.html .
Le document indique (dans la section 'Cycle de vie de l'activité'):
Notez qu'il est important de sauvegarder données persistantes dans
onPause()
à la place deonSaveInstanceState(Bundle)
parce que ce dernier ne fait pas partie de la callbacks de cycle de vie, donc ne sera pas appelé dans chaque situation comme décrit dans sa documentation.
En d'autres termes, placez votre code de sauvegarde/restauration pour les données persistantes dans onPause()
et onResume()
!
EDIT: Pour plus de précisions, voici la documentation de onSaveInstanceState()
:
Cette méthode est appelée avant qu'une activité puisse être tuée de sorte que quand. revient dans le futur, il peut restaurer son état. Pour Par exemple, si l’activité B est lancée devant l’activité A et à un moment donné l'activité ponctuelle A est tuée pour récupérer les ressources, l'activité A aura. une chance de sauvegarder l’état actuel de son interface utilisateur via ceci méthode de sorte que lorsque l'utilisateur retourne à l'activité A, l'état du fichier L’interface utilisateur peut être restaurée via
onCreate(Bundle)
ouonRestoreInstanceState(Bundle)
.
Mon collègue a écrit un article expliquant l'état des applications sur les appareils Android, y compris des explications sur le cycle de vie d'une activité et des informations d'état, comment stocker des informations d'état et enregistrer les états Bundle
et SharedPreferences
et à regarder ici .
L'article couvre trois approches:
[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);
}
[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();
}
[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();
}
Ceci est un «gotcha» classique du développement Android. Il y a deux problèmes ici:
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".
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) 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 celle où votre activité est toujours logiquement «au-dessus», mais doit quand même ê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 au cours du développement (c’est-à-dire à 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 comprendre que mon problème principal était ce bogue, et non le comportement du framework souhaité. Un excellent article et workaround (UPDATE: voir ci-dessous) semble provenir de l'utilisateur @kaciula dans cette réponse:
Comportement de la touche Accueil
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;
}
}
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 doit également être enregistré dans onPause
Il doit être enregistré dans un stockage persistant tel que Preferences
ou Sqlite
Les deux méthodes sont utiles et valides et conviennent parfaitement à différents scénarios:
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 un onResume()
ou un onCreate()
(ou lors d'un appel de cycle de vie). Ce comportement peut ou peut ne pas être souhaité. Si vous la stockez dans une liasse dans une InstanceState
, elle 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 les «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 désirez et de choisir l’approche la plus appropriée.
Sauver l'état est au mieux un bécasse en ce qui me concerne. Si vous avez besoin de sauvegarder 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;
Je pense avoir trouvé la réponse. Permettez-moi de dire ce que j'ai fait avec des mots simples:
Supposons que j’ai deux activités, activité1 et activité2 et que je navigue d’activité1 à activité2 (j’ai effectué quelques travaux dans activité2) puis que je retourne à l’activité 1 en cliquant sur un bouton de l’activité1. Maintenant, à ce stade, je voulais revenir à activity2 et je veux voir mon activité2 dans le même état que la dernière fois que j'ai quitté activity2.
Pour le scénario ci-dessus, ce que j'ai fait est que, dans le manifeste, j'ai apporté des modifications comme celle-ci:
<activity Android:name=".activity2"
Android:alwaysRetainTaskState="true"
Android:launchMode="singleInstance">
</activity>
Et dans le activity1 sur l'événement de clic de bouton, j'ai fait comme ça:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
Et dans activity2 sur l'événement de clic de bouton, je l'ai fait comme ceci:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Maintenant, ce qui va arriver, c’est que, quels que soient les changements que nous avons apportés à l’activité2, ils ne seront pas perdus et que nous pourrons 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 que cela fonctionne bien pour moi. Corrigez-moi si je me trompe.
onSaveInstanceState()
pour les données transitoires (restauré dans onCreate()
/onRestoreInstanceState()
), onPause()
pour les données persistantes (restauré dans onResume()
). À partir des ressources techniques Android:
onSaveInstanceState () est appelé par Android si l'activité est en cours d'arrêt et peut être éliminée avant d'être reprise! Cela signifie qu'il devrait stocker tout état nécessaire pour se réinitialiser dans la même condition lorsque l'activité est redémarrée. Il s'agit de la contrepartie de la méthode onCreate () et 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'initiions (avec un appel finish () par exemple). Nous allons l'utiliser pour enregistrer la note actuelle dans la base de données. Une bonne pratique consiste à libérer toutes les ressources pouvant être libérées au cours d'une opération onPause (), afin de consommer moins de ressources dans l'état passif.
Vraiment onSaveInstance
état appelant lorsque l'activité passe à l'arrière-plan
Citation tirée de la documentation: "La méthode onSaveInstanceState(Bundle)
est appelée avant de placer l'activité dans un tel état d'arrière-plan"
Pour aider à réduire les problèmes, j'utilise les interface
et class
suivants pour lire/écrire dans un Bundle
afin de sauvegarder l'état de l'instance.
Tout d’abord, créez une interface qui sera utilisée pour annoter vos variables d’instance:
import Java.lang.annotation.Documented;
import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import Java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.FIELD
})
public @interface SaveInstance {
}
Ensuite, cré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();
}
}
}
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 .
En attendant, en général, je n’utilise plus
Bundle savedInstanceState & Co
Le cycle de vie est trop compliqué et inutile pour la plupart des activités.
Et Google se dit, ce n'est même pas fiable.
Mon moyen est d’enregistrer immédiatement tout changement 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 Bundles ..__ Et, naturellement et au début, de telles 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.
Lorsque vous appliquez ce concept, l'activité continue simplement 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.
Pour répondre directement à la question initiale. savedInstancestate est null car votre activité n'est jamais recréée.
Votre activité ne sera recréée avec un lot d’états que:
Android détruira les activités en arrière-plan en cas de pression de la mémoire ou après une longue période en arrière-plan.
Lorsque vous testez votre exemple hello world, il existe plusieurs façons de quitter et de revenir à l'activité.
Dans la plupart des cas, si vous appuyez simplement sur la touche d'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. C’est une excellente option à laisser activée lors du développement car elle simule le pire des scénarios. (Un périphérique à faible mémoire recyclant vos activités tout le temps).
Les autres réponses sont précieuses car elles vous apprennent les bonnes manières de stocker l'état, mais je ne pensais pas qu'elles répondaient vraiment POURQUOI votre code ne fonctionnait pas comme prévu.
Les méthodes onSaveInstanceState(bundle)
et onRestoreInstanceState(bundle)
sont utiles pour la persistance des données simplement en faisant pivoter l'écran (changement d'orientation).
Ils ne sont même pas bons lors du basculement entre applications (puisque la méthode onSaveInstanceState()
est appelée mais que onCreate(bundle)
et onRestoreInstanceState(bundle)
ne sont plus invoqués.
Pour plus de persistance, utilisez les préférences partagées. lire cet article
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 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 avais besoin, comme suit:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
J'utilise onSaveInstanceState
et onRestoreInstanceState
comme suggéré ci-dessus, mais je suppose que je pourrais aussi ou alternativement utiliser ma méthode pour enregistrer la variable lorsqu'elle change (par exemple, en utilisant putBoolean
)
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 un Bundle
.
Quand 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, comme outState.putString ("clé", "Welcome Back") et sauvegardez en appelant super . Lorsque l'activité sera détruite, son état sera enregistré dans l'objet Bundle et pourra être restauré après la récré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");
}
Il existe fondamentalement deux manières de mettre en œuvre ce changement.
onSaveInstanceState()
et onRestoreInstanceState()
.Android:configChanges="orientation|screenSize"
.Je ne recommande vraiment pas d'utiliser la deuxième méthode. D'après l'une de mes expériences, la moitié de l'écran de l'appareil était noir lors du basculement d'un portrait à l'autre et inversement.
En utilisant la première méthode mentionnée ci-dessus, nous pouvons conserver les données lorsque l'orientation est modifiée ou en cas de modification de la configuration ..___ Je connais un moyen de stocker tout type de données dans l'objet state savedInstance.
Exemple: si vous souhaitez conserver un objet Json, créez une classe de 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 à quelque chose comme ça:
@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);
}
Voici un commentaire de la réponse de Steve Moseley (par ToolmakerSteve) qui met les choses en perspective (dans l'ensemble onSaveInstanceState vs onPause, saga East Cost vs West Cost
@VVK - Je suis partiellement en désaccord. Certaines manières de quitter une application ne se déclenchent pas onSaveInstanceState (oSIS). Cela limite l'utilité de oSIS. Ses Cela vaut la peine d’être soutenu, pour des ressources minimales du système d’exploitation, mais si une application le souhaite ramène l'utilisateur à l'état dans lequel il se trouvait, quelle que soit l'application à la place, il est nécessaire d’utiliser une approche de stockage persistant à la place . J'utilise onCreate pour rechercher un ensemble. S'il est manquant, cochezstockage persistant. Cela centralise la prise de décision. Je peux récupérer après un plantage, ou quitter le bouton Précédent ou l’élément de menu personnalisé Quitter, ou revenir à l'écran utilisateur était sur plusieurs jours plus tard. - ToolmakerSteve Sep 19 '15 à 10h38
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()
ou onRestoreInstanceState()
val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable
Ajouter des valeurs par défaut si vous ne voulez pas avoir d'options
Simple rapide pour résoudre ce problème utilise IcePick
Commencez par configurer la bibliothèque dans app/build.gradle
repositories {
maven {url "https://clojars.org/repo/"}
}
dependencies {
compile 'frankiesardo:icepick:3.2.0'
provided 'frankiesardo:icepick-processor:3.2.0'
}
Maintenant, 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 devant sérialiser son état sur un paquet (par exemple, ViewPresenters de mortar)
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
}
Pas sûr que ma solution soit mal vue ou non, mais j'utilise un service lié pour conserver l'état ViewModel. Que vous le stockiez en mémoire dans le service ou persistiez et récupériez-le à partir d'une base de données SQLite, cela dépend de vos besoins. C’est ce que font les services de toutes les saveurs, ils fournissent des services tels que la gestion de l’état des applications 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, il s'agit uniquement d'un composant de la 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 en matière d’architecture d’applications Web utilisent le modèle très ancien Modèle, Vue, Contrôleur (MVC), dans lequel la page correspond à la Vue, les données de domaine au modèle, et le contrôleur se cache derrière un service Web. Le même modèle peut être utilisé dans Android, la vue étant, ainsi ... la vue, le modèle correspond aux données de votre 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-le au démarrage/reprise et dissociez-le à l'arrêt/la pause.
Cette approche vous donne l’avantage supplémentaire d’appliquer le principe de conception Séparation de préoccupations en ce sens que toute votre logique métier d’application peut être déplacée vers votre service, ce qui réduit la duplication de plusieurs vues et permet à la vue d’appliquer un autre principe de conception important, la responsabilité unique.
Pour que les données sur l’état de l’activité soient stockées dans onCreate()
, vous devez d’abord enregistrer les données dans savedInstanceState en remplaçant la méthode SaveInstanceState(Bundle savedInstanceState)
.
Lorsque la méthode activity destroy SaveInstanceState(Bundle savedInstanceState)
est appelée et que vous enregistrez les données que vous souhaitez sauvegarder. Et vous obtenez la même chose dans onCreate()
lorsque l'activité redémarre. (SavedInstanceState ne sera pas nul, car vous avez sauvegardé certaines données avant que l'activité ne soit détruite)
Maintenant, Android fournit ViewModels pour sauvegarder l’état, vous devriez essayer de l’utiliser à la place de saveInstanceState.
Vous êtes-vous déjà demandé pourquoi le texte de la EditText
est enregistré automatiquement pendant le 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, modification de la configuration). Il essaie de le recréer en utilisant un ensemble de données sauvegardées de l'ancien état d'activité (instance state).
Instance state est une collection de paires key-value stockées dans un objet Bundle
.
Par défaut, le système enregistre les objets View du lot par exemple.
EditText
ListView
, etc.Si vous avez besoin qu'une autre variable soit sauvegardée en tant qu'élément de l'état de l'instance, vous devez REMPLACER LA M&EACUTE;THODEonSavedInstanceState(Bundle savedinstaneState)
.
Par exemple, int currentScore
dans une GameActivity
@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 sauvegardé.
onCreate(Bundle savedInstanceState)
OU
onRestoreInstanceState(Bundle savedInstanceState)
Les deux méthodes obtiennent le même objet Bundle. Par conséquent, l'endroit où vous écrivez votre logique de restauration n'a pas vraiment d'importance. La seule différence est que, dans la méthode onCreate(Bundle savedInstanceState)
, vous devrez effectuer une vérification nulle alors que cela 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.
@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
onSaveInstanceState(Bundle savedInstanceState)
est appelé par le système uniquement lorsque l'utilisateur a l'intention de revenir à l'activité. Par exemple, vous utilisez App X et vous recevez un appel. Vous passez à l'application appelante et revenez à l'application X. Dans ce cas, la méthode onSaveInstanceState(Bundle savedInstanceState)
sera invoquée.
Mais considérez ceci si un utilisateur appuie sur le bouton retour. Il est supposé que l'utilisateur n'a pas l'intention de revenir à l'activité, c'est pourquoi, dans ce cas, onSaveInstanceState(Bundle savedInstanceState)
ne sera pas appelé par le système.
Démo sur le comportement par défaut
Documentation officielle Android .
Vous devez remplacer onSaveInstanceState
et onRestoreInstanceState
pour stocker et récupérer les variables que vous souhaitez conserver.
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")
}
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
}
J'ai une meilleure idée. Ce serait mieux de sauvegarder vos données sans appeler onCreate. Vous pouvez le désactiver d'activité lorsque l'orientation change.
Dans votre manifeste:
<activity Android:name=".MainActivity"
Android:configChanges="orientation|screenSize">