Ich habe an der Android SDK-Plattform gearbeitet, und es ist etwas unklar, wie der Status einer Anwendung gespeichert wird. In Anbetracht dieser geringfügigen Überarbeitung des Beispiels "Hallo, 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);
}
}
Ich dachte, es wäre für den einfachsten Fall ausreichend, aber es antwortet immer mit der ersten Nachricht, egal wie ich von der App weg navigiere.
Ich bin mir sicher, dass die Lösung so einfach ist wie das Überschreiben von onPause
oder so ähnlich, aber ich habe mich seit ungefähr 30 Minuten in der Dokumentation versteckt und nichts offensichtliches gefunden.
Sie müssen onSaveInstanceState(Bundle savedInstanceState)
überschreiben und die Anwendungsstatuswerte, die Sie ändern möchten, wie folgt in den Parameter Bundle
schreiben:
@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.
}
Das Bundle ist im Wesentlichen eine Methode zum Speichern einer NVP-Karte ("Name-Value Pair"), und es wird an onCreate()
und auch an onRestoreInstanceState()
übergeben, wo Sie dann die Werte wie folgt extrahieren würden:
@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");
}
Normalerweise verwenden Sie diese Technik, um Instanzwerte für Ihre Anwendung zu speichern (Auswahl, nicht gespeicherter Text usw.).
Beachten Sie, dass es NICHT sicher ist, onSaveInstanceState
und onRestoreInstanceState
für persistente Daten zu verwenden, wie in der Dokumentation zu Aktivitätsstatus angegeben in http://developer.Android.com/reference/Android/app/Activity.html .
Im Dokument heißt es (im Abschnitt "Aktivitätslebenszyklus"):
Beachten Sie, dass es wichtig ist, persistente Daten in
onPause()
anstelle vononSaveInstanceState(Bundle)
zu speichern, da das Letztere nicht Teil der Lebenszyklus-Rückrufe ist und daher nicht in jeder Situation wie in der Dokumentation beschrieben aufgerufen wird.
Mit anderen Worten, setzen Sie Ihren Speicher-/Wiederherstellungscode für persistente Daten in onPause()
und onResume()
!
EDIT: Zur weiteren Verdeutlichung hier die Dokumentation zu onSaveInstanceState()
:
Diese Methode wird aufgerufen, bevor eine Aktivität beendet werden kann, damit sie ihren Zustand wiederherstellen kann, wenn sie zu einem späteren Zeitpunkt erneut auftritt. Wenn beispielsweise die Aktivität B vor der Aktivität A gestartet wird und die Aktivität A zu einem bestimmten Zeitpunkt beendet wird, um Ressourcen zurückzugewinnen, hat die Aktivität A die Möglichkeit, den aktuellen Status ihrer Benutzeroberfläche über diese Methode zu speichern, sodass der Benutzer bei seiner Rückkehr darauf zugreifen kann In Aktivität A kann der Status der Benutzeroberfläche über
onCreate(Bundle)
oderonRestoreInstanceState(Bundle)
wiederhergestellt werden.
Die Variable savedInstanceState
dient nur zum Speichern des Zustands, der einer aktuellen Instanz einer Aktivität zugeordnet ist, z. B. der aktuellen Navigation oder Auswahlinformationen. Wenn also Android eine Aktivität zerstört und neu erstellt, kann sie wie zuvor wiederhergestellt werden. Siehe die Dokumentation für onCreate
und onSaveInstanceState
Für eine längere Lebensdauer sollten Sie die Verwendung einer SQLite-Datenbank, einer Datei oder von Voreinstellungen in Betracht ziehen. Siehe Dauerzustand speichern .
Mein Kollege schrieb einen Artikel, in dem der Anwendungsstatus auf Android-Geräten erläutert wird, einschließlich Erklärungen zum Aktivitätslebenszyklus und Statusinformationen, zum Speichern von Statusinformationen und zum Speichern in den Status Bundle
und SharedPreferences
und hier .
Der Artikel behandelt drei Ansätze:
[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();
}
Dies ist eine klassische "Gotcha" der Android-Entwicklung. Hier gibt es zwei Probleme:
Beim Durchstöbern all dieser Threads vermute ich, dass Entwickler in den meisten Fällen gleichzeitig über diese zwei verschiedenen Themen sprechen ... daher all die Verwirrung und Berichte über "das funktioniert nicht für mich".
Um das 'beabsichtigte' Verhalten zu verdeutlichen, sind zunächst onSaveInstance und onRestoreInstance fragil und nur für vorübergehende Zustände. Die beabsichtigte Verwendung (Affekt) ist die Durchführung der Aktivitätswiederherstellung, wenn das Telefon gedreht wird (Orientierungsänderung). Mit anderen Worten, die beabsichtigte Verwendung ist, wenn Ihre Aktivität immer noch logisch "oben" ist, aber vom System immer wieder neu festgelegt werden muss. Das gespeicherte Bundle wird nicht außerhalb des Prozesses/Speichers/GC gespeichert. Sie können sich also nicht wirklich darauf verlassen, wenn Ihre Aktivitäten in den Hintergrund treten. Ja, das Gedächtnis Ihrer Aktivität überlebt vielleicht die Reise in den Hintergrund und entgeht der GC, dies ist jedoch nicht zuverlässig (noch vorhersehbar).
Wenn Sie also ein Szenario haben, in dem sinnvolle "Benutzerfortschritte" oder Zustände bestehen, die zwischen den "Starts" Ihrer Anwendung bestehen bleiben sollen, sollten Sie onPause und onResume verwenden. Sie müssen selbst einen beständigen Speicher auswählen und vorbereiten.
ABER - es gibt einen sehr verwirrenden Fehler, der all dies kompliziert. Details finden Sie hier:
http://code.google.com/p/Android/issues/detail?id=2373
http://code.google.com/p/Android/issues/detail?id=5277
Wenn Ihre Anwendung mit dem SingleTask-Flag gestartet wird und Sie sie später über den Startbildschirm oder das Startmenü starten, wird durch den folgenden Aufruf eine NEUE Aufgabe erstellt ... Sie haben praktisch zwei verschiedene Instanzen Ihrer App den gleichen Stack bewohnen ... was sehr schnell sehr seltsam wird. Dies scheint zu passieren, wenn Sie Ihre App während der Entwicklung starten (d. H. Von Eclipse oder Intellij). Daher stoßen Entwickler viel darauf. Aber auch durch einige der App Store-Aktualisierungsmechanismen (so wirkt sich dies auch auf Ihre Benutzer aus).
Ich kämpfte stundenlang durch diese Threads, bevor mir klar wurde, dass mein Hauptaugenmerk auf diesen Fehler und nicht auf das beabsichtigte Rahmenverhalten gerichtet war. Ein toller Bericht und workaround (UPDATE: siehe unten) scheint in dieser Antwort vom Benutzer @kaciula zu stammen:
UPDATE Juni 2013: Monate später habe ich endlich die 'richtige' Lösung gefunden. Sie müssen keine stateful-startApp-Flags selbst verwalten, Sie können dies aus dem Framework herausfinden und entsprechend entlassen. Ich verwende dies am Anfang meiner 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
wird aufgerufen, wenn das System Speicher benötigt und eine Anwendung abbricht. Es wird nicht aufgerufen, wenn der Benutzer die Anwendung gerade schließt. Ich denke, der Anwendungsstatus sollte auch in onPause
gespeichert werden. Es sollte in einem permanenten Speicher wie Preferences
oder Sqlite
gespeichert werden
Beide Methoden sind nützlich und gültig und beide eignen sich am besten für unterschiedliche Szenarien:
onSaveInstanceState()
und onRestoreInstanceState()
ist normalerweise ausreichend.Wenn Sie die Statusdaten dauerhaft speichern, können Sie sie in einer onResume()
oder onCreate()
(oder tatsächlich in einem beliebigen Lebenszyklusaufruf) erneut laden. Dies kann ein gewünschtes Verhalten sein oder nicht. Wenn Sie es in einem Bündel in einer InstanceState
speichern, ist es vorübergehend und eignet sich nur zum Speichern von Daten zur Verwendung in derselben Benutzer-Sitzung (ich verwende den Begriff Sitzung locker), jedoch nicht zwischen Sitzungen.
Es ist nicht so, dass ein Ansatz besser ist als der andere, wie alles andere. Es ist nur wichtig zu verstehen, welches Verhalten Sie benötigen, und den am besten geeigneten Ansatz zu wählen.
Der Zustand zu retten ist für mich bestenfalls ein Kludge. Wenn Sie persistente Daten speichern möchten, verwenden Sie einfach eine SQLite -Datenbank. Android macht es SOOO einfach.
Etwas wie das:
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) {
}
}
}
Ein einfacher Anruf danach
dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;
Ich glaube, ich habe die Antwort gefunden. Lassen Sie mich mit einfachen Worten sagen, was ich getan habe:
Angenommen, ich habe zwei Aktivitäten, Aktivität1 und Aktivität2, und ich navigiere von Aktivität1 zu Aktivität2 (Ich habe einige Arbeiten in Aktivität2 ausgeführt) und wieder zu Aktivität 1, indem ich auf eine Schaltfläche in Aktivität1 klicke. Zu diesem Zeitpunkt wollte ich zu Activity2 zurückkehren und möchte meine Activity2 in demselben Zustand sehen, wenn ich Activity2 zuletzt verlassen habe.
Für das obige Szenario habe ich folgende Änderungen vorgenommen:
<activity Android:name=".activity2"
Android:alwaysRetainTaskState="true"
Android:launchMode="singleInstance">
</activity>
Und in der activity1 auf dem Button-Click-Event habe ich folgendes gemacht:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
Und bei activity2 on button click habe ich folgendes gemacht:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Was nun passieren wird, ist, dass die Änderungen, die wir in der Aktivität2 vorgenommen haben, nicht verloren gehen, und wir können Aktivität2 in dem Zustand sehen, den wir zuvor verlassen haben.
Ich glaube, das ist die Antwort und das funktioniert gut für mich. Korrigiere mich, wenn ich falsch liege.
onSaveInstanceState()
für transiente Daten (wiederhergestellt in onCreate()
/onRestoreInstanceState()
), onPause()
für persistente Daten (wiederhergestellt in onResume()
). Von den technischen Ressourcen von Android:
onSaveInstanceState () wird von Android aufgerufen, wenn die Aktivität angehalten wird, und möglicherweise abgebrochen, bevor sie fortgesetzt wird! Das heißt, es sollte alle erforderlichen Zustände speichern, die beim Neustart der Aktivität für die Neuinitialisierung erforderlich sind. Es ist das Gegenstück zur onCreate () -Methode. Tatsächlich ist das gespeicherteInstanceState-Bundle, das an onCreate () übergeben wurde, das gleiche Bundle, das Sie als outState in der onSaveInstanceState () - Methode erstellen.
onPause () und onResume () sind ebenfalls komplementäre Methoden. onPause () wird immer dann aufgerufen, wenn die Aktivität endet, selbst wenn wir (beispielsweise mit einem finish () - Aufruf) diesen Aufruf initiiert haben. Wir werden dies verwenden, um die aktuelle Notiz in der Datenbank zu speichern. Es empfiehlt sich, alle Ressourcen freizugeben, die während einer onPause () -Anweisung freigegeben werden können, um im passiven Zustand weniger Ressourcen zu beanspruchen.
Wirklich onSaveInstance
Status aufgerufen, wenn die Aktivität in den Hintergrund wechselt
Zitat aus den Dokumenten: "Die Methode onSaveInstanceState(Bundle)
wird aufgerufen, bevor die Aktivität in einen solchen Hintergrundzustand versetzt wird."
Um die Boilerplate zu reduzieren, verwende ich die folgenden interface
und class
zum Lesen/Schreiben in eine Bundle
zum Speichern des Instanzzustands.
Erstellen Sie zunächst eine Schnittstelle, die zum Annotieren Ihrer Instanzvariablen verwendet wird:
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 {
}
Erstellen Sie dann eine Klasse, in der Reflection zum Speichern von Werten im Bundle verwendet wird:
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);
}
}
Hinweis: Dieser Code wurde aus einem Bibliotheksprojekt mit dem Namen AndroidAutowire angepasst, das unter der Lizenz MIT lizenziert ist.
Mittlerweile nutze ich im Allgemeinen keinen Gebrauch mehr
Bundle savedInstanceState & Co
Der Lebenszyklus ist für die meisten Aktivitäten zu kompliziert und nicht notwendig.
Und Google erklärt sich selbst, es ist nicht einmal zuverlässig.
Mein Weg ist, alle Änderungen sofort in den Einstellungen zu speichern:
SharedPreferences p;
p.edit().put(..).commit()
In gewisser Weise funktionieren SharedPreferences ähnlich wie Bundles ..__ und natürlich müssen diese Werte zunächst aus den Einstellungen gelesen werden.
Bei komplexen Daten können Sie anstelle von Voreinstellungen SQLite verwenden.
Bei der Anwendung dieses Konzepts verwendet die Aktivität nur den zuletzt gespeicherten Status, unabhängig davon, ob es sich um ein anfängliches Öffnen mit Neustarts dazwischen oder um ein erneutes Öffnen aufgrund des Back-Stacks handelt.
Um die ursprüngliche Frage direkt zu beantworten. savedInstancestate ist null, da Ihre Aktivität niemals neu erstellt wird.
Ihre Aktivität wird nur mit einem Statusbündel neu erstellt, wenn:
Android zerstört Hintergrundaktivitäten, wenn der Speicher unter Druck gerät oder längere Zeit im Hintergrund war.
Beim Testen Ihres Hallo-Welt-Beispiels gibt es einige Möglichkeiten, die Aktivität zu verlassen und wieder zurückzukehren.
In den meisten Fällen muss die Aktivität nicht neu erstellt werden, wenn Sie einfach auf Home drücken und die App erneut starten. Es ist bereits im Speicher vorhanden, so dass onCreate () nicht aufgerufen wird.
Unter Einstellungen -> Entwickleroptionen gibt es eine Option mit dem Namen "Aktivitäten nicht beibehalten". Wenn es aktiviert ist, zerstört Android Aktivitäten und erstellt sie im Hintergrund neu. Dies ist eine großartige Option, die bei der Entwicklung aktiviert bleiben sollte, da das Worst-Case-Szenario simuliert wird. (Ein Gerät mit wenig Speicherplatz, das Ihre Aktivitäten ständig wiederverwendet).
Die anderen Antworten sind insofern wertvoll, als sie Ihnen die richtigen Methoden zum Speichern des Zustands beibringen, aber ich hatte nicht das Gefühl, dass sie wirklich geantwortet haben, WARUM Ihr Code nicht wie erwartet funktioniert.
Die Methoden onSaveInstanceState(bundle)
und onRestoreInstanceState(bundle)
sind für die Datenpersistenz lediglich beim Drehen des Bildschirms (Orientierungsänderung) nützlich.
Sie sind nicht einmal gut, wenn sie zwischen Anwendungen wechseln (da die onSaveInstanceState()
-Methode aufgerufen wird, aber onCreate(bundle)
und onRestoreInstanceState(bundle)
nicht erneut aufgerufen werden.
Verwenden Sie für mehr Persistenz gemeinsame Einstellungen. diesen Artikel lesen
Mein Problem war, dass ich nur während der Anwendungslebensdauer Persistenz brauchte (d. H. Eine einzelne Ausführung, einschließlich des Startens anderer Unteraktivitäten innerhalb derselben App und Drehen des Geräts usw.). Ich habe verschiedene Kombinationen der obigen Antworten ausprobiert, aber nicht in allen Situationen das bekommen, was ich wollte. Am Ende funktionierte es für mich, während onCreate einen Verweis auf den SavedInstanceState zu erhalten:
mySavedInstanceState=savedInstanceState;
und verwenden Sie das, um den Inhalt meiner Variablen zu erhalten, wenn ich es brauchte, wie folgt:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
Ich verwende onSaveInstanceState
und onRestoreInstanceState
wie oben vorgeschlagen, aber ich denke, ich könnte meine Methode auch oder alternativ verwenden, um die Variable zu speichern, wenn sie sich ändert (z. B. putBoolean
).
Obwohl die akzeptierte Antwort korrekt ist, gibt es eine schnellere und einfachere Methode zum Speichern des Aktivitätsstatus auf Android mithilfe einer Bibliothek namens Icepick . Icepick ist ein Anmerkungsprozessor, der sich um den gesamten Boilerplate-Code kümmert, der zum Speichern und Wiederherstellen des Status für Sie verwendet wird.
So etwas mit Icepick machen:
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);
}
}
Ist das Gleiche wie folgt:
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 funktioniert mit jedem Objekt, das seinen Zustand mit einer Bundle
speichert.
Wenn eine Aktivität erstellt wird, wird die onCreate () -Methode aufgerufen.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
savedInstanceState ist ein Objekt der Bundle-Klasse, das zum ersten Mal null ist, aber Werte enthält, wenn es neu erstellt wird. Um den Status der Aktivität zu speichern, müssen Sie onSaveInstanceState () überschreiben.
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("key","Welcome Back")
super.onSaveInstanceState(outState); //save state
}
setzen Sie Ihre Werte in "outState" -Objekt wie "outState.putString" ("key", "Welcome Back") und speichern Sie sie, indem Sie super ..__ aufrufen. Wenn die Aktivität zerstört wird, wird der Status im Bundle-Objekt gespeichert und kann nach der Wiederherstellung wiederhergestellt werden in onCreate () oder onRestoreInstanceState (). Bündel, die in onCreate () und onRestoreInstanceState () empfangen wurden, sind identisch.
@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");
}
}
oder
//restores activity's saved state
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
String restoredMessage=savedInstanceState.getString("key");
}
Es gibt grundsätzlich zwei Möglichkeiten, diese Änderung umzusetzen.
onSaveInstanceState()
und onRestoreInstanceState()
.Android:configChanges="orientation|screenSize"
.Ich empfehle wirklich nicht, die zweite Methode zu verwenden. Denn in einer meiner Erfahrungen verursachte es, dass die Hälfte des Bildschirms schwarz wurde, während er vom Hochformat in das Querformat und umgekehrt gedreht wurde.
Mit der oben erwähnten ersten Methode können wir Daten beibehalten, wenn die Ausrichtung geändert wird oder eine Konfigurationsänderung stattfindet ... Ich kenne eine Möglichkeit, auf die Sie beliebige Datentypen im Zustandsobjekt "saveInstance" speichern können.
Beispiel: Betrachten Sie einen Fall, wenn Sie ein Json-Objekt beibehalten möchten. Eine Modellklasse mit Gettern und Setters erstellen.
class MyModel extends Serializable{
JSONObject obj;
setJsonObject(JsonObject obj)
{
this.obj=obj;
}
JSONObject getJsonObject()
return this.obj;
}
}
Führen Sie nun in Ihrer Aktivität in der Methode onCreate und onSaveInstanceState Folgendes aus. Es wird ungefähr so aussehen:
@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);
}
Hier ist ein Kommentar von Steve Moseley 's Antwort (von ToolmakerSteve), der die Dinge in die richtige Perspektive bringt (im ganzen onSaveInstanceState vs. onPause, East Cost vs. West Cost Saga)
@VVK - Ich stimme teilweise nicht zu. Einige Methoden zum Beenden einer App lösen keine .__ aus. onSaveInstanceState (oSIS). Dies begrenzt den Nutzen von oSIS. Es ist Es lohnt sich zu unterstützen, für minimale OS-Ressourcen, aber wenn eine App den Benutzer in den Zustand zurückversetzen, in dem er sich befand, unabhängig davon, wie die App war Wenn Sie das Programm verlassen, müssen Sie stattdessen einen permanenten Speicheransatz verwenden. Ich verwende onCreate, um nach Bundle zu suchen, und wenn es fehlt, dannDauerspeicher. Dies zentralisiert die Entscheidungsfindung. Ich kann Wiederherstellen nach einem Absturz oder Zurück-Schaltfläche beenden oder benutzerdefinierten Menüeintrag Beenden oder Zurück zum Bildschirmbenutzer war viele Tage später. - ToolmakerSteve Sep 19 '15 um 10:38
Kotlin-Code:
sparen:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState.apply {
putInt("intKey", 1)
putString("stringKey", "String Value")
putParcelable("parcelableKey", parcelableObject)
})
}
und dann in onCreate()
oder 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
Fügen Sie Standardwerte hinzu, wenn Sie keine Optionals verwenden möchten
Einfach, dieses Problem schnell zu lösen, verwenden Sie IcePick
Richten Sie zuerst die Bibliothek in app/build.gradle
ein.
repositories {
maven {url "https://clojars.org/repo/"}
}
dependencies {
compile 'frankiesardo:icepick:3.2.0'
provided 'frankiesardo:icepick-processor:3.2.0'
}
Sehen wir uns dieses Beispiel unten an, um den Status in Activity zu speichern
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);
}
}
Funktioniert für Aktivitäten, Fragmente oder jedes Objekt, das seinen Status für ein Bündel serialisieren muss (z. B. ViewPresenters für Mörtel).
Icepick kann auch den Instanzstatuscode für benutzerdefinierte Ansichten generieren:
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
}
Ich bin nicht sicher, ob meine Lösung missbilligt wird oder nicht, aber ich verwende einen gebundenen Dienst, um den ViewModel-Status beizubehalten. Ob Sie sie im Dienst im Speicher ablegen oder aus einer SQLite-Datenbank abrufen und abrufen, hängt von Ihren Anforderungen ab. Dies ist, was Dienste jeder Art tun, sie bieten Dienste wie die Aufrechterhaltung des Anwendungsstatus und abstrakte allgemeine Geschäftslogik.
Aufgrund von Speicher- und Verarbeitungsbeschränkungen für mobile Geräte behandle ich Android-Ansichten ähnlich wie eine Webseite. Die Seite behält den Status nicht bei, es handelt sich lediglich um eine Präsentationsschichtkomponente, deren einziger Zweck darin besteht, den Anwendungsstatus darzustellen und Benutzereingaben zu akzeptieren. Aktuelle Trends in der Web-App-Architektur verwenden das uralte Modell, View, Controller (MVC) -Muster, bei dem die Seite die View ist, die Domänendaten das Modell sind und der Controller hinter einem Webdienst sitzt. Das gleiche Muster kann in Android verwendet werden, wobei die Ansicht also ... die Ansicht, das Modell Ihre Domain-Daten ist und der Controller als Android-gebundener Dienst implementiert ist. Wann immer Sie möchten, dass eine Ansicht mit dem Controller interagiert, binden Sie beim Starten/Fortsetzen eine Verbindung dazu und bei Stopp/Pause die Bindung auf.
Dieser Ansatz bietet Ihnen den zusätzlichen Vorteil der Durchsetzung des Konstruktionsprinzips "Separation of Concern", da die gesamte Geschäftslogik Ihrer Anwendung in Ihren Service verschoben werden kann, wodurch doppelte Logik in mehreren Ansichten reduziert wird und die Ansicht die Erzwingung eines weiteren wichtigen Konstruktionsprinzips, "Single Responsibility", ermöglicht.
Um Aktivitätsstatusdaten in onCreate()
abzurufen, müssen Sie die Daten zunächst in SavedInstanceState speichern, indem Sie die SaveInstanceState(Bundle savedInstanceState)
-Methode überschreiben.
Wenn die Funktion destroy SaveInstanceState(Bundle savedInstanceState)
aufgerufen wird, werden die Daten gespeichert, die Sie speichern möchten. Und Sie erhalten dasselbe in onCreate()
, wenn die Aktivität erneut gestartet wird.
Jetzt bietet Android ViewModels zum Speichern des Status an, Sie sollten versuchen, dies anstelle von saveInstanceState zu verwenden.
Haben Sie sich schon immer gefragt, warum der Text in der EditText
automatisch gespeichert wird, während sich die Ausrichtung ändert? Nun, diese Antwort ist für Sie.
Wenn eine Instanz einer Aktivität zerstört wird und das System eine neue Instanz erstellt (z. B. Konfigurationsänderung). Es versucht, es mit einem Satz gespeicherter Daten des alten Aktivitätsstatus (Instanzstatus) neu zu erstellen.
Der Instanzstatus ist eine Sammlung von key-value - Paaren, die in einem Bundle
-Objekt gespeichert sind.
Standardmäßig speichert das System die View-Objekte beispielsweise im Bundle.
EditText
ListView
usw.Wenn Sie eine andere Variable als Teil des Instanzstatus speichern möchten, sollten Sie die -METHODEonSavedInstanceState(Bundle savedinstaneState)
überschreiben.
Zum Beispiel int currentScore
in einer 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);
}
Also aus Versehen, wenn Sie vergessen, Anzurufen.
super.onSaveInstanceState(savedInstanceState);
des Standardverhaltens funktioniert nicht, dh Text in EditText wird nicht gespeichert.
onCreate(Bundle savedInstanceState)
ODER
onRestoreInstanceState(Bundle savedInstanceState)
Beide Methoden erhalten dasselbe Bundle-Objekt, sodass es nicht wirklich wichtig ist, wo Sie Ihre Wiederherstellungslogik schreiben. Der einzige Unterschied besteht darin, dass Sie in der onCreate(Bundle savedInstanceState)
-Methode eine Nullprüfung durchführen müssen, während dies im letzteren Fall nicht erforderlich ist. Andere Antworten enthalten bereits Codeausschnitte. Sie können sie verweisen.
@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);
}
Rufen Sie immer
super.onRestoreInstanceState(savedInstanceState);
auf, damit System die Ansichtshierarchie standardmäßig wiederherstellen kann
onSaveInstanceState(Bundle savedInstanceState)
wird vom System nur aufgerufen, wenn der Benutzer zur Aktivität zurückkehren möchte. Beispielsweise verwenden Sie App X und plötzlich erhalten Sie einen Anruf. Sie wechseln zur Anrufer-App und kehren zur App X zurück. In diesem Fall wird die onSaveInstanceState(Bundle savedInstanceState)
-Methode aufgerufen.
Beachten Sie dies jedoch, wenn ein Benutzer die Zurück-Taste drückt. Es wird davon ausgegangen, dass der Benutzer nicht beabsichtigt, zur Aktivität zurückzukehren. In diesem Fall wird onSaveInstanceState(Bundle savedInstanceState)
nicht vom System aufgerufen. Sie sollten alle Szenarien beim Speichern der Daten berücksichtigen.
Demo zum Standardverhalten
Offizielle Android-Dokumentation .
Sie müssen onSaveInstanceState
und onRestoreInstanceState
überschreiben, um Ihre Variablen zu speichern und abzurufen, die persistent sein sollen
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
}
Ich habe eine bessere Idee. Dies ist besser, wenn Sie Ihre Daten speichern, ohne onCreate erneut aufzurufen. Sie können die Aktivität deaktivieren, wenn sich die Ausrichtung ändert.
In Ihrem Manifest:
<activity Android:name=".MainActivity"
Android:configChanges="orientation|screenSize">