Jak zapisać stan aktywności za pomocą stanu zapisu instancji?


2620

Pracuję na platformie Android SDK i jest trochę niejasne, jak zapisać stan aplikacji. Biorąc pod uwagę tę drobną przeróbkę przykładu „Hello, Android”:

package com.android.hello;

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

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

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

    mTextView = new TextView(this);

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

    setContentView(mTextView);
  }
}

Pomyślałem, że to wystarczy w najprostszym przypadku, ale zawsze reaguje na pierwszą wiadomość, bez względu na to, jak opuściłem aplikację.

Jestem pewien, że rozwiązanie jest tak proste, jak zastąpienie onPauselub coś w tym rodzaju, ale od 30 minut zajmuję się dokumentacją i nie znalazłem niczego oczywistego.


9
Kiedy jest zapisaneInstanceState == null, a kiedy nie jest null?
Trojan.ZBOT,

90
Jawnie niszczysz swoją aktywność, - jak powiedziałeś, oddalając się od niej, na przykład naciskając wstecz. W rzeczywistości scenariusz, w którym jest używany ten „saveInstanceState”, ma miejsce, gdy Android niszczy Twoją aktywność w celach rekreacyjnych. Dla intencji: Jeśli zmienisz język telefonu w trakcie działania (a więc należy załadować inne zasoby z projektu). Innym bardzo częstym scenariuszem jest obrócenie telefonu na bok, aby odtworzyć aktywność i wyświetlić ją w układzie poziomym.
villoren

16
Aby otrzymać drugą wiadomość, włącz opcję „Nie przechowuj działań” w opcjach programistycznych. Naciśnij przycisk Home i wróć z ostatnich.
Yaroslav Mytkalyk


6
możesz to zrobić za pomocą: onSaveInstanceState (pakiet saveInstanceState)
VahidHoseini

Odpowiedzi:


2568

Musisz zastąpić onSaveInstanceState(Bundle savedInstanceState)i zapisać wartości stanu aplikacji, które chcesz zmienić, do Bundleparametru w następujący sposób:

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

Pakiet jest zasadniczo sposobem przechowywania mapy NVP („para nazwa-wartość”) i zostanie przekazany do, onCreate()a także onRestoreInstanceState()tam, gdzie następnie wyodrębnisz wartości z działania w następujący sposób:

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

Lub z fragmentu.

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

Zazwyczaj używasz tej techniki do przechowywania wartości instancji dla aplikacji (selekcje, niezapisany tekst itp.).


24
Czy jest jakaś szansa, że ​​zadziała to na telefonie, ale nie w emulatorze? Wydaje się, że nie mogę uzyskać zapisanej wartości innej niż nst .InstanceState.
Adam Jack

491
UWAGA: musisz wywołać super.onSaveInstanceState (zapisanyInstanceState) przed dodaniem wartości do pakietu, w przeciwnym razie zostaną one usunięte z tego połączenia (Droid X Android 2.2).
jkschneider

121
Ostrożnie: oficjalna dokumentacja stwierdza, że ​​należy zapisać ważne informacje w ramach metody onPause, ponieważ metoda onsaveinstance nie jest częścią cyklu życia Androida. developer.android.com/reference/android/app/Activity.html
schlingel

32
Fakt ten skutecznie czyni onSaveInstanceStateprawie bezużytecznym, z wyjątkiem przypadku zmiany orientacji ekranu. W prawie wszystkich innych przypadkach nigdy nie można na nim polegać i konieczne będzie ręczne zapisanie stanu interfejsu użytkownika w innym miejscu. Lub zapobieganie zabiciu aplikacji przez zastąpienie zachowania przycisku WSTECZ. Nie rozumiem, dlaczego w ogóle tak to zaimplementowali. Całkowicie nieintuicyjny. I nie możesz mieć tego Pakietu, w który system da ci zapisywanie rzeczy, z wyjątkiem tej bardzo szczególnej metody.
chakrit

12
Pamiętaj, że zapisywanie / przywracanie stanu interfejsu użytkownika do / z pakietu jest automatycznie obsługiwane dla Views, którym przypisano identyfikatory . Z onSaveInstanceStatedokumentacji: „Domyślna implementacja zajmuje się większością stanu interfejsu użytkownika na instancję, wywołując onSaveInstanceState()każdy widok w hierarchii, który ma identyfikator, i zapisując identyfikator aktualnie aktywnego widoku (wszystkie są przywracane przez domyślną implementację onRestoreInstanceState(Bundle)) ”
Vicky Chijwani

433

Służy savedInstanceStatetylko do zapisywania stanu związanego z bieżącym wystąpieniem działania, na przykład bieżącej nawigacji lub informacji o zaznaczeniu, dzięki czemu jeśli Android zniszczy i odtworzy działanie, może wrócić tak, jak było wcześniej. Zobacz dokumentację dla onCreateionSaveInstanceState

Aby uzyskać bardziej długotrwały stan, rozważ użycie bazy danych SQLite, pliku lub preferencji. Zobacz Zapisywanie trwałego stanu .


3
Kiedy jest zapisaneInstanceState == null, a kiedy nie jest null?
Trojan.ZBOT

