Wie speichere ich einen Aktivitätsstatus mit dem Status "Instanz speichern"?


2620

Ich habe an der Android SDK-Plattform gearbeitet und es ist ein wenig unklar, wie der Status einer Anwendung gespeichert werden soll. Angesichts 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ürde für den einfachsten Fall ausreichen, 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 onPauseoder ähnliches, aber ich habe ungefähr 30 Minuten lang in der Dokumentation gestöbert und nichts Offensichtliches gefunden.


9
Wann ist savedInstanceState == null und wann ist es nicht null?
Trojan.ZBOT

90
Sie zerstören Ihre Aktivität explizit, indem Sie - wie Sie sagten - von ihr weg navigieren, z. B. indem Sie zurück drücken. Das Szenario, in dem dieser 'savedInstanceState' verwendet wird, ist, wenn Android Ihre Aktivitäten zur Erholung zerstört. Zur Absicht: Wenn Sie die Sprache Ihres Telefons ändern, während die Aktivität ausgeführt wurde (und daher andere Ressourcen aus Ihrem Projekt geladen werden müssen). Ein weiteres sehr häufiges Szenario ist, wenn Sie Ihr Telefon zur Seite drehen, damit die Aktivität neu erstellt und im Querformat angezeigt wird.
Villoren

16
Um die zweite Nachricht zu erhalten, aktivieren Sie in den Entwicklungsoptionen "Aktivitäten nicht beibehalten". Drücken Sie eine Home-Taste und kehren Sie von den letzten zurück.
Jaroslaw Mytkalyk


6
Sie können es tun mit: onSaveInstanceState (Bundle savedInstanceState)
VahidHoseini

Antworten:


2568

Sie müssen onSaveInstanceState(Bundle savedInstanceState)die Anwendungsstatuswerte, die Sie in den BundleParameter ändern möchten, wie folgt überschreiben und 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 Möglichkeit zum Speichern einer NVP-Zuordnung ("Name-Wert-Paar"). Es wird an onCreate()und dort übergeben, onRestoreInstanceState()wo Sie dann die Werte aus Aktivitäten wie diesen 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");
}

Oder aus einem Fragment.

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
    // Restore UI state from the savedInstanceState.
    // This bundle has also been passed to onCreate.
    boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
    double myDouble = savedInstanceState.getDouble("myDouble");
    int myInt = savedInstanceState.getInt("MyInt");
    String myString = savedInstanceState.getString("MyString");
}

Normalerweise verwenden Sie diese Technik, um Instanzwerte für Ihre Anwendung zu speichern (Auswahl, nicht gespeicherter Text usw.).


24
Gibt es eine Chance, dass dies am Telefon funktioniert, aber nicht im Emulator? Ich kann anscheinend keinen gespeicherten Instanzstatus ungleich Null erhalten.
Adam Jack

491
VORSICHT: Sie müssen super.onSaveInstanceState (savedInstanceState) aufrufen, bevor Sie Ihre Werte zum Bundle hinzufügen. Andernfalls werden sie bei diesem Aufruf gelöscht (Droid X Android 2.2).
jkschneider

121
Achtung: In der offiziellen Dokumentation heißt es, dass Sie wichtige Informationen in der onPause-Methode speichern sollten, da die onsaveinstance-Methode nicht Teil des Android-Lebenszyklus ist. developer.android.com/reference/android/app/Activity.html
schlingel

32
Diese Tatsache macht onSaveInstanceStatepraktisch unbrauchbar, außer bei Änderungen der Bildschirmausrichtung. In fast allen anderen Fällen können Sie sich nie darauf verlassen und müssen Ihren UI-Status manuell an einem anderen Ort speichern. Oder verhindern Sie, dass Ihre App getötet wird, indem Sie das Verhalten der Schaltfläche "Zurück" überschreiben. Ich verstehe nicht, warum sie es überhaupt so implementiert haben. Völlig unintuitiv. Und Sie können dieses Bundle nicht haben, in dem Sie Dinge speichern können, außer in dieser speziellen Methode.
Chakrit

12
Beachten Sie, dass Speichern / Wiederherstellen UI Zustand zu / von dem Bundle wird automatisch erledigt für Views , die zugewiesen ids gewesen sein . Aus den onSaveInstanceStateDokumenten: "Die Standardimplementierung übernimmt für Sie den größten Teil des UI-Status pro Instanz, indem Sie onSaveInstanceState()jede Ansicht in der Hierarchie mit einer ID aufrufen und die ID der aktuell fokussierten Ansicht speichern (die alle wiederhergestellt wird) durch die Standardimplementierung von onRestoreInstanceState(Bundle)) "
Vicky Chijwani

433

Dies savedInstanceStatedient nur zum Speichern des Status, der einer aktuellen Instanz einer Aktivität zugeordnet ist, z. B. aktuelle Navigations- oder Auswahlinformationen. Wenn Android eine Aktivität zerstört und neu erstellt, kann sie wie zuvor wiederhergestellt werden. Siehe die Dokumentation für onCreateundonSaveInstanceState

