it-swarm.com.de

Wie kann ein Android-Aktivitätsstatus mithilfe des Status der Instanzspeicherung gespeichert werden?

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.

2359
Bernard

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.).

2377
Reto Meier

Beachten Sie, dass es NICHT sicher ist, onSaveInstanceState und onRestoreInstanceStatefü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 von onSaveInstanceState(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) oder onRestoreInstanceState(Bundle) wiederhergestellt werden.

400
Steve Moseley

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 .

396
Dave L.

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:

Speichern Sie lokale Variablen/UI-Steuerdaten für die Anwendungslebensdauer (d. H. Vorübergehend) unter Verwendung eines Instanzzustandsbündels

[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);
}

Speichern Sie lokale Variablen-/UI-Steuerdaten zwischen Anwendungsinstanzen (d. H. Dauerhaft) mithilfe gemeinsamer Voreinstellungen

[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();
}

Halten von Objektinstanzen zwischen Aktivitäten innerhalb der Anwendungslebensdauer mithilfe einer beibehaltenen Nichtkonfigurationsinstanz im Speicher

[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();
}
180

Dies ist eine klassische "Gotcha" der Android-Entwicklung. Hier gibt es zwei Probleme:

  • Es gibt einen subtilen Android-Framework-Fehler, der das Application-Stack-Management während der Entwicklung erheblich erschwert, zumindest bei älteren Versionen (nicht ganz sicher, ob/wann/wie es behoben wurde). Ich werde diesen Fehler weiter unten besprechen.
  • Der "normale" oder beabsichtigte Weg, dieses Problem zu lösen, ist an sich ziemlich kompliziert mit der Dualität von onPause/onResume und onSaveInstanceState/onRestoreInstanceState

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:

Home Tastendruck Verhalten

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;
    }
}
132
Mike Repass

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

73
Fedor

Beide Methoden sind nützlich und gültig und beide eignen sich am besten für unterschiedliche Szenarien:

  1. Der Benutzer bricht die Anwendung ab und öffnet sie zu einem späteren Zeitpunkt erneut. Die Anwendung muss jedoch Daten aus der letzten Sitzung erneut laden. Dies erfordert einen dauerhaften Speicheransatz, z. B. die Verwendung von SQLite.
  2. Der Benutzer wechselt die Anwendung und kehrt zum Original zurück. Er möchte dort weitermachen, wo er aufgehört hat. Das Speichern und Wiederherstellen von Bündeldaten (z. B. Anwendungsstatusdaten) in 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.

62
David

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;
54
Mike A.

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.

51
roy mathew

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.

37
Ixx

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."

34
u-foka

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();
        }
    }

}

Verwendungsbeispiel:

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.

29
Jared Rummler

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.

29
stefan bachert

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:

  • Konfigurationsänderungen, z. B. Ändern der Ausrichtung oder der Telefonsprache, sodass möglicherweise eine neue Aktivitätsinstanz erstellt wird.
  • Sie kehren aus dem Hintergrund zur App zurück, nachdem das Betriebssystem die Aktivität zerstört hat. 

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.

  • Wenn Sie die Zurück-Taste drücken, ist die Aktivität beendet. Das erneute Starten der App ist eine brandneue Instanz. Sie setzen den Hintergrund überhaupt nicht fort.
  • Wenn Sie die Home-Taste drücken oder den Task-Switcher verwenden, wechselt die Aktivität in den Hintergrund. Wenn Sie zur Anwendung zurück navigieren, wird onCreate nur aufgerufen, wenn die Aktivität zerstört werden muss. 

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.

28
Jared Kells

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

24
Mahorad

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 onSaveInstanceStateund 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).

16
torwalker

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.

15
Kevin Cronly

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");
    }
13
Mansuu....

Es gibt grundsätzlich zwei Möglichkeiten, diese Änderung umzusetzen.

  1. mit onSaveInstanceState() und onRestoreInstanceState().
  2. Im Manifest 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); 

}
12
Krishna

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

8
samis

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

7
Rafols

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
}
5
THANN Phearum

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.

5
ComeIn

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.

5
ascii_walker

Jetzt bietet Android ViewModels zum Speichern des Status an, Sie sollten versuchen, dies anstelle von saveInstanceState zu verwenden.

2
M Abdul Sami

Was zu retten und was nicht?

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.

  • Text in EditText
  • Bildlaufposition in einer 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

Weitere Informationen zum onSavedInstanceState (Bundle gespeichert im stationState) beim Speichern von Daten

@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.

Welches ist für die Wiederherstellung des Aktivitätsstatus zu wählen?

 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.

Weitere Details zum onRestoreInstanceState (Bundle wird im stationateState gespeichert)

@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

Bonus

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.

Relevante Links:

Demo zum Standardverhalten
Offizielle Android-Dokumentation .

0
Rohit Singh

Kotlin

Sie müssen onSaveInstanceState und onRestoreInstanceState überschreiben, um Ihre Variablen zu speichern und abzurufen, die persistent sein sollen

Lebenszyklusdiagramm

Variablen speichern

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

Variablen abrufen

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
}
0

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">
0
Hossein Karami