6
saveInstanceState ma wartość NULL, gdy system tworzy nową instancję działania, i nie ma wartości NULL podczas przywracania.
Gabriel Câmara

6
... co rodzi pytanie, kiedy system musi utworzyć nową instancję działania. Niektóre sposoby wychodzenia z aplikacji nie tworzą pakietu, dlatego należy utworzyć nowe wystąpienie. To jest podstawowy problem; oznacza to, że nie można polegać na istnieniu pakietu i należy zastosować alternatywne sposoby trwałego przechowywania. Zaletą onSave / onRestoreInstanceState jest to, że jest to mechanizm, który system może zrobić nagle , bez zużywania dużych zasobów systemowych. Warto więc to wspierać, a także mieć trwałe miejsce do przechowywania, aby uzyskać bardziej płynne wyjście z aplikacji.
ToolmakerSteve,

415

Należy pamiętać, że NIE jest bezpieczny w użyciu onSaveInstanceStatei onRestoreInstanceState dla trwałych danych , zgodnie z dokumentacją dotyczącą stanów aktywności w http://developer.android.com/reference/android/app/Activity.html .

Dokument stwierdza (w sekcji „Cykl życia aktywności”):

Należy pamiętać, że ważne jest zapisywanie trwałych danych onPause()zamiast, onSaveInstanceState(Bundle) ponieważ później nie jest częścią wywołań zwrotnych cyklu życia, więc nie będą wywoływane w każdej sytuacji, jak opisano w jego dokumentacji.

Innymi słowy, umieść kod zapisywania / przywracania trwałych danych w onPause()i onResume()!

EDYCJA : Dla dalszego wyjaśnienia, oto onSaveInstanceState()dokumentacja:

Ta metoda jest wywoływana przed zabiciem działania, aby po powrocie w przyszłości mogło przywrócić swój stan. Na przykład, jeśli działanie B zostanie uruchomione przed działaniem A, a w pewnym momencie działanie A zostanie zabite w celu odzyskania zasobów, działanie A będzie miało szansę zapisać bieżący stan swojego interfejsu użytkownika za pomocą tej metody, aby po powrocie użytkownika do działania A można przywrócić stan interfejsu użytkownika za pomocą onCreate(Bundle)lub onRestoreInstanceState(Bundle).


55
Po prostu nitpick: to też nie jest niebezpieczne. Zależy to tylko od tego, co chcesz zachować i na jak długo, czego @Bernard nie jest do końca jasne w swoim pierwotnym pytaniu. InstanceState jest idealny do zachowania bieżącego stanu interfejsu użytkownika (dane wprowadzone do kontrolek, bieżące pozycje na listach itd.), Podczas gdy Pauza / Wznowienie jest jedyną możliwością długoterminowego trwałego przechowywania.
Pontus Gagge

30
Należy to zlekceważyć. Nie jest bezpiecznie używać w InstanceState (Save | Restore) podobnie jak w metodach cyklu życia (tzn. Robić w nich cokolwiek innego niż zapisywanie / przywracanie stanu). Doskonale nadają się do zapisywania / przywracania stanu. Ponadto, jak chcesz zapisać / przywrócić stan w onPause i onResume? Nie dostajesz pakietów w tych metodach, których możesz użyć, więc musiałbyś zastosować jakieś inne zapisujące stan, w bazach danych, plikach itp., Co jest głupie.
Felix

141
Nie powinniśmy głosować z dołu na tę osobę, przynajmniej on dołożył starań, aby przejrzeć dokumentację i myślę, że my tutaj jesteśmy po to, aby faktycznie zbudować kompetentną społeczność i pomagać sobie nawzajem, aby NIE GŁOSOWAĆ. więc oddaj 1 głos za wysiłek, a ja poproszę was, abyście nie głosowali za głosowaniem, raczej głosujcie w górę lub nie głosujcie ... ta osoba usunie zamieszanie, które chcielibyście mieć podczas przeglądania dokumentacji. 1 głos w górę :)
AZ_

21
Nie sądzę, aby ta odpowiedź zasługiwała na głosowanie. Przynajmniej starał się odpowiedzieć i zacytował fragment z doco.
GSree,

34
Ta odpowiedź jest absolutnie poprawna i zasługuje na głosowanie UP, a nie odrzucenie! Pozwól mi wyjaśnić różnicę między stanami dla tych facetów, którzy tego nie widzą. Stan GUI, podobnie jak wybrane przyciski opcji i tekst w polu wejściowym, jest znacznie mniej ważny niż stan danych, podobnie jak rekordy dodane do listy wyświetlanej w ListView. Ten ostatni musi być przechowywany w bazie danych w onPause, ponieważ jest to jedyne gwarantowane połączenie. Jeśli zamiast tego umieścisz go w onSaveInstanceState, ryzykujesz utratą danych, jeśli nie zostanie to wywołane. Ale jeśli wybór przycisku opcji nie zostanie zapisany z tego samego powodu - to nie jest wielka sprawa.
JBM

206