Für einen langlebigeren Status sollten Sie eine SQLite-Datenbank, eine Datei oder Einstellungen verwenden. Siehe Speichern des dauerhaften Status .


3
Wann ist savedInstanceState == null und wann ist es nicht null?
Trojan.ZBOT

6
savedInstanceState ist null, wenn das System eine neue Instanz Ihrer Aktivität erstellt, und nicht null, wenn es wiederhergestellt wird.
Gabriel Câmara

7
... was die Frage aufwirft, wann das System eine neue Instanz von Aktivität erstellen muss. Einige Möglichkeiten zum Beenden einer App erstellen kein Bundle, daher muss eine neue Instanz erstellt werden. Dies ist das grundlegende Problem; es bedeutet , man nicht kann vertrauen auf Existenz Bündel und müssen einige alternative Mittel zur dauerhaften Speicher tun. Der Vorteil von onSave / onRestoreInstanceState besteht darin, dass es sich um einen Mechanismus handelt, den das System abrupt ausführen kann , ohne viel Systemressourcen zu verbrauchen. Es ist also gut, dies zu unterstützen und über einen dauerhaften Speicher zu verfügen, um die App eleganter zu beenden.
ToolmakerSteve

415

Beachten Sie, dass die Verwendung und für persistente Daten gemäß der Dokumentation zu Aktivitätsstatus unter http://developer.android.com/reference/android/app/Activity.html NICHT sicher ist .onSaveInstanceStateonRestoreInstanceState

In dem Dokument heißt es (im Abschnitt "Aktivitätslebenszyklus"):

Beachten Sie, dass es wichtig ist, persistente Daten zu speichern, onPause()anstatt, onSaveInstanceState(Bundle) da letztere nicht Teil der Lebenszyklus-Rückrufe sind und daher nicht in jeder Situation aufgerufen werden, wie in der Dokumentation beschrieben.

Mit anderen Worten, geben Sie Ihren Sicherungs- / Wiederherstellungscode für persistente Daten in onPause()und ein onResume()!

EDIT : Zur weiteren Verdeutlichung hier die onSaveInstanceState()Dokumentation:

Diese Methode wird aufgerufen, bevor eine Aktivität beendet werden kann, damit sie bei einem späteren Zeitpunkt ihren Status wiederherstellen kann. Wenn beispielsweise Aktivität B vor Aktivität A gestartet wird und Aktivität A irgendwann beendet wird, um Ressourcen zurückzugewinnen, hat Aktivität A die Möglichkeit, den aktuellen Status ihrer Benutzeroberfläche über diese Methode zu speichern, damit der Benutzer bei seiner Rückkehr zurückkehrt Bei Aktivität A kann der Status der Benutzeroberfläche über onCreate(Bundle)oder wiederhergestellt werden onRestoreInstanceState(Bundle).


55
Nur um nicht zu picken: Es ist auch nicht unsicher. Dies hängt nur davon ab, was und wie lange Sie aufbewahren möchten, worüber @Bernard in seiner ursprünglichen Frage nicht ganz klar ist. InstanceState ist perfekt, um den aktuellen UI-Status beizubehalten (in Steuerelemente eingegebene Daten, aktuelle Positionen in Listen usw.), während Pause / Resume die einzige Möglichkeit für eine langfristige dauerhafte Speicherung ist.
Pontus Gagge

30
Dies sollte abgelehnt werden. Es ist nicht sicher, InstanceState wie Lebenszyklusmethoden (Speichern | Wiederherstellen) zu verwenden (dh alles andere in ihnen zu tun, als den Status zu speichern / wiederherzustellen). Sie eignen sich perfekt zum Speichern / Wiederherstellen des Status. Wie möchten Sie den Status in onPause und onResume speichern / wiederherstellen? Sie erhalten keine Bundles in den Methoden, die Sie verwenden können, also müssten Sie eine andere Statusersparnis in Datenbanken, Dateien usw. anwenden, was dumm ist.
Felix

141
Wir sollten diese Person nicht ablehnen, zumindest hat sie sich bemüht, die Dokumentation durchzugehen, und ich denke, wir Menschen sind hier, um tatsächlich eine sachkundige Gemeinschaft aufzubauen und uns gegenseitig zu helfen, nicht abzustimmen. Also stimme ich für die Mühe und ich fordere Sie auf, nicht abzustimmen, sondern abzustimmen oder nicht abzustimmen. Diese Person klärt die Verwirrung, die man beim Durchgehen der Dokumentation haben möchte. 1 Stimme auf :)
AZ_

21
Ich denke nicht, dass diese Antwort eine Ablehnung verdient. Zumindest bemühte er sich zu antworten und hatte einen Abschnitt aus doco zitiert.
GSree