Mój kolega napisał artykuł wyjaśniający stan aplikacji na urządzeniach z Androidem, w tym wyjaśnień dotyczących działalności cyklu i informacji państwowego, sposobu przechowywania informacji o stanie i zapisywanie do stanu Bundlea SharedPreferencesi zapoznać się tutaj .

Artykuł obejmuje trzy podejścia:

Przechowuj zmienne lokalne / dane sterujące interfejsu użytkownika przez cały okres użytkowania aplikacji (tj. Tymczasowo) za pomocą pakietu stanu instancji

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

Przechowuj dane sterujące zmiennej lokalnej / interfejsu użytkownika między instancjami aplikacji (tj. Na stałe) przy użyciu wspólnych preferencji

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

Utrzymywanie żywotności instancji obiektów w pamięci między działaniami w okresie istnienia aplikacji za pomocą zachowanej instancji innej niż konfiguracja

[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 Artykuł mówi o danych w SharedPreferences, że „Te dane są zapisywane w bazie danych na urządzeniu ..” Wierzę, że dane są przechowywane w pliku w katalogu aplikacji systemu plików.
Tom

2
@Tom SharefPrefs dane są zapisywane w pliku xml. Czy xml jest rodzajem bazy danych? Powiedziałbym, że tak;)
MaciejGórski

148

Jest to klasyczna „gotcha” rozwoju Androida. Istnieją tutaj dwa problemy:

  • Istnieje subtelny błąd systemu Android, który znacznie komplikuje zarządzanie stosem aplikacji podczas programowania, przynajmniej w starszych wersjach (nie do końca wiadomo, czy / kiedy / jak to zostało naprawione). Omówię ten błąd poniżej.
  • „Normalny” lub zamierzony sposób zarządzania tym problemem jest sam w sobie dość skomplikowany z dwoistością onPause / onResume i onSaveInstanceState / onRestoreInstanceState

Przeglądając wszystkie te wątki, podejrzewam, że przez większość czasu programiści rozmawiają o tych dwóch różnych problemach jednocześnie ... stąd całe zamieszanie i raporty „to nie działa dla mnie”.

Po pierwsze, aby wyjaśnić „zamierzone” zachowanie: onSaveInstance i onRestoreInstance są delikatne i tylko w stanie przejściowym. Zamierzonym zastosowaniem (afaict) jest obsługa odtwarzania aktywności po obróceniu telefonu (zmiana orientacji). Innymi słowy, zamierzone użycie ma miejsce, gdy działanie jest nadal logicznie „na górze”, ale system musi je ponownie przywrócić. Zapisany pakiet nie jest utrwalany poza procesem / pamięcią / gc, więc nie możesz tak naprawdę polegać na tym, jeśli twoja aktywność przechodzi w tło. Tak, być może pamięć Twojej Aktywności przetrwa podróż do tła i ucieknie z GC, ale nie jest to wiarygodne (ani nie jest przewidywalne).

Więc jeśli masz scenariusz, w którym występuje znaczący „postęp użytkownika” lub stan, który powinien zostać utrwalony między „uruchomieniami” aplikacji, wskazówkami jest użycie onPause i onResume. Musisz sam wybrać i przygotować trwały sklep.

ALE - jest bardzo mylący błąd, który komplikuje to wszystko. Szczegóły są tutaj:

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

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

Zasadniczo, jeśli aplikacja zostanie uruchomiona z flagą SingleTask, a następnie uruchomisz ją z ekranu głównego lub menu uruchamiania, to kolejne wywołanie utworzy NOWE zadanie ... będziesz mieć dwa różne wystąpienia aplikacji zamieszkujących ten sam stos ... co bardzo szybko staje się dziwne. Wydaje się, że dzieje się tak, gdy uruchamiasz aplikację podczas programowania (np. Z Eclipse lub Intellij), więc programiści często się w to angażują. Ale także przez niektóre mechanizmy aktualizacji sklepu z aplikacjami (więc wpływa to również na twoich użytkowników).

Walczyłem przez te wątki przez wiele godzin, zanim zdałem sobie sprawę, że moim głównym problemem był ten błąd, a nie zamierzone zachowanie frameworka. Świetny napis iobejście (AKTUALIZACJA: patrz poniżej) wydaje się pochodzić od użytkownika @kaciula w tej odpowiedzi:

Zachowanie naciśnięcia klawisza Home

AKTUALIZACJA Czerwiec 2013 : Miesiące później w końcu znalazłem „prawidłowe” rozwiązanie. Nie musisz samodzielnie zarządzać stanowymi uruchomionymi flagami aplikacji, możesz to wykryć z frameworka i odpowiednio zwolnić za kaucją. Używam tego na początku mojej 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

onSaveInstanceStatejest wywoływany, gdy system potrzebuje pamięci i zabija aplikację. Nie jest wywoływane, gdy użytkownik zamyka aplikację. Więc myślę, że stan aplikacji powinien być również zapisany w. onPausePowinien zostać zapisany w pamięci trwałej, takiej jak PreferenceslubSqlite