34
Diese Antwort ist absolut richtig und verdient UP-Abstimmung, nicht down! Lassen Sie mich den Unterschied zwischen den Staaten für diejenigen klären, die ihn nicht sehen. Ein GUI-Status wie ausgewählte Optionsfelder und Text im Eingabefeld ist viel weniger wichtig als der Datenstatus, z. B. Datensätze, die einer in einer ListView angezeigten Liste hinzugefügt wurden. Letzteres muss in onPause in der Datenbank gespeichert werden, da dies der einzige garantierte Aufruf ist. Wenn Sie es stattdessen in onSaveInstanceState einfügen, besteht die Gefahr, dass Daten verloren gehen, wenn dies nicht aufgerufen wird. Aber wenn die Auswahl der Optionsfelder aus demselben Grund nicht gespeichert wird, ist das keine große Sache.
JBM

206

Mein Kollege hat einen Artikel erklärt Anwendungszustand auf Android - Geräten , einschließlich Erklärungen auf Aktivität Lifecycle und Zustandsinformationen, wie Statusinformationen zu speichern und zu Speicher in dem Zustand Bundleund SharedPreferencesund einen Blick auf hier nehmen .

Der Artikel behandelt drei Ansätze:

Speichern Sie lokale Variablen- / UI-Steuerdaten für die Anwendungslebensdauer (dh vorübergehend) mithilfe eines Instanzstatuspakets

[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 (dh permanent) mithilfe gemeinsamer Einstellungen

[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 im Speicher zwischen Aktivitäten innerhalb der Anwendungslebensdauer mithilfe einer beibehaltenen Nichtkonfigurationsinstanz am Leben

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

3
@ MartinBelcher-Eigo Artikel sagt über Daten in SharedPreferences, dass "diese Daten in die Datenbank auf dem Gerät geschrieben werden." Ich glaube, dass die Daten in einer Datei im Verzeichnis der App des Dateisystems gespeichert sind.
Tom

2
@ Tom SharefPrefs-Daten werden in eine XML-Datei geschrieben. Ist XML eine Art Datenbank? Ich würde sagen, es ist;)
MaciejGórski

148

Dies ist ein klassisches "Gotcha" der Android-Entwicklung. Hier gibt es zwei Probleme:

  • Es gibt einen subtilen Android Framework-Fehler, der die Verwaltung von Anwendungsstapeln während der Entwicklung erheblich erschwert, zumindest bei älteren Versionen (nicht ganz sicher, ob / wann / wie er behoben wurde). Ich werde diesen Fehler unten diskutieren.
  • Die "normale" oder beabsichtigte Art, dieses Problem zu verwalten, ist selbst ziemlich kompliziert mit der Dualität von onPause / onResume und onSaveInstanceState / onRestoreInstanceState

Beim Durchsuchen all dieser Themen vermute ich, dass Entwickler die meiste Zeit gleichzeitig über diese beiden unterschiedlichen Probleme sprechen ... daher all die Verwirrung und Berichte über "das funktioniert bei mir nicht".

Um das 'beabsichtigte' Verhalten zu verdeutlichen: onSaveInstance und onRestoreInstance sind fragil und nur für den vorübergehenden Zustand. Die beabsichtigte Verwendung (afaict) ist die Wiederherstellung von Aktivitäten, wenn das Telefon gedreht wird (Orientierungsänderung). Mit anderen Worten, die beabsichtigte Verwendung ist, wenn Ihre Aktivität logischerweise immer noch "oben" ist, aber immer noch vom System wiederhergestellt werden muss. Das gespeicherte Bundle wird außerhalb des Prozesses / memory / gc nicht beibehalten, sodass Sie sich nicht wirklich darauf verlassen können, wenn Ihre Aktivität in den Hintergrund tritt. Ja, vielleicht überlebt das Gedächtnis Ihrer Aktivität die Reise in den Hintergrund und entgeht der GC, aber dies ist nicht zuverlässig (und auch nicht vorhersehbar).

Wenn Sie also ein Szenario haben, in dem ein bedeutender Benutzerfortschritt oder -status vorliegt, der zwischen den Starts Ihrer Anwendung beibehalten werden sollte, sollten Sie onPause und onResume verwenden. Sie müssen einen dauerhaften Speicher selbst 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 diesen nachfolgenden Aufruf eine NEUE Aufgabe erstellt. Sie haben effektiv zwei verschiedene Instanzen Ihrer App den gleichen Stapel bewohnen ... was sehr schnell sehr seltsam wird. Dies scheint zu passieren, wenn Sie Ihre App während der Entwicklung starten (z. B. von Eclipse oder Intellij), sodass Entwickler häufig darauf stoßen. Aber auch über einige der App Store-Aktualisierungsmechanismen (so wirkt sich dies auch auf Ihre Benutzer aus).

Ich habe mich stundenlang durch diese Threads gekämpft, bevor mir klar wurde, dass mein Hauptproblem dieser Fehler war, nicht das beabsichtigte Framework-Verhalten. Ein tolles Schreiben undProblemumgehung (UPDATE: siehe unten) scheint von Benutzer @kaciula in dieser Antwort zu stammen:

Verhalten beim Drücken der Home-Taste

UPDATE Juni 2013 : Monate später habe ich endlich die 'richtige' Lösung gefunden. Sie müssen keine statusbehafteten Start-App-Flags selbst verwalten. Sie können dies anhand des Frameworks erkennen und entsprechend sichern. Ich benutze 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;
    }
}

87

onSaveInstanceStatewird aufgerufen, wenn das System Speicher benötigt und eine Anwendung beendet. Es wird nicht aufgerufen, wenn der Benutzer die Anwendung gerade schließt. Daher denke ich, dass der Anwendungsstatus auch in gespeichert werden onPausesollte. Er sollte in einem dauerhaften Speicher wie Preferencesoder gespeichert werdenSqlite


36
Entschuldigung, das ist nicht ganz richtig. onSaveInstanceState wird aufgerufen, bevor die Aktivität erneut ausgeführt werden muss. dh jedes Mal, wenn der Benutzer das Gerät dreht. Es ist zum Speichern vorübergehender Ansichtszustände gedacht. Wenn Android das Schließen der Anwendung erzwingt, wird onSaveInstanceState tatsächlich NICHT aufgerufen (weshalb das Speichern wichtiger Anwendungsdaten nicht sicher ist). onPause wird jedoch garantiert aufgerufen, bevor die Aktivität beendet wird. Daher sollte es verwendet werden, um permanente Informationen in Einstellungen oder Squlite zu speichern. Richtige Antwort, falsche Gründe.
moveaway00

74

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

  1. Der Benutzer beendet die Anwendung und öffnet sie zu einem späteren Zeitpunkt erneut. Die Anwendung muss jedoch die Daten der letzten Sitzung neu laden. Dies erfordert einen dauerhaften Speicheransatz, z. B. die Verwendung von SQLite.
  2. Der Benutzer wechselt die Anwendung und kehrt dann zum Original zurück und möchte dort weitermachen, wo er aufgehört hat. Speichern und Wiederherstellen von Bundle-Daten (z. B. Anwendungsstatusdaten) in onSaveInstanceState()und onRestoreInstanceState()in der Regel ausreichend.

Wenn Sie die Statusdaten dauerhaft speichern, können sie in einem onResume()oder onCreate()(oder tatsächlich in einem beliebigen Lebenszyklusaufruf) neu geladen werden . Dies kann ein gewünschtes Verhalten sein oder auch nicht. Wenn Sie es in einem Bundle in einem speichern InstanceState, ist es vorübergehend und eignet sich nur zum Speichern von Daten zur Verwendung in derselben Benutzersitzung (ich verwende den Begriff Sitzung lose), 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 auszuwählen.


70

Das Speichern des Staates ist für mich bestenfalls ein Kludge. Wenn Sie persistente Daten speichern müssen, 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;

9
Da das Laden einer SQLite-Datenbank zu lange dauert, ist dies der entscheidende Pfad, um dem Benutzer die Benutzeroberfläche der App anzuzeigen. Ich habe es nicht wirklich zeitlich festgelegt, daher bin ich froh, korrigiert zu werden, aber das Laden und Öffnen einer Datenbankdatei wird sicherlich nicht schnell gehen?
Tom

5
Vielen Dank für die Bereitstellung einer Lösung, die ein Neuling ausschneiden und in seine App einfügen und sofort verwenden kann! @Tom Was die Geschwindigkeit betrifft, dauert es ungefähr sieben Sekunden, um 1000 Paare zu speichern, aber Sie können dies in einer AsyncTask tun. Sie müssen jedoch endlich {cursor.close ()} hinzufügen, da dies sonst aufgrund eines Speicherverlusts abstürzt.
Noumenon

3
Ich bin darauf gestoßen, und obwohl es ordentlich erscheint, zögere ich, dies auf Google Glass zu verwenden, dem Gerät, an dem ich in letzter Zeit arbeite.
Stephen Tetreault

61

Ich glaube, ich habe die Antwort gefunden. Lassen Sie mich in 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 zurück zu Aktivität 1, indem ich auf eine Schaltfläche in Aktivität1 klicke. Zu diesem Zeitpunkt wollte ich zu Aktivität2 zurückkehren und meine Aktivität2 in demselben Zustand sehen, in dem ich zuletzt Aktivität2 verlassen habe.

Für das obige Szenario habe ich im Manifest einige Änderungen wie folgt vorgenommen:

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

Und in der Aktivität1 auf dem Button-Klick-Ereignis 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 in Aktivität 2 beim Klicken auf die Schaltfläche habe ich Folgendes getan:

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

Was nun passieren wird, ist, dass alle Änderungen, die wir an der Aktivität2 vorgenommen haben, nicht verloren gehen und wir die Aktivität2 in demselben Zustand anzeigen können, in dem wir sie zuvor verlassen haben.

Ich glaube, das ist die Antwort und das funktioniert gut für mich. Korrigieren Sie mich, wenn ich falsch liege.


2
@bagusflyer möchte genauer sein ??? Ihr Kommentar ist nicht hilfreich und niemand kann Ihnen darauf basierend helfen.
Stephen Tetreault