36
Przepraszam, to nie do końca prawda. onSaveInstanceState zostaje wywołany przed ponownym wykonaniem działania. tzn. za każdym razem, gdy użytkownik obraca urządzenie. Służy do przechowywania przejściowych stanów widoku. Kiedy Android zmusza aplikację do zamknięcia, onSaveInstanceState tak naprawdę NIE jest wywoływany (dlatego jest niebezpieczny do przechowywania ważnych danych aplikacji). onPause jest jednak gwarantowane, że zostanie wywołany przed zabiciem działania, dlatego należy go używać do przechowywania trwałych informacji w preferencjach lub Squlite. Prawidłowa odpowiedź, złe powody.
moveaway00

74

Obie metody są przydatne i poprawne, a obie najlepiej nadają się do różnych scenariuszy:

  1. Użytkownik zamyka aplikację i ponownie ją otwiera w późniejszym terminie, ale aplikacja musi ponownie załadować dane z ostatniej sesji - wymaga to trwałego podejścia do przechowywania, takiego jak używanie SQLite.
  2. Użytkownik przełącza aplikację, a następnie wraca do oryginału i chce odebrać tam, gdzie ją przerwał - zapisywać i przywracać dane pakietu (takie jak dane stanu aplikacji) onSaveInstanceState()i onRestoreInstanceState()zwykle jest odpowiedni.

Jeśli dane stanu zostaną zapisane w sposób trwały, można je ponownie załadować w trybie onResume()lub onCreate()(lub w dowolnym wywołaniu cyklu życia). To może być pożądane zachowanie. Jeśli przechowujesz go w pakiecie w InstanceState, wówczas jest przejściowy i nadaje się tylko do przechowywania danych do użycia w tej samej „sesji” użytkownika (używam terminu sesja luźno), ale nie między „sesjami”.

Nie chodzi o to, że jedno podejście jest lepsze od drugiego, podobnie jak wszystko, ważne jest, aby zrozumieć, jakiego zachowania potrzebujesz i wybrać najbardziej odpowiednie podejście.


70

Moim zdaniem stan oszczędzania jest co najwyżej kludge. Jeśli chcesz zapisać trwałe dane, po prostu skorzystaj z bazy danych SQLite . Android ułatwia SOOO .

Coś takiego:

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

Po tym proste połączenie

dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;

9
Ponieważ ładowanie bazy danych SQLite trwa zbyt długo, biorąc pod uwagę, że jest to ścieżka krytyczna do pokazania użytkownikowi interfejsu użytkownika aplikacji. Właściwie to nie mierzyłem czasu, więc cieszę się, że mogę to poprawić, ale na pewno ładowanie i otwieranie pliku bazy danych nie będzie szybkie?
Tom

5
Dziękujemy bardzo za udostępnienie rozwiązania, które nowicjusz może wyciąć i wkleić do swojej aplikacji i używać od razu! @Tom Jeśli chodzi o prędkość, zapisanie 1000 par zajmuje około siedmiu sekund, ale możesz to zrobić w AsyncTask. Musisz jednak dodać w końcu {kursor.close ()}, ponieważ spowoduje to awarię pamięci z wycieku.
Noumenon

3
Natknąłem się na to i choć wydaje się to fajne, waham się przed użyciem tego w Google Glass, na którym ostatnio pracuję.
Stephen Tetreault

61

Myślę, że znalazłem odpowiedź. Pozwól mi powiedzieć, co zrobiłem prostymi słowami:

Załóżmy, że mam dwie aktywności, aktywność 1 i aktywność 2 i przechodzę od aktywności 1 do aktywności 2 (wykonałem kilka prac w aktywności 2) i ponownie z powrotem do aktywności 1, klikając przycisk w aktywności 1. Teraz na tym etapie chciałem wrócić do aktywności 2 i chcę zobaczyć moją aktywność 2 w tym samym stanie, kiedy po raz ostatni opuściłem aktywność 2.

W powyższym scenariuszu zrobiłem to, że w manifeście wprowadziłem kilka takich zmian:

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

I w działaniu 1 na zdarzeniu kliknięcia przycisku zrobiłem tak:

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);

I w action2 na zdarzeniu kliknięcia przycisku zrobiłem tak:

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

Teraz stanie się to, że cokolwiek zmiany, które wprowadziliśmy w działaniu 2, nie zostaną utracone, i możemy oglądać działanie 2 w takim samym stanie, jak poprzednio.

Wierzę, że to jest odpowiedź i działa mi dobrze. Popraw mnie, jeśli się mylę.


2
@bagusflyer care, aby być bardziej szczegółowym ??? Twój komentarz nie jest pomocny i nikt nie może ci pomóc w oparciu o to.
Stephen Tetreault

2
To odpowiedź na inną sytuację: dwie czynności w ramach tej samej aplikacji. OP polega na opuszczeniu aplikacji (np. Przycisk Home lub inny sposób na przejście do innej aplikacji).
ToolmakerSteve

44

onSaveInstanceState()dla danych przejściowych (przywrócone w onCreate()/ onRestoreInstanceState()), onPause()dla trwałych danych (przywrócone w onResume()). Z zasobów technicznych Androida:

onSaveInstanceState () jest wywoływany przez Androida, jeśli działanie jest zatrzymywane i może zostać zabite przed wznowieniem! Oznacza to, że powinien zachować każdy stan niezbędny do ponownej inicjalizacji do tego samego stanu po ponownym uruchomieniu działania. Jest to odpowiednik metody onCreate (), a tak naprawdę pakiet saveInstanceState przekazany do onCreate () jest tym samym pakietem, który tworzysz jako outState w metodzie onSaveInstanceState ().

onPause () i onResume () są również metodami komplementarnymi. Funkcja onPause () jest zawsze wywoływana po zakończeniu działania, nawet jeśli zainicjowaliśmy to (na przykład wywołaniem kończącym ()). Użyjemy tego, aby zapisać bieżącą notatkę z powrotem w bazie danych. Dobrą praktyką jest zwolnienie wszelkich zasobów, które można zwolnić również podczas działania onPause (), aby zużywać mniej zasobów w stanie pasywnym.


40

Naprawdę onSaveInstanceState()jest wywoływany, gdy działanie przechodzi do tła.

Cytat z dokumentów: „Ta metoda jest wywoływana, zanim dana czynność może zostać zabita, aby po powrocie w przyszłości mogła przywrócić jej stan”. Źródło


37

Aby zredukować płytę kotłową, korzystam z poniższych interfacei classdo odczytu / zapisu w Bundlecelu zapisania stanu instancji.


Najpierw utwórz interfejs, który będzie używany do opisywania zmiennych instancji:

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 {

}

Następnie utwórz klasę, w której odbicie będzie używane do zapisywania wartości w pakiecie:

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

}

Przykładowe użycie:

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

}

Uwaga: ten kod został zaadaptowany z projektu biblioteki o nazwie AndroidAutowire, który jest licencjonowany na licencji MIT .


34

Tymczasem generalnie już nie używam

Bundle savedInstanceState & Co

Cykl życia jest w przypadku większości czynności zbyt skomplikowany i niepotrzebny.

I sam Google stwierdza, że ​​NIE jest nawet wiarygodny.

Moim sposobem jest natychmiastowe zapisanie zmian w preferencjach:

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

W pewien sposób SharedPreferencje działają podobnie jak pakiety. Naturalnie i na początku takie wartości należy odczytać z preferencji.

W przypadku złożonych danych możesz użyć SQLite zamiast preferencji.

Podczas stosowania tej koncepcji działanie po prostu nadal używa ostatniego zapisanego stanu, niezależnie od tego, czy było to początkowe otwarcie z ponownym uruchomieniem pomiędzy, czy ponowne otwarcie z powodu tylnego stosu.


31

Aby bezpośrednio odpowiedzieć na oryginalne pytanie. saveInstancestate ma wartość NULL, ponieważ Twoja aktywność nigdy nie jest odtwarzana ponownie.

Twoja aktywność zostanie odtworzona z pakietem stanu tylko wtedy, gdy:

  • Zmiany w konfiguracji, takie jak zmiana orientacji lub języka telefonu, które mogą wymagać utworzenia nowej instancji działania.
  • Powrócisz do aplikacji z tła po tym, jak system operacyjny zniszczy aktywność.

Android niszczy działania w tle, gdy są pod presją pamięci lub po dłuższym przebywaniu w tle.

Podczas testowania przykładu z Witaj świecie istnieje kilka sposobów na opuszczenie i powrót do działania.

  • Po naciśnięciu przycisku Wstecz aktywność jest zakończona. Ponowne uruchomienie aplikacji to zupełnie nowa instancja. W ogóle nie wracasz z tła.
  • Gdy naciśniesz przycisk strony domowej lub użyjesz przełącznika zadań, aktywność przejdzie w tło. Podczas powrotu do aplikacji onCreate zostanie wywołany tylko wtedy, gdy działanie musiało zostać zniszczone.

W większości przypadków, jeśli tylko naciskasz przycisk Home, a następnie ponownie uruchamiasz aplikację, nie trzeba będzie ponownie tworzyć aktywności. Istnieje już w pamięci, więc funkcja onCreate () nie zostanie wywołana.

W obszarze Ustawienia -> Opcje programisty dostępna jest opcja o nazwie „Nie zachowuj aktywności”. Po włączeniu Android zawsze niszczy działania i odtwarza je, gdy są w tle. Jest to świetna opcja, aby pozostawić włączone podczas opracowywania, ponieważ symuluje najgorszy scenariusz. (Urządzenie z małą ilością pamięci stale przetwarza twoje działania).

Inne odpowiedzi są cenne, ponieważ uczą cię prawidłowych sposobów przechowywania stanu, ale nie czułem, że tak naprawdę odpowiedzieli DLACZEGO twój kod nie działał w oczekiwany sposób.


28

onSaveInstanceState(bundle)I onRestoreInstanceState(bundle)metody są użyteczne dla utrzymywania danych tylko podczas obracania ekranu (zmiana orientacji).
Oni nie są nawet dobre podczas przełączania między aplikacjami (ponieważ onSaveInstanceState()metoda nazywa ale onCreate(bundle)i onRestoreInstanceState(bundle)nie jest ponownie wywoływany.
Aby uzyskać więcej ruchu utrzymywanie wspólnych upodobań. Przeczytaj ten artykuł


2
W twoim przypadku onCreatei onRestoreInstanceStatenie są nazywane, ponieważ Activitynie jest w ogóle zniszczona podczas przełączania aplikacji, więc nie ma potrzeby, aby przywrócić wszystko. Android dzwoni onSaveInstanceStatena wypadek, gdyby Aktywność uległa później zniszczeniu (co dzieje się ze 100% pewnością podczas obracania ekranu, ponieważ zmieniła się cała konfiguracja urządzenia i Aktywność musi zostać odtworzona od nowa).
Vicky Chijwani

20

Mój problem polegał na tym, że potrzebowałem trwałości tylko podczas życia aplikacji (tj. Pojedynczego wykonania, w tym uruchomienia innych poddziałań w ramach tej samej aplikacji i obracania urządzenia itp.). Próbowałem różnych kombinacji powyższych odpowiedzi, ale nie uzyskałem tego, czego chciałem we wszystkich sytuacjach. Ostatecznie udało mi się uzyskać odniesienie do saveInstanceState podczas onCreate:

mySavedInstanceState=savedInstanceState;

i użyj tego, aby uzyskać zawartość mojej zmiennej, gdy jej potrzebuję, zgodnie z następującymi zasadami:

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

Używam onSaveInstanceStatei onRestoreInstanceStatejak sugerowałem powyżej, ale myślę, że mógłbym lub alternatywnie użyć mojej metody, aby zapisać zmienną, gdy się zmienia (np. Używając putBoolean)


19

Chociaż zaakceptowana odpowiedź jest prawidłowa, istnieje szybsza i łatwiejsza metoda zapisania stanu aktywności na Androidzie za pomocą biblioteki o nazwie Icepick . Icepick to procesor adnotacji, który dba o cały kod płyty wzorcowej używany do zapisywania i przywracania dla ciebie stanu.

Robienie czegoś takiego z Icepick:

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

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

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

Czy to samo, co robienie tego:

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 będzie działał z każdym obiektem, który zapisuje swój stan za pomocą Bundle.


16

Po utworzeniu działania wywoływana jest jego metoda onCreate ().

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

saveInstanceState to obiekt klasy pakietu, który po raz pierwszy ma wartość NULL, ale zawiera wartości podczas odtwarzania. Aby zapisać stan działania, musisz zastąpić onSaveInstanceState ().

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

umieść swoje wartości w obiekcie pakietu „outState”, takim jak outState.putString („klucz”, „Welcome Back”) i zapisz, wywołując super. Kiedy aktywność zostanie zniszczona, jej stan zostanie zapisany w obiekcie pakietu i może zostać przywrócony po odtworzeniu w onCreate () lub onRestoreInstanceState (). Pakiet odebrany w onCreate () i onRestoreInstanceState () są takie same.

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

lub

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

15

Istnieją zasadniczo dwa sposoby wprowadzenia tej zmiany.

  1. za pomocą onSaveInstanceState()i onRestoreInstanceState().
  2. W manifestie android:configChanges="orientation|screenSize".

Naprawdę nie polecam używać drugiej metody. Ponieważ w jednym z moich doświadczeń powodowało to, że połowa ekranu urządzenia była czarna podczas obracania z portretu na krajobraz i odwrotnie.

Używając pierwszej metody wspomnianej powyżej, możemy utrwalić dane po zmianie orientacji lub każdej zmianie konfiguracji. Znam sposób, w jaki możesz przechowywać dowolny typ danych w obiekcie stanu saveInstance.

Przykład: Rozważ przypadek, jeśli chcesz utrwalić obiekt Json. utwórz klasę modelu z getterami i setterami.

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

Teraz w swojej aktywności w metodach onCreate i onSaveInstanceState wykonaj następujące czynności. Będzie to wyglądać mniej więcej tak:

@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

Oto komentarz z odpowiedzi Steve'a Moseleya (autorstwa ToolmakerSteve ), który przedstawia rzeczy w perspektywie (w całości onSaveInstanceState vs onPause, koszt wschodni vs saga zachodni)

@VVK - częściowo się nie zgadzam. Niektóre sposoby wychodzenia z aplikacji nie uruchamiają onSaveInstanceState (oSIS). Ogranicza to użyteczność oSIS. Warto wspierać, dla minimalnych zasobów systemu operacyjnego, ale jeśli aplikacja chce przywrócić użytkownika do stanu, w którym się znajdował, bez względu na to, jak aplikacja została zamknięta, konieczne jest zastosowanie metody trwałego przechowywania. Używam onCreate, aby sprawdzić pakiet, a jeśli go brakuje, sprawdź trwałe miejsce do przechowywania. Centralizuje to podejmowanie decyzji. Mogę wyzdrowieć po awarii, wyjściu z przycisku Wstecz lub niestandardowym elemencie menu Wyjdź, albo wrócić do ekranu, w którym użytkownik był wiele dni później. - ToolmakerSteve, 19 września 15 o 10:38


10

Kod Kotlin:

zapisać:

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

a następnie w onCreate()lubonRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

Dodaj wartości domyślne, jeśli nie chcesz mieć Opcjonalnych


9

Aby uzyskać dane stanu aktywności zapisane w onCreate(), najpierw musisz zapisać dane w saveInstanceState metodą przesłonięcia SaveInstanceState(Bundle savedInstanceState).

Gdy SaveInstanceState(Bundle savedInstanceState)zostanie wywołana metoda niszczenia aktywności i tam zapisujesz dane, które chcesz zapisać. I to samo onCreate()dzieje się, gdy aktywność zostanie ponownie uruchomiona. (SaveInstanceState nie będzie miało wartości zerowej, ponieważ zapisałeś w niej dane przed zniszczeniem aktywności)


6

Prostym szybkim rozwiązaniem tego problemu jest użycie IcePick

Najpierw skonfiguruj bibliotekę w app/build.gradle

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

Teraz sprawdźmy ten przykład poniżej, jak zapisać stan w Aktywności

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

Działa dla działań, fragmentów lub dowolnego obiektu, który musi szeregować swój stan w pakiecie (np. ViewPresenters zaprawy)

Icepick może również generować kod stanu instancji dla niestandardowych widoków:

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 tak, działa w przypadku fragmentów i widoku niestandardowego. Sprawdź przykładowy kod. Zredagowałem swoją odpowiedź. Sugeruję, aby przejść do oficjalnej dokumentacji tutaj github.com/frankiesardo/icepick, aby znaleźć więcej próbek kodu.
Niż Phearum

@ChetanMehra masz na myśli niestandardową klasę widoku, prawda? Jeśli jest to widok niestandardowy, możemy zastąpić onSaveInstanceState i onRestoreInstanceState, jak powyższy przykład CustomView.
Niż Phearum

Mam na myśli obiekt klasy wewnątrz klasy widoku na przykład: klasa CustomView rozszerza widok {@State KlasaA a;} lub klasa CustomView rozszerza widok {@ Stan Klasa wewnętrzna {}}
Chetan Mehra

@THANNPhearum Czy powinienem zadać to jako inne pytanie?
Chetan Mehra

Widzę. Jeśli tak, twoja klasa A powinna być paczkowa. Jak wspomniano, że działa dla działań, fragmentów lub dowolnego obiektu, który musi zserializować swój stan w pakiecie
THANN Phearum

6

Nie jestem pewien, czy moje rozwiązanie jest rozczarowane, czy nie, ale używam usługi powiązanej, aby utrzymać stan ViewModel. To, czy przechowujesz go w pamięci w usłudze, czy utrwalasz i pobierasz z bazy danych SQLite, zależy od twoich wymagań. To właśnie robią usługi dowolnego rodzaju, świadczą usługi takie jak utrzymanie stanu aplikacji i abstrakcyjna wspólna logika biznesowa.

Ze względu na ograniczenia związane z pamięcią i przetwarzaniem związane z urządzeniami mobilnymi, widoki systemu Android traktuję podobnie jak stronę internetową. Strona nie utrzymuje stanu, jest jedynie składnikiem warstwy prezentacji, którego jedynym celem jest prezentacja stanu aplikacji i akceptacja danych wprowadzonych przez użytkownika. Najnowsze trendy w architekturze aplikacji internetowych wykorzystują odwieczny wzorzec Model, Widok, Kontroler (MVC), gdzie strona to Widok, dane domeny to model, a kontroler siedzi za usługą internetową. Ten sam wzorzec może być zastosowany w Androidzie z Widokiem, cóż ... Widokem, modelem są dane twojej domeny, a Kontroler jest implementowany jako usługa powiązana z Androidem. Ilekroć chcesz, aby widok współdziałał z kontrolerem, powiąż go przy uruchamianiu / wznawianiu i rozpinaj przy zatrzymaniu / pauzie.

Takie podejście daje dodatkową korzyść z egzekwowania zasady projektowania separacji obaw, ponieważ logika biznesowa aplikacji może zostać przeniesiona do Twojej usługi, co zmniejsza zduplikowaną logikę w wielu widokach i pozwala na wymuszenie kolejnej ważnej zasady projektowania, pojedynczej odpowiedzialności.


5

Kotlin

Musisz zastąpić onSaveInstanceStateionRestoreInstanceState przechowywać i odzyskiwać zmienne, które chcesz zachować

Wykres cyklu życia

Przechowuj zmienne

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

Pobierz zmienne

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

Teraz Android zapewnia ViewModels do zapisywania stanu, powinieneś spróbować użyć tego zamiast saveInstanceState.


3
To nie jest prawda. Z dokumentacji: „W przeciwieństwie do stanu zapisanej instancji, ViewModels są niszczone podczas inicjowanej przez system śmierci procesowej. Dlatego należy używać obiektów ViewModel w połączeniu z onSaveInstanceState () (lub inną trwałością dysku), ukrywanie identyfikatorów w saveInstanceState, aby ułatwić przeglądanie modele ponownie ładują dane po śmierci systemu. ”
Wiaczesław Martynenko

Właśnie wpadłem na to ze zmieniającymi się uprawnieniami w tle.
Brill Pappin,

Zgadzam się, z dokumentu „jeśli potrzebujesz obsługi śmierci procesowej zainicjowanej przez system, możesz użyć onSaveInstanceState () jako kopii zapasowej”.
Zhar

2

Istnieje sposób, aby Android zapisał stany bez implementacji żadnej metody. Po prostu dodaj ten wiersz do deklaracji Manifest in Activity:

android:configChanges="orientation|screenSize"

To powinno wyglądać tak:

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

Tutaj możesz znaleźć więcej informacji o tej nieruchomości.

Zaleca się, aby system Android zajął się tym za Ciebie niż obsługa ręczna.


2
Nie ma to nic wspólnego z ratowaniem stanu, po prostu rezygnujesz ze zmian orientacji, pamiętaj, że aplikację można w dowolnym momencie zrestartować, zatrzymać i wznowić dla różnych wydarzeń
lord-ralf-adolf

1
Ta odpowiedź jest dla tych, którzy chcą ocalić stan po zmianie orientacji i chcą uniknąć zrozumienia i wdrożenia złożonego sposobu
IgniteCoders

całkiem słuszne rozumiem twój punkt widzenia, myślę, że większość ludzi, którzy mają problem z zapisaniem stanu, używa fragmentów, ponieważ działania faktycznie zapisują statystyki składników interfejsu użytkownika, o ile mają one identyfikator, ale fragmenty są bardziej specjalne, użyłem fragmentów raz, ale nigdy nie użyję im znowu problem z zapisaniem instancji był uciążliwy
Lord-Ralf-Adolf

to działa ... dzięki
Fanadez

1

Co zaoszczędzić, a czego nie?

Czy zastanawiałeś się kiedyś, dlaczego tekst w pliku EditTextjest zapisywany automatycznie podczas zmiany orientacji? Ta odpowiedź jest dla ciebie.

Gdy instancja działania zostanie zniszczona, a system odtworzy nową instancję (na przykład zmianę konfiguracji). Próbuje go odtworzyć przy użyciu zestawu zapisanych danych starego stanu aktywności ( stanu instancji ).

Stan instancji to zbiór par klucz-wartość przechowywany w Bundleobiekcie.

Domyślnie system zapisuje na przykład obiekty View w pakiecie.

  • Text in EditText
  • Przewiń pozycję w ListViewitd.

Jeśli potrzebujesz innej zmiennej do zapisania jako części stanu instancji, powinieneś OVERRIDE onSavedInstanceState(Bundle savedinstaneState) .

Na przykład int currentScorew GameActivity

Więcej szczegółów na temat onSavedInstanceState (pakiet saveinstaneState) podczas zapisywania danych

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

Tak więc przez pomyłkę, jeśli zapomnisz wywołać super.onSaveInstanceState(savedInstanceState);domyślne zachowanie nie będzie działać, tzn. Tekst w EditText nie zostanie zapisany.

Które wybrać do przywrócenia stanu aktywności?

 onCreate(Bundle savedInstanceState)

LUB

onRestoreInstanceState(Bundle savedInstanceState)

Obie metody otrzymują ten sam obiekt pakietu, więc tak naprawdę nie ma znaczenia, gdzie napiszesz logikę przywracania. Jedyną różnicą jest to, że w onCreate(Bundle savedInstanceState)metodzie będziesz musiał dać kontrolę zerową, podczas gdy w drugim przypadku nie jest to konieczne. Inne odpowiedzi mają już fragmenty kodu. Możesz je polecić.

Więcej szczegółów na temat onRestoreInstanceState (pakiet zapisanyinstaneState)

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

Zawsze wywołuj super.onRestoreInstanceState(savedInstanceState);, aby system domyślnie przywrócił hierarchię widoku

Premia

System onSaveInstanceState(Bundle savedInstanceState)jest wywoływany tylko wtedy, gdy użytkownik zamierza wrócić do działania. Na przykład używasz aplikacji X i nagle otrzymujesz połączenie. Przejdziesz do aplikacji dzwoniącego i wrócisz do aplikacji X. W tym przypadkuonSaveInstanceState(Bundle savedInstanceState) metoda zostanie wywołana.

Ale rozważ to, jeśli użytkownik naciśnie przycisk Wstecz. Zakłada się, że użytkownik nie zamierza powrócić do działania, dlatego w tym przypadku system onSaveInstanceState(Bundle savedInstanceState)nie będzie go wywoływał. Należy pamiętać, że podczas zapisywania danych należy wziąć pod uwagę wszystkie scenariusze.

Ważne linki:

Demonstracja domyślnego zachowania
Oficjalna dokumentacja Androida .


1

Teraz warto zrobić 2 sposoby w modelu widoku. jeśli chcesz zapisać pierwszy jako zapisaną instancję: Możesz dodać parametr stanu do modelu widoku takiego jak ten https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

lub możesz zapisać zmienne lub obiekt w modelu widoku, w tym przypadku model widoku będzie utrzymywał cykl życia do momentu zniszczenia działania.

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

masz rację, ale ta biblioteka jest wciąż dostępna, więc myślę, że powinniśmy poczekać ...
Zhar

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.