2
Dies ist eine Antwort auf eine andere Situation: zwei Aktivitäten innerhalb derselben App. Bei OP geht es darum , die App zu verlassen (z. B. Home-Taste oder andere Mittel, um zu einer anderen App zu wechseln).
ToolmakerSteve

44

onSaveInstanceState()für vorübergehende Daten (wiederhergestellt in onCreate()/ onRestoreInstanceState()), onPause()für persistente Daten (wiederhergestellt in onResume()). Aus technischen Ressourcen von Android:

onSaveInstanceState () wird von Android aufgerufen, wenn die Aktivität gestoppt wird und möglicherweise beendet wird, bevor sie wieder aufgenommen wird! Dies bedeutet, dass jeder Status gespeichert werden sollte, der erforderlich ist, um beim Neustart der Aktivität unter denselben Bedingungen neu zu initialisieren. Es ist das Gegenstück zur onCreate () -Methode, und tatsächlich ist das an onCreate () übergebene savedInstanceState-Bundle dasselbe Bundle, das Sie in der onSaveInstanceState () -Methode als outState erstellen.

onPause () und onResume () sind ebenfalls kostenlose Methoden. onPause () wird immer aufgerufen, wenn die Aktivität endet, auch wenn wir dies angestiftet haben (zum Beispiel mit einem Aufruf von finish ()). Wir werden dies verwenden, um die aktuelle Notiz wieder in der Datenbank zu speichern. Es wird empfohlen, alle Ressourcen freizugeben, die auch während einer onPause () freigegeben werden können, um im passiven Zustand weniger Ressourcen zu verbrauchen.


40

Wird wirklich onSaveInstanceState()aufgerufen, wenn die Aktivität in den Hintergrund tritt.

Zitat aus den Dokumenten: "Diese Methode wird aufgerufen, bevor eine Aktivität beendet werden kann, damit sie bei einem späteren Zeitpunkt ihren Status wiederherstellen kann." Quelle


37

Um die Boilerplate zu reduzieren, verwende ich Folgendes interfaceund classlese / schreibe in einen Bundlezum Speichern des Instanzstatus.


Erstellen Sie zunächst eine Schnittstelle, mit der Sie Ihre Instanzvariablen mit Anmerkungen versehen können:

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 die Reflektion verwendet wird, um Werte im Bundle zu speichern:

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

}

Anwendungsbeispiel:

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 namens AndroidAutowire angepasst, das unter der MIT-Lizenz lizenziert ist .


34

Mittlerweile benutze ich generell nichts mehr

Bundle savedInstanceState & Co

Der Lebenszyklus ist für die meisten Aktivitäten zu kompliziert und nicht notwendig.

Und Google sagt selbst, es ist nicht einmal zuverlässig.

Ich speichere alle Änderungen sofort in den Einstellungen:

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

In gewisser Weise funktionieren SharedPreferences ähnlich wie Bundles. Und natürlich und zuerst müssen solche Werte aus Präferenzen gelesen werden.

Bei komplexen Daten können Sie SQLite anstelle von Einstellungen verwenden.

Bei Anwendung dieses Konzepts verwendet die Aktivität weiterhin den zuletzt gespeicherten Status, unabhängig davon, ob es sich um ein anfängliches Öffnen mit dazwischen liegenden Neustarts oder ein erneutes Öffnen aufgrund des Backstacks handelte.


31

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 Statuspaket neu erstellt, wenn:

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

Android zerstört Hintergrundaktivitäten, wenn es unter Speicherdruck steht oder wenn es längere Zeit im Hintergrund war.

Wenn Sie Ihr Hallo-Welt-Beispiel testen, gibt es einige Möglichkeiten, die Aktivität zu verlassen und zurückzukehren.

  • Wenn Sie die Zurück-Taste drücken, ist die Aktivität beendet. Der Neustart der App ist eine brandneue Instanz. Sie werden überhaupt nicht aus dem Hintergrund wieder aufgenommen.
  • Wenn Sie die Home-Taste drücken oder den Task-Umschalter verwenden, tritt die Aktivität in den Hintergrund. Beim Zurücknavigieren zur Anwendung wird onCreate nur aufgerufen, wenn die Aktivität zerstört werden musste.

In den meisten Fällen muss die Aktivität nicht neu erstellt werden, wenn Sie nur auf Home drücken und die App dann erneut starten. Es ist bereits im Speicher vorhanden, sodass onCreate () nicht aufgerufen wird.

Unter Einstellungen -> Entwickleroptionen gibt es eine Option namens "Aktivitäten nicht beibehalten". Wenn es aktiviert ist, zerstört Android immer Aktivitäten und erstellt sie neu, wenn sie im Hintergrund sind. Dies ist eine großartige Option, um sie bei der Entwicklung aktiviert zu lassen, da sie das Worst-Case-Szenario simuliert. (Ein Gerät mit wenig Speicher, das Ihre Aktivitäten ständig recycelt).

Die anderen Antworten sind insofern wertvoll, als sie Ihnen die richtigen Methoden zum Speichern des Status beibringen, aber ich hatte nicht das Gefühl, dass sie wirklich geantwortet haben, WARUM Ihr Code nicht wie erwartet funktioniert hat.


28

Die Methoden onSaveInstanceState(bundle)und onRestoreInstanceState(bundle)sind nützlich für die Datenpersistenz nur beim Drehen des Bildschirms (Orientierungsänderung).
Sie sind nicht einmal gut , während Umschalten zwischen Anwendungen (da die onSaveInstanceState()Methode aufgerufen wird , aber onCreate(bundle)und onRestoreInstanceState(bundle)wird nicht wieder aufgerufen werden .
Für weitere Persistenz Verwendung gemeinsamer Einstellungen. Diesen Artikel lesen


2
In Ihrem Fall onCreateund onRestoreInstanceStatewerden nicht aufgerufen, da das Activitybeim Wechseln von Apps überhaupt nicht zerstört wird, sodass keine Wiederherstellung erforderlich ist. Android ruft onSaveInstanceStatenur für den Fall auf, dass die Aktivität später zerstört wird (was beim Drehen des Bildschirms mit 100% iger Sicherheit geschieht, da sich die gesamte Gerätekonfiguration geändert hat und die Aktivität von Grund auf neu erstellt werden muss).
Vicky Chijwani

20

Mein Problem war, dass ich nur während der Anwendungslebensdauer Persistenz benötigte (dh eine einzelne Ausführung, einschließlich des Startens anderer Unteraktivitäten innerhalb derselben App und des Drehens des Geräts usw.). Ich habe verschiedene Kombinationen der oben genannten 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 benutze das, um den Inhalt meiner Variablen zu erhalten, wenn ich ihn brauchte, wie folgt:

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

Ich benutze onSaveInstanceStateund onRestoreInstanceStatewie oben vorgeschlagen, aber ich denke, ich könnte auch oder alternativ meine Methode verwenden, um die Variable zu speichern, wenn sie sich ändert (zB mit putBoolean)


19

Obwohl die akzeptierte Antwort korrekt ist, gibt es eine schnellere und einfachere Methode, um den Aktivitätsstatus unter Android mithilfe einer Bibliothek namens Icepick zu speichern . 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 dies:

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 Status mit a speichert Bundle.


16

Wenn eine Aktivität erstellt wird, wird die Methode onCreate () aufgerufen.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

savedInstanceState ist ein Objekt der Bundle-Klasse, das zum ersten Mal null ist, aber bei der Neuerstellung Werte enthält. 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
    }

Fügen Sie Ihre Werte in das "outState" -Bündelobjekt wie outState.putString ("key", "Welcome Back") ein und speichern Sie es, indem Sie super aufrufen. Wenn die Aktivität zerstört wird, wird ihr Status im Bundle-Objekt gespeichert und kann nach der Neuerstellung in onCreate () oder onRestoreInstanceState () wiederhergestellt werden. Das in onCreate () und onRestoreInstanceState () empfangene Bundle ist 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");
    }

15

Grundsätzlich gibt es 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. Da es meiner Erfahrung nach dazu führte, dass die Hälfte des Gerätebildschirms beim Drehen vom Hoch- zum Querformat und umgekehrt schwarz wurde.

Mit der oben genannten ersten Methode können wir Daten beibehalten, wenn die Ausrichtung geändert wird oder eine Konfigurationsänderung auftritt. Ich kenne eine Möglichkeit, wie Sie jede Art von Daten innerhalb des Statusobjekts savedInstance speichern können.

Beispiel: Betrachten Sie einen Fall, wenn Sie das Json-Objekt beibehalten möchten. Erstellen Sie eine Modellklasse mit Gettern und Setzern.

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 den Methoden onCreate und onSaveInstanceState die folgenden Schritte 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); 

}

11

Hier ist ein Kommentar aus Steve Moseleys Antwort (von ToolmakerSteve ), der die Dinge relativiert (in der gesamten onSaveInstanceState vs onPause, East Cost vs West Cost Saga).

@VVK - Ich bin teilweise anderer Meinung. Einige Möglichkeiten zum Beenden einer App lösen onSaveInstanceState (oSIS) nicht aus. Dies schränkt den Nutzen von oSIS ein. Es lohnt sich zu unterstützen, wenn nur minimale Betriebssystemressourcen zur Verfügung stehen. Wenn eine App den Benutzer jedoch in den Zustand zurückversetzen möchte, in dem er sich befand, unabhängig davon, wie die App beendet wurde, muss stattdessen ein dauerhafter Speicheransatz verwendet werden. Ich verwende onCreate, um nach Bundles zu suchen. Wenn es fehlt, überprüfe ich den persistenten Speicher. Dies zentralisiert die Entscheidungsfindung. Ich kann mich von einem Absturz erholen oder die Schaltfläche "Zurück" oder den benutzerdefinierten Menüpunkt "Beenden" beenden oder zum Bildschirm zurückkehren. Der Benutzer war viele Tage später eingeschaltet. - ToolmakerSteve 19. September 15 um 10:38 Uhr


10

Kotlin-Code:

speichern:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}

und dann in onCreate()oderonRestoreInstanceState()

    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 haben möchten


9

Um Aktivitätsstatusdaten zu speichern onCreate(), müssen Sie zuerst Daten in savedInstanceState speichern, indem Sie die SaveInstanceState(Bundle savedInstanceState)Methode überschreiben .

Wenn die Aktivitätszerstörungsmethode SaveInstanceState(Bundle savedInstanceState)aufgerufen wird und Sie dort Daten speichern, die Sie speichern möchten. Und Sie erhalten dasselbe, onCreate()wenn die Aktivität neu gestartet wird (savedInstanceState wird nicht null sein, da Sie einige Daten darin gespeichert haben, bevor die Aktivität zerstört wird).


6

Die einfache und schnelle Lösung dieses Problems ist die Verwendung von IcePick

Richten Sie zuerst die Bibliothek in ein app/build.gradle

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

Schauen wir uns nun das folgende Beispiel an, wie der Status in Aktivität gespeichert wird

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

Es funktioniert für Aktivitäten, Fragmente oder alle Objekte, die ihren Status in einem Bundle serialisieren müssen (z. B. ViewPresenters von Mörser).

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
}

1
@ralphspoon ja, es funktioniert für Fragment und benutzerdefinierte Ansicht. Bitte überprüfen Sie den Beispielcode. Ich habe meine Antwort bearbeitet. Ich empfehle Ihnen, die offiziellen Dokumente hier unter github.com/frankiesardo/icepick zu lesen , um weitere Codebeispiele zu finden.
THANN Phearum

@ChetanMehra du meinst benutzerdefinierte Ansichtsklasse, oder? Wenn es sich um eine benutzerdefinierte Ansicht handelt, können wir onSaveInstanceState und onRestoreInstanceState wie im obigen Beispiel für CustomView überschreiben.
THANN Phearum

Ich meine Klassenobjekt innerhalb der Ansichtsklasse zum Beispiel: Klasse CustomView erweitert Ansicht {@State ClassA a;} oder Klasse CustomView erweitert Ansicht {@ State Inner class {}}
Chetan Mehra

@ THANNPhearum Soll ich es als eine andere Frage stellen?
Chetan Mehra

Aha. In diesem Fall sollte Ihre ClassA paketierbar sein. Wie bereits erwähnt, funktioniert es für Aktivitäten, Fragmente oder
andere Objekte

6

Ich bin mir nicht sicher, ob meine Lösung verpönt ist oder nicht, aber ich verwende einen gebundenen Dienst, um den ViewModel-Status beizubehalten. Ob Sie es im Speicher des Dienstes speichern oder beibehalten und aus einer SQLite-Datenbank abrufen, hängt von Ihren Anforderungen ab. Dies ist, was Dienste jeder Art tun, sie bieten Dienste wie das Beibehalten des Anwendungsstatus und die abstrakte allgemeine Geschäftslogik.

Aufgrund von Speicher- und Verarbeitungsbeschränkungen auf Mobilgeräten behandle ich Android-Ansichten ähnlich wie eine Webseite. Die Seite behält nicht den Status bei, sondern ist lediglich eine Präsentationsschichtkomponente, deren einziger Zweck darin besteht, den Anwendungsstatus darzustellen und Benutzereingaben zu akzeptieren. Jüngste Trends in der Web-App-Architektur verwenden das uralte MVC-Muster (Model, View, Controller), wobei die Seite die Ansicht ist, Domänendaten das Modell sind und der Controller sich hinter einem Webdienst befindet. Das gleiche Muster kann in Android verwendet werden, wobei die Ansicht die Ansicht ist, das Modell Ihre Domain-Daten sind und der Controller als Android-gebundener Dienst implementiert ist. Wenn Sie möchten, dass eine Ansicht mit dem Controller interagiert, binden Sie sie beim Starten / Fortsetzen an sie und lösen Sie die Bindung beim Stoppen / Anhalten.

Dieser Ansatz bietet Ihnen den zusätzlichen Vorteil, dass Sie das Prinzip der Trennung von Bedenken durchsetzen können, indem Sie Ihre gesamte Anwendungsgeschäftslogik in Ihren Service verschieben können, wodurch doppelte Logik in mehreren Ansichten reduziert wird und die Ansicht ein weiteres wichtiges Entwurfsprinzip, die Einzelverantwortung, durchsetzen kann.


5

Kotlin

Sie müssen Ihre Variablen überschreiben onSaveInstanceStateund onRestoreInstanceStatespeichern und abrufen, um dauerhaft zu sein

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
}

2

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


3
Das ist nicht wahr. Aus der Dokumentation: "Im Gegensatz zum Status der gespeicherten Instanz werden ViewModels während eines vom System initiierten Prozesstodes zerstört. Aus diesem Grund sollten Sie ViewModel-Objekte in Kombination mit onSaveInstanceState () (oder einer anderen Festplattenpersistenz) verwenden und Bezeichner in savedInstanceState verstauen, um die Anzeige zu erleichtern." Modelle laden die Daten nach dem Systemtod neu. "
Vyacheslav Martynenko

Ich bin gerade darauf gestoßen, wobei sich die Berechtigungen im Hintergrund geändert haben.
Brill Pappin

Ich bin damit einverstanden, dass aus dem Dokument "Wenn Sie den vom System initiierten Prozesstod behandeln müssen, möchten Sie möglicherweise onSaveInstanceState () als Backup verwenden."
Zhar

2

Es gibt eine Möglichkeit, Android dazu zu bringen, die Status zu speichern, ohne eine Methode zu implementieren. Fügen Sie diese Zeile einfach zu Ihrer Manifest in Activity-Erklärung hinzu:

android:configChanges="orientation|screenSize"

Es sollte so aussehen:

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

Hier finden Sie weitere Informationen zu dieser Immobilie.

Es wird empfohlen, dass Android dies für Sie erledigt, als die manuelle Bearbeitung.


2
Dies hat nichts mit dem Speichern des Status zu tun. Sie geben lediglich Änderungen der Ausrichtung auf. Denken Sie daran, dass Ihre App jederzeit für verschiedene Ereignisse neu gestartet, angehalten und fortgesetzt werden kann
Lord-Ralf-Adolf,

1
Diese Antwort ist für diejenigen
gedacht

Ich verstehe Ihren Standpunkt, ich denke, die meisten Leute, die Schwierigkeiten haben, den Status zu retten, verwenden Fragmente, weil Aktivitäten tatsächlich den Status von UI-Komponenten speichern, solange sie eine ID haben, aber Fragmente sind spezieller. Ich habe Fragmente einmal verwendet, aber ich werde sie nie verwenden Bei ihnen war der Save Instance Stat wieder ein
Problem

es funktioniert ... danke
Fanadez

1

Was sparen und was nicht?

Haben Sie sich jemals gefragt, warum der Text im EditTextautomatisch gespeichert wird, wenn 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 neu erstellt (z. B. Konfigurationsänderung). Es wird versucht, es mithilfe einer Reihe gespeicherter Daten des alten Aktivitätsstatus ( Instanzstatus ) neu zu erstellen .

Der Instanzstatus ist eine Sammlung von Schlüssel-Wert- Paaren, die in einem BundleObjekt gespeichert sind .

Standardmäßig speichert das System beispielsweise die Ansichtsobjekte im Bundle.

  • Text in EditText
  • Bildlaufposition in a ListViewusw.

Wenn Sie eine andere Variable als Teil des Instanzstatus speichern möchten, sollten Sie dieonSavedInstanceState(Bundle savedinstaneState) Methode OVERRIDE verwenden.

Zum Beispiel int currentScorein einer GameActivity

Weitere Details zum onSavedInstanceState (Bundle savedinstaneState) 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);
}

Wenn Sie also vergessen, super.onSaveInstanceState(savedInstanceState);das Standardverhalten aufzurufen, funktioniert dies versehentlich nicht, dh Text in EditText wird nicht gespeichert.

Welche Option zum Wiederherstellen des Aktivitätsstatus?

 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 bei der onCreate(Bundle savedInstanceState)Methode eine Nullprüfung durchführen müssen, während diese im letzteren Fall nicht benötigt wird. Andere Antworten haben bereits Code-Schnipsel. Sie können sie verweisen.

Weitere Details zum onRestoreInstanceState (Bundle savedinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

Rufen Sie immer an, super.onRestoreInstanceState(savedInstanceState);damit das System die Ansichtshierarchie standardmäßig wiederherstellt

Bonus

Das onSaveInstanceState(Bundle savedInstanceState)wird vom System nur aufgerufen, wenn der Benutzer beabsichtigt, zur Aktivität zurückzukehren. Zum Beispiel 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, und wird daher in diesem Fall onSaveInstanceState(Bundle savedInstanceState)vom System nicht aufgerufen. Beachten Sie, dass Sie beim Speichern von Daten alle Szenarien berücksichtigen sollten.

Relevante Links:

Demo zum Standardverhalten
Offizielle Android-Dokumentation .


1

Jetzt ist es sinnvoll, im Ansichtsmodell zwei Möglichkeiten zu wählen. Wenn Sie die erste als gespeicherte Instanz speichern möchten: Sie können dem Ansichtsmodell Statusparameter wie diesen hinzufügen: https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

oder Sie können Variablen oder Objekte im Ansichtsmodell speichern. In diesem Fall hält das Ansichtsmodell den Lebenszyklus, bis die Aktivität zerstört wird.

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

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

public class HelloAndroid extends Activity {

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

    mTextView = new TextView(this);

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

    setContentView(mTextView);
  }
}

Sie haben Recht, aber diese Bibliothek ist noch in der Veröffentlichung, also denke ich, wir sollten warten ...
Zhar

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.