Come salvare uno stato di attività utilizzando lo stato dell'istanza di salvataggio?


2620

Ho lavorato sulla piattaforma Android SDK ed è poco chiaro come salvare lo stato di un'applicazione. Quindi, dato questo piccolo re-tooling dell'esempio "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);
  }
}

Ho pensato che sarebbe bastato per il caso più semplice, ma risponde sempre con il primo messaggio, indipendentemente da come mi allontano dall'app.

Sono sicuro che la soluzione è semplice come ignorare onPauseo qualcosa del genere, ma ho cercato nella documentazione per circa 30 minuti e non ho trovato nulla di ovvio.


9
Quando viene salvatoInstanceState == null e quando non è null?
Trojan.ZBOT

90
Stai esplicitamente distruggendo la tua attività - come hai detto, allontanandoti da essa, ad esempio premendo indietro. In realtà, lo scenario in cui viene utilizzato questo 'salvatoInstanceState' è quando Android distrugge la tua attività a scopo ricreativo. Per intenderci: se si cambia la lingua del telefono mentre l'attività era in esecuzione (e quindi è necessario caricare risorse diverse dal progetto). Un altro scenario molto comune è quando si ruota il telefono su un lato in modo che l'attività venga ricreata e visualizzata in orizzontale.
villoren,

16
Per ottenere il secondo messaggio, abilita "Non conservare attività" nelle opzioni di sviluppo. Premi un pulsante Home e torna dai recenti.
Yaroslav Mytkalyk,


6
puoi farlo con: onSaveInstanceState (pacchetto salvatoInstanceState)
VahidHoseini

Risposte:


2568

È necessario sovrascrivere onSaveInstanceState(Bundle savedInstanceState)e scrivere i valori dello stato dell'applicazione che si desidera modificare nel Bundleparametro in questo modo:

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

Il Bundle è essenzialmente un modo per archiviare una mappa NVP ("Coppia Nome-Valore"), e verrà passata a onCreate()e anche onRestoreInstanceState()dove si estrarranno i valori da attività come questa:

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

O da un frammento.

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

Di solito useresti questa tecnica per memorizzare i valori di istanza per la tua applicazione (selezioni, testo non salvato, ecc.).


24
Qualche possibilità che funzioni al telefono, ma non nell'emulatore? Non riesco a ottenere un salvatoInstanceState non null.
Adam Jack,

491
ATTENZIONE: devi chiamare super.onSaveInstanceState (savedInstanceState) prima di aggiungere i tuoi valori al pacchetto, altrimenti verranno cancellati da quella chiamata (Droid X Android 2.2).
jkschneider,

121
Attenzione: la documentazione ufficiale afferma che è necessario salvare informazioni importanti all'interno del metodo onPause perché il metodo onsaveinstance non fa parte del ciclo di vita di Android. developer.android.com/reference/android/app/Activity.html
schlingel

32
Questo fatto rende effettivamente onSaveInstanceStatequasi inutile tranne che per i cambiamenti di orientamento dello schermo. In quasi tutti gli altri casi, non puoi mai fare affidamento su di esso e dovrai salvare manualmente lo stato dell'interfaccia utente da qualche altra parte. O impedire che l'app venga uccisa ignorando il comportamento del pulsante INDIETRO. Non capisco perché lo abbiano persino implementato in questo modo in primo luogo. Totalmente poco intuitivo. E non puoi avere quel Bundle in cui ti danno il sistema per salvare le cose se non in questo metodo molto particolare.
Chakrit,

12
Si noti che il salvataggio / ripristino dello stato dell'interfaccia utente nel / dal pacchetto viene automaticamente curato per gli ViewID assegnati . Dai onSaveInstanceStatedocumenti: "L'implementazione predefinita si occupa della maggior parte dello stato per istanza dell'interfaccia utente richiamando onSaveInstanceState()ogni vista nella gerarchia che ha un ID e salvando l'id della vista attualmente focalizzata (che viene ripristinata dall'implementazione predefinita di onRestoreInstanceState(Bundle)) "
Vicky Chijwani,

433

Serve savedInstanceStatesolo per salvare lo stato associato a un'istanza corrente di un'attività, ad esempio le informazioni di navigazione o di selezione correnti, in modo che se Android distrugge e ricrea un'attività, questa può tornare come prima. Vedere la documentazione per onCreateeonSaveInstanceState

Per uno stato più longevo, considerare l'utilizzo di un database SQLite, un file o preferenze. Vedi Salvataggio dello stato persistente .


3
Quando viene salvatoInstanceState == null e quando non è null?
Trojan.ZBOT

6
savedInstanceState è nullo quando il sistema sta creando una nuova istanza della tua attività e non nullo durante il ripristino.
Gabriel Câmara,

7
... che solleva la questione di quando il sistema deve creare una nuova istanza di attività. Alcuni modi per uscire da un'app non creano un bundle, quindi è necessario creare una nuova istanza. Questo è il problema fondamentale; significa che non si può fare affidamento sull'esistenza di bundle e si devono fare alcuni mezzi alternativi di archiviazione persistente. Il vantaggio di onSave / onRestoreInstanceState è che è un meccanismo che il sistema può fare bruscamente , senza consumare molte risorse di sistema. Quindi è bene supportarlo, oltre ad avere una memoria persistente per un'uscita più aggraziata dall'app.
ToolmakerSteve,

415

Si noti che NON è sicuro da usare onSaveInstanceStatee onRestoreInstanceState per i dati persistenti , secondo la documentazione sugli stati di attività in http://developer.android.com/reference/android/app/Activity.html .

Il documento afferma (nella sezione "Ciclo di vita delle attività"):

Si noti che è importante salvare i dati persistenti onPause()invece che onSaveInstanceState(Bundle) perché il successivo non fa parte dei callback del ciclo di vita, quindi non verrà chiamato in ogni situazione come descritto nella sua documentazione.

In altre parole, inserisci il tuo codice di salvataggio / ripristino per i dati persistenti in onPause()e onResume()!

EDIT : per ulteriori chiarimenti, ecco la onSaveInstanceState()documentazione:

Questo metodo viene chiamato prima che un'attività possa essere uccisa in modo che quando ritorni qualche tempo in futuro possa ripristinarne lo stato. Ad esempio, se l'attività B viene avviata davanti all'attività A e ad un certo punto l'attività A viene interrotta per recuperare risorse, l'attività A avrà la possibilità di salvare lo stato corrente della sua interfaccia utente tramite questo metodo in modo che quando l'utente ritorna all'attività A, è possibile ripristinare lo stato dell'interfaccia utente tramite onCreate(Bundle)o onRestoreInstanceState(Bundle).


55
Solo per nitpick: non è neanche pericoloso. Questo dipende solo da cosa vuoi conservare e per quanto tempo, su cui @Bernard non è del tutto chiaro nella sua domanda originale. InstanceState è perfetto per preservare l'attuale stato dell'interfaccia utente (dati inseriti nei controlli, posizioni correnti negli elenchi e così via), mentre Pausa / Riprendi è l'unica possibilità per l'archiviazione persistente a lungo termine.
Pontus Gagge,

30
Questo dovrebbe essere ridimensionato. Non è sicuro utilizzare su InstanceState (Salva | Ripristina) come i metodi del ciclo di vita (ovvero fare qualsiasi altra cosa al di fuori di salvare / ripristinare lo stato). Sono perfettamente buoni per salvare / ripristinare lo stato. Inoltre, come si desidera salvare / ripristinare lo stato in onPause e onResume? Non ottieni pacchetti in quei metodi che puoi usare, quindi dovresti impiegare qualche altro salvataggio di stato, in database, file, ecc. Che è stupido.
Felix,

141
Non dovremmo sottovalutare questa persona, almeno ha fatto degli sforzi per esaminare la documentazione e penso che siamo qui per costruire una comunità ben informata e aiutarci a vicenda a non VOTARE. quindi 1 vota per lo sforzo e ti chiederò di non votare verso il basso piuttosto che votare o non votare ... questa persona cancella la confusione che si vorrebbe avere quando si passa attraverso la documentazione. 1 voto in alto :)
AZ_

21
Non credo che questa risposta meriti un voto negativo. Almeno si sforzò di rispondere e aveva citato una sezione di doco.
GSree

34
Questa risposta è assolutamente corretta e merita il voto UP, non down! Vorrei chiarire la differenza tra gli stati per quei ragazzi che non lo vedono. Uno stato della GUI, come i pulsanti di opzione selezionati e del testo nel campo di input, è molto meno importante dello stato dei dati, come i record aggiunti a un elenco visualizzato in un ListView. Quest'ultimo deve essere archiviato nel database in onPause perché è l'unica chiamata garantita. Se lo metti in onSaveInstanceState invece, rischi di perdere dati se non viene chiamato. Ma se la selezione del pulsante di opzione non viene salvata per lo stesso motivo, non è un grosso problema.
JBM,

206

Il mio collega ha scritto un articolo che spiega lo stato dell'applicazione su dispositivi Android, tra cui le spiegazioni sulle attività del ciclo di vita e le informazioni di stato, come memorizzare le informazioni di stato, e il salvataggio di Stato Bundlee SharedPreferencesdi dare un'occhiata a qui .

L'articolo tratta tre approcci:

Archivia i dati di controllo della variabile locale / dell'interfaccia utente per la durata dell'applicazione (cioè temporaneamente) utilizzando un bundle di stato dell'istanza

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

Archivia i dati di controllo della variabile locale / dell'interfaccia utente tra istanze dell'applicazione (ovvero permanentemente) utilizzando le preferenze condivise

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

Mantenere attive le istanze degli oggetti nella memoria tra le attività durante la vita dell'applicazione utilizzando un'istanza non configurata conservata

[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 L'articolo dice sui dati in SharedPreferences che "Questi dati sono scritti nel database sul dispositivo .." Credo che i dati siano archiviati in un file nella directory dell'app del file system.
Tom,

2
I dati di @Tom SharefPrefs sono scritti in file xml. XML è una specie di database? Direi che lo è;)
MaciejGórski

148

Questo è un classico 'gotcha' dello sviluppo Android. Ci sono due problemi qui:

  • Esiste un sottile bug di Android Framework che complica notevolmente la gestione dello stack delle applicazioni durante lo sviluppo, almeno sulle versioni legacy (non del tutto sicuro se / quando / come è stato risolto). Discuterò questo bug di seguito.
  • Il modo "normale" o previsto di gestire questo problema è, di per sé, piuttosto complicato con la dualità di onPause / onResume e onSaveInstanceState / onRestoreInstanceState

Navigando attraverso tutti questi thread, sospetto che la maggior parte delle volte gli sviluppatori parlino contemporaneamente di questi due diversi problemi ... quindi tutta la confusione e le notizie di "questo non funziona per me".

In primo luogo, per chiarire il comportamento "previsto": onSaveInstance e onRestoreInstance sono fragili e solo per uno stato transitorio. L'uso previsto (afaict) è gestire la ricreazione delle attività quando il telefono viene ruotato (cambio di orientamento). In altre parole, l'uso previsto è quando la tua attività è ancora logicamente "in primo piano", ma deve ancora essere rafforzata dal sistema. Il bundle salvato non è persistente al di fuori del processo / memory / gc, quindi non puoi davvero fare affidamento su questo se la tua attività passa in background. Sì, forse la memoria della tua attività sopravviverà al suo viaggio in background e sfuggirà a GC, ma questo non è affidabile (né prevedibile).

Quindi, se hai uno scenario in cui vi sono significativi "progressi dell'utente" o uno stato che dovrebbe essere persistito tra i "lanci" della tua applicazione, la guida è di usare onPause e onResume. Devi scegliere tu stesso e preparare un negozio persistente.

MA - c'è un bug molto confuso che complica tutto questo. I dettagli sono qui:

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

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

Fondamentalmente, se la tua applicazione viene avviata con il flag SingleTask, e successivamente la avvii dalla schermata principale o dal menu di avvio, quella successiva chiamata creerà una NUOVA attività ... avrai effettivamente due diverse istanze della tua app abitare nello stesso stack ... che diventa molto strano molto velocemente. Questo sembra accadere quando avvii la tua app durante lo sviluppo (ovvero da Eclipse o Intellij), quindi gli sviluppatori si imbattono molto in questo. Ma anche attraverso alcuni dei meccanismi di aggiornamento dell'app store (quindi influisce anche sui tuoi utenti).

Ho affrontato questi thread per ore prima di rendermi conto che il mio problema principale era questo bug, non il comportamento del framework previsto. Un grande commento esoluzione (AGGIORNAMENTO: vedi sotto) sembra provenire dall'utente @kaciula in questa risposta:

Comportamento della pressione del tasto Home

AGGIORNAMENTO Giugno 2013 : mesi dopo, ho finalmente trovato la soluzione "corretta". Non è necessario gestire autonomamente alcun flag StateApp avviato con stato, è possibile rilevarlo dal framework e salvarlo in modo appropriato. Lo uso all'inizio del mio 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

onSaveInstanceStateviene chiamato quando il sistema ha bisogno di memoria e uccide un'applicazione. Non viene chiamato quando l'utente chiude l'applicazione. Quindi penso che anche lo stato dell'applicazione debba essere salvato in onPauseDovrebbe essere salvato in un archivio persistente come PreferencesoSqlite


36
Spiacenti, non è del tutto corretto. onSaveInstanceState viene chiamato prima che l'attività debba essere rifatta. cioè ogni volta che l'utente ruota il dispositivo. È pensato per la memorizzazione di stati di vista transitori. Quando Android forza la chiusura dell'applicazione, onSaveInstanceState NON viene effettivamente chiamato (motivo per cui non è sicuro per la memorizzazione di dati importanti dell'applicazione). onPause, tuttavia, è garantito che venga chiamato prima che l'attività venga interrotta, quindi dovrebbe essere utilizzato per memorizzare informazioni permanenti nelle preferenze o in Squlite. Risposta giusta, ragioni sbagliate.
moveaway00

74

Entrambi i metodi sono utili e validi ed entrambi sono più adatti per diversi scenari:

  1. L'utente termina l'applicazione e la riapre in un secondo momento, ma l'applicazione deve ricaricare i dati dall'ultima sessione; ciò richiede un approccio di archiviazione persistente come l'utilizzo di SQLite.
  2. L'utente cambia applicazione, quindi torna all'originale e desidera riprendere da dove era stato interrotto - salvare e ripristinare i dati del bundle (come i dati sullo stato dell'applicazione) onSaveInstanceState()e di onRestoreInstanceState()solito è adeguato.

Se si salvano i dati di stato in modo persistente, è possibile ricaricarli in un onResume()o onCreate()(o effettivamente in qualsiasi chiamata del ciclo di vita). Questo può o non può essere il comportamento desiderato. Se lo memorizzi in un bundle in un InstanceState, allora è temporaneo ed è adatto solo per l'archiviazione dei dati da utilizzare nella stessa "sessione" dell'utente (io uso il termine sessione in modo approssimativo) ma non tra "sessioni".

Non è che un approccio sia migliore dell'altro, come tutto, è solo importante capire quale comportamento è necessario e selezionare l'approccio più appropriato.


70

Per quanto mi riguarda, il salvataggio dello stato è un kludge al massimo. Se è necessario salvare dati persistenti, basta usare un database SQLite . Android rende SOOO semplice.

Qualcosa come questo:

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

Una semplice telefonata dopo

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

9
Perché impiega troppo tempo a caricare un database SQLite, considerato che si trova sul percorso critico per mostrare all'utente l'interfaccia utente dell'app. In realtà non l'ho cronometrato, quindi sono felice di essere corretto, ma sicuramente caricare e aprire un file di database non sarà veloce?
Tom,

5
Grazie mille per aver fornito una soluzione che un principiante può tagliare e incollare nella sua app e utilizzare subito! @Tom Per quanto riguarda la velocità, sono necessari circa sette secondi per memorizzare 1000 coppie, ma è possibile farlo in un AsyncTask. Tuttavia, è necessario aggiungere un {pointer.close ()} al fine altrimenti si bloccherà dalla perdita di memoria mentre lo si fa.
Noumenon,

3
Mi sono imbattuto in questo e mentre sembra pulito, sono titubante nel provare a utilizzarlo su Google Glass, che è il dispositivo sul quale sto lavorando / con cui ultimamente.
Stephen Tetreault,

61

Penso di aver trovato la risposta. Lasciami dire cosa ho fatto in parole semplici:

Supponiamo di avere due attività, attività1 e attività2 e sto navigando dall'attività1 all'attività2 (ho svolto alcuni lavori nell'attività2) e di nuovo all'attività 1 facendo clic su un pulsante nell'attività1. Ora, a questo punto, volevo tornare all'attività2 e voglio vedere la mia attività2 nelle stesse condizioni quando ho lasciato l'ultima attività2.

Per lo scenario sopra quello che ho fatto è che nel manifest ho apportato alcune modifiche come questa:

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

E nell'attività1 sull'evento clic pulsante ho fatto così:

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

E in attività2 all'evento clic sul pulsante ho fatto così:

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

Ora, ciò che accadrà è che qualsiasi modifica apportata all'attività2 non andrà persa, e possiamo vedere l'attività2 nello stesso stato in cui eravamo rimasti in precedenza.

Credo che questa sia la risposta e questo funziona bene per me. Correggimi se sbaglio.


2
Cura @bagusflyer per essere più specifici ??? Il tuo commento non è utile e nessuno può aiutarti in base a questo.
Stephen Tetreault,

2
Questa è una risposta a una situazione diversa: due attività all'interno della stessa app. OP riguarda l' uscita dall'app (ad es. Pulsante home o altri mezzi per passare a un'altra app).
ToolmakerSteve

44

onSaveInstanceState()per dati temporanei (ripristinati in onCreate()/ onRestoreInstanceState()), onPause()per dati persistenti (ripristinati in onResume()). Dalle risorse tecniche di Android:

onSaveInstanceState () viene chiamato da Android se l'attività viene interrotta e potrebbe essere interrotta prima di riprenderla! Ciò significa che dovrebbe archiviare qualsiasi stato necessario per reinizializzare la stessa condizione al riavvio dell'attività. È la controparte del metodo onCreate () e in effetti il ​​bundle saveInstanceState passato a onCreate () è lo stesso bundle che costruisci come outState nel metodo onSaveInstanceState ().

onPause () e onResume () sono anche metodi gratuiti. onPause () viene sempre chiamato quando l'attività termina, anche se lo abbiamo istigato (ad esempio con una chiamata finish ()). Useremo questo per salvare la nota corrente nel database. La buona pratica è rilasciare qualsiasi risorsa che può essere rilasciata anche durante un onPause (), per occupare meno risorse quando si trova nello stato passivo.


40

onSaveInstanceState()Viene chiamato veramente quando l'attività va in background.

Citazione dalla documentazione: "Questo metodo viene chiamato prima che un'attività possa essere uccisa in modo che quando ritorni qualche tempo in futuro possa ripristinarne lo stato." fonte


37

Per aiutare a ridurre il boilerplate, uso quanto segue interfacee classper leggere / scrivere in uno Bundleper salvare lo stato dell'istanza.


Innanzitutto, crea un'interfaccia che verrà utilizzata per annotare le variabili dell'istanza:

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 {

}

Quindi, crea una classe in cui la riflessione verrà utilizzata per salvare i valori nel pacchetto:

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

}

Esempio di utilizzo:

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

}

Nota: questo codice è stato adattato da un progetto di libreria denominato AndroidAutowire che è concesso in licenza con la licenza MIT .


34

Nel frattempo in genere non uso più

Bundle savedInstanceState & Co

Il ciclo di vita è per la maggior parte delle attività troppo complicato e non necessario.

E Google si afferma, NON è nemmeno affidabile.

Il mio modo è di salvare immediatamente eventuali modifiche nelle preferenze:

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

In qualche modo SharedPreferences funziona in modo simile ai pacchetti. E naturalmente e inizialmente tali valori devono essere letti dalle preferenze.

Nel caso di dati complessi è possibile utilizzare SQLite invece di utilizzare le preferenze.

Quando si applica questo concetto, l'attività continua a utilizzare l'ultimo stato salvato, indipendentemente dal fatto che fosse un'apertura iniziale con riavvii intermedi o una riapertura a causa dello stack posteriore.


31

Per rispondere direttamente alla domanda originale. savedInstancestate è null perché l'attività non viene mai ricreata.

La tua attività verrà ricreata con un bundle di stato quando:

  • Modifiche alla configurazione come la modifica dell'orientamento o della lingua del telefono che potrebbe richiedere la creazione di una nuova istanza di attività.
  • Si ritorna all'app dallo sfondo dopo che il sistema operativo ha distrutto l'attività.

Android distruggerà le attività in background quando sono sotto pressione della memoria o dopo che sono state in background per un lungo periodo di tempo.

Quando provi il tuo esempio ciao mondo ci sono alcuni modi per uscire e tornare all'attività.

  • Quando si preme il pulsante Indietro, l'attività è terminata. Il riavvio dell'app è una nuova istanza. Non riprendi affatto dallo sfondo.
  • Quando si preme il pulsante Home o si utilizza il selettore attività, l'attività passerà in background. Quando si ritorna all'applicazione, onCreate verrà chiamato solo se l'attività deve essere distrutta.

Nella maggior parte dei casi, se stai solo premendo Home e quindi riavvii l'app, non sarà necessario ricreare l'attività. Esiste già in memoria, quindi onCreate () non verrà chiamato.

C'è un'opzione in Impostazioni -> Opzioni sviluppatore denominata "Non conservare attività". Quando è abilitato, Android distrugge sempre le attività e le ricrea quando sono in background. Questa è un'ottima opzione da lasciare abilitata durante lo sviluppo perché simula lo scenario peggiore. (Un dispositivo con poca memoria che ricicla continuamente le tue attività).

Le altre risposte sono preziose in quanto ti insegnano i modi corretti per memorizzare lo stato, ma non pensavo che avessero davvero risposto PERCHÉ il tuo codice non funzionava come previsto.


28

I metodi onSaveInstanceState(bundle)e onRestoreInstanceState(bundle)sono utili per la persistenza dei dati semplicemente durante la rotazione dello schermo (cambio di orientamento).
Non sono nemmeno buoni quando si passa da un'applicazione all'altra (poiché il onSaveInstanceState()metodo viene chiamato ma onCreate(bundle)e onRestoreInstanceState(bundle)non viene più richiamato.
Per maggiore persistenza, utilizzare le preferenze condivise. Leggi questo articolo


2
Nel tuo caso onCreatee onRestoreInstanceStatenon vengono chiamati perché Activitynon vengono affatto distrutti quando si cambia app, quindi non è necessario ripristinare nulla. Android chiama onSaveInstanceStatenel caso in cui l'attività venga distrutta in seguito (cosa che accade con la certezza del 100% quando si ruota lo schermo perché l'intera configurazione del dispositivo è cambiata e l'attività deve essere ricreata da zero).
Vicky Chijwani,

20

Il mio problema era che avevo bisogno di persistenza solo durante la vita dell'applicazione (ovvero una singola esecuzione che includeva l'avvio di altre attività secondarie all'interno della stessa app e la rotazione del dispositivo, ecc.). Ho provato varie combinazioni delle risposte sopra ma non ho ottenuto quello che volevo in tutte le situazioni. Alla fine, ciò che ha funzionato per me è stato ottenere un riferimento allo stato di stato salvato durante onCreate:

mySavedInstanceState=savedInstanceState;

e usalo per ottenere il contenuto della mia variabile quando ne avevo bisogno, sulla falsariga di:

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

Uso onSaveInstanceStatee onRestoreInstanceStatecome suggerito sopra ma immagino che potrei anche o in alternativa utilizzare il mio metodo per salvare la variabile quando cambia (ad es. Usando putBoolean)


19

Sebbene la risposta accettata sia corretta, esiste un metodo più rapido e semplice per salvare lo stato di attività su Android utilizzando una libreria chiamata Icepick . Icepick è un processore di annotazione che si occupa di tutto il codice della caldaia utilizzato per salvare e ripristinare lo stato.

Fare qualcosa del genere con 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);
  }
}

È lo stesso di fare questo:

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 funzionerà con qualsiasi oggetto che salvi il suo stato con a Bundle.


16

Quando viene creata un'attività, viene chiamato il metodo onCreate ().

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

savedInstanceState è un oggetto della classe Bundle che è null per la prima volta, ma contiene valori quando viene ricreato. Per salvare lo stato di Activity devi sovrascrivere onSaveInstanceState ().

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

inserisci i tuoi valori nell'oggetto bundle "outState" come outState.putString ("chiave", "Bentornato") e salva chiamando super. Quando l'attività verrà distrutta, il suo stato viene salvato nell'oggetto Bundle e può essere ripristinato dopo la ricreazione in onCreate () o onRestoreInstanceState (). Il pacchetto ricevuto in onCreate () e onRestoreInstanceState () sono uguali.

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

o

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

15

Esistono sostanzialmente due modi per attuare questo cambiamento.

  1. usando onSaveInstanceState()eonRestoreInstanceState() .
  2. In manifest android:configChanges="orientation|screenSize".

Non raccomando davvero di usare il secondo metodo. Dal momento che in una mia esperienza stava causando la metà dello schermo del dispositivo nero mentre ruotava da ritratto a paesaggio e viceversa.

Utilizzando il primo metodo sopra menzionato, possiamo conservare i dati quando l'orientamento viene modificato o si verifica qualsiasi modifica della configurazione. Conosco un modo in cui è possibile archiviare qualsiasi tipo di dati all'interno dell'oggetto stato salvatoInstance.

Esempio: considerare un caso se si desidera persistere l'oggetto Json. creare una classe di modello con getter e setter.

class MyModel extends Serializable{
JSONObject obj;

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

JSONObject getJsonObject()
return this.obj;
} 
}

Ora nella tua attività nel metodo onCreate e onSaveInstanceState fai quanto segue. Sarà simile a questo:

@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

Ecco un commento dalla risposta di Steve Moseley (di ToolmakerSteve ) che mette le cose in prospettiva (nel complesso onSaveInstanceState vs onPause, East cost vs west cost saga)

@VVK - In parte non sono d'accordo. Alcuni modi per uscire da un'app non attivano onSaveInstanceState (oSIS). Ciò limita l'utilità di oSIS. Vale la pena supportarlo, per risorse minime del sistema operativo, ma se un'app vuole riportare l'utente nello stato in cui si trovava, indipendentemente da come l'app è stata abbandonata, è invece necessario utilizzare un approccio di archiviazione persistente. Uso onCreate per verificare la presenza di bundle e, se manca, controllare l' archiviazione persistente. Questo centralizza il processo decisionale. Posso riprendermi da un arresto anomalo, dall'uscita del pulsante Indietro o dalla voce di menu personalizzata Esci, o tornare allo schermo l'utente era su molti giorni dopo. - ToolmakerSteve 19 settembre 15 alle 10:38


10

Codice Kotlin:

Salva:

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

e poi in onCreate()oonRestoreInstanceState()

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

Aggiungi valori predefiniti se non vuoi avere Optionals


9

Per archiviare i onCreate()dati sullo stato delle attività , per prima cosa è necessario salvare i dati in savedInstanceState mediante il SaveInstanceState(Bundle savedInstanceState)metodo di sostituzione .

Quando SaveInstanceState(Bundle savedInstanceState)viene chiamato il metodo di distruzione delle attività e lì si salvano i dati che si desidera salvare. E ottieni lo stesso onCreate()quando si riavvia l'attività. (SaveInstanceState non sarà null poiché hai salvato alcuni dati al suo interno prima che l'attività venga distrutta)


6

Semplice e veloce per risolvere questo problema sta usando IcePick

Innanzitutto, installa la libreria app/build.gradle

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

Ora, controlliamo questo esempio sotto come salvare lo stato in Attività

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

Funziona con attività, frammenti o qualsiasi oggetto che deve serializzare il suo stato su un pacchetto (ad esempio ViewPresenters di mortaio)

Icepick può anche generare il codice di stato dell'istanza per le visualizzazioni personalizzate:

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 Sì, funziona per Fragment e Custom View. Si prega di controllare il codice di esempio. Ho modificato la mia risposta. Ti suggerisco di andare a documenti ufficiali qui github.com/frankiesardo/icepick per trovare altro esempio di codice.
THANN Phearum,

@ChetanMehra intendi classe di visualizzazione personalizzata, giusto? Se si tratta di una visualizzazione personalizzata, possiamo sovrascrivere onSaveInstanceState e onRestoreInstanceState come nell'esempio sopra di CustomView.
THANN Phearum,

Intendo oggetto classe all'interno della classe vista ad esempio: la classe CustomView estende Visualizza {@State ClassA a;} o la classe CustomView estende Visualizza {@ State Classe interna {}}
Chetan Mehra

@THANNPhearum Devo porlo come un'altra domanda?
Chetan Mehra,

Vedo. In tal caso, la tua Classe A dovrebbe essere Parcelable. Come ha detto che funziona per attività, frammenti o qualsiasi oggetto che debba serializzare il suo stato su un pacchetto
THANN Phearum,

6

Non sono sicuro che la mia soluzione sia disapprovata o meno, ma utilizzo un servizio associato per mantenere lo stato ViewModel. Se lo memorizzi nella memoria nel servizio o persisti e lo recuperi da un database SQLite dipende dai tuoi requisiti. Questo è ciò che fanno i servizi di qualsiasi tipo, forniscono servizi come il mantenimento dello stato dell'applicazione e la logica aziendale comune astratta.

A causa dei limiti di memoria e di elaborazione inerenti ai dispositivi mobili, tratto le visualizzazioni Android in modo simile a una pagina Web. La pagina non mantiene lo stato, è puramente un componente del livello di presentazione il cui unico scopo è presentare lo stato dell'applicazione e accettare l'input dell'utente. Le recenti tendenze nell'architettura delle app Web utilizzano l'uso del modello vecchio modello, vista, controller (MVC), in cui la pagina è la vista, i dati di dominio sono il modello e il controller si trova dietro un servizio web. Lo stesso modello può essere utilizzato in Android con View, beh ... View, il modello sono i dati del tuo dominio e il Controller è implementato come un servizio associato Android. Ogni volta che si desidera che una vista interagisca con il controller, associarla all'avvio / riprendere e separare all'arresto / pausa.

Questo approccio offre l'ulteriore vantaggio di applicare il principio di progettazione Separazione delle preoccupazioni in quanto tutte le logiche aziendali dell'applicazione possono essere spostate nel servizio, riducendo la duplicazione della logica su più viste e consentendo alla vista di applicare un altro importante principio di progettazione, la responsabilità singola.


5

Kotlin

È necessario ignorare onSaveInstanceStateeonRestoreInstanceState archiviare e recuperare le variabili che si desidera essere persistenti

Grafico del ciclo di vita

Memorizza variabili

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

Recupera variabili

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

Ora Android fornisce ViewModels per il salvataggio dello stato, dovresti provare a usarlo invece di saveInstanceState.


3
Questo non è vero. Dalla documentazione: "A differenza dello stato dell'istanza salvato, ViewModels viene distrutto durante una morte del processo avviata dal sistema. Ecco perché dovresti usare gli oggetti ViewModel in combinazione con onSaveInstanceState () (o qualche altra persistenza del disco), riponendo gli identificatori in savedInstanceState per aiutare a visualizzare i modelli ricaricano i dati dopo la morte del sistema. "
Vyacheslav Martynenko,

Mi sono appena imbattuto in questo con le autorizzazioni che cambiano in background.
Brill Pappin,

Sono d'accordo, dal documento "se è necessario gestire la morte del processo avviata dal sistema, è possibile utilizzare onSaveInstanceState () come backup".
Zhar,

2

C'è un modo per fare in modo che Android salvi gli stati senza implementare alcun metodo. Aggiungi questa riga alla dichiarazione Manifest in Activity:

android:configChanges="orientation|screenSize"

Dovrebbe sembrare come questo:

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

Qui puoi trovare ulteriori informazioni su questa proprietà.

Si consiglia di lasciare che Android gestisca questo per te rispetto alla gestione manuale.


2
Questo non ha nulla a che fare con il salvataggio dello stato, basta rinunciare ai cambiamenti di orientamento, tenere presente che l'app può essere riavviata, messa in pausa e ripresa in qualsiasi momento per eventi diversi
lord-ralf-adolf

1
Questa risposta è per coloro che vogliono salvare lo stato quando l'orientamento è cambiato e vogliono evitare la comprensione e l'implementazione di un modo complesso
IgniteCoders

abbastanza giusto vedo il tuo punto, penso che la maggior parte delle persone che lottano per salvare lo stato usano frammenti perché le attività effettivamente salvano le statistiche dei componenti dell'interfaccia utente fintanto che hanno un ID, ma i frammenti sono più speciali, ho usato i frammenti una volta ma non userò mai ancora una volta la statistica dell'istanza di salvataggio è stata una sofferenza da affrontare
lord-ralf-adolf

funziona ... grazie
Fanadez

1

Cosa salvare e cosa non fare?

Vi siete mai chiesti perché il testo in EditTextviene salvato automaticamente mentre cambia un orientamento? Bene, questa risposta è per te.

Quando un'istanza di un'attività viene distrutta e il sistema ricrea una nuova istanza (ad esempio, modifica della configurazione). Tenta di ricrearlo utilizzando un set di dati salvati del vecchio stato attività ( stato istanza ).

Lo stato dell'istanza è una raccolta di coppie chiave-valore memorizzate in un Bundleoggetto.

Per impostazione predefinita, il sistema salva ad esempio gli oggetti Visualizza nel pacchetto.

  • Testo in EditText
  • Posizione di scorrimento in a ListView, ecc.

Se è necessario salvare un'altra variabile come parte dello stato dell'istanza, è necessario eseguire l'override onSavedInstanceState(Bundle savedinstaneState) metodo .

Per esempio, int currentScore in un GameActivity

Maggiori dettagli su onSavedInstanceState (pacchetto salvato instaneState) durante il salvataggio dei dati

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

Quindi, per errore, se si dimentica di chiamare super.onSaveInstanceState(savedInstanceState);il comportamento predefinito non funzionerà, ovvero il testo in EditText non verrà salvato.

Quale scegliere per ripristinare lo stato dell'attività?

 onCreate(Bundle savedInstanceState)

O

onRestoreInstanceState(Bundle savedInstanceState)

Entrambi i metodi ottengono lo stesso oggetto Bundle, quindi non importa dove si scrive la logica di ripristino. L'unica differenza è che nel onCreate(Bundle savedInstanceState)metodo dovrai dare un controllo nullo mentre non è necessario in quest'ultimo caso. Altre risposte hanno già frammenti di codice. Puoi riferirli.

Maggiori dettagli su onRestoreInstanceState (pacchetto salvato instaneState)

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

Chiamare sempre in super.onRestoreInstanceState(savedInstanceState);modo che il sistema ripristini la gerarchia di visualizzazione per impostazione predefinita

indennità

Il onSaveInstanceState(Bundle savedInstanceState)viene invocato dal sistema solo quando l'utente intende tornare all'attività. Ad esempio, stai usando App X e all'improvviso ricevi una chiamata. Passare all'app del chiamante e tornare all'app X. In questo caso, ilonSaveInstanceState(Bundle savedInstanceState) metodo verrà richiamato.

Ma considera questo se un utente preme il pulsante Indietro. Si presume che l'utente non intenda tornare all'attività, quindi in questo caso onSaveInstanceState(Bundle savedInstanceState)non verrà invocato dal sistema. Il punto è che dovresti considerare tutti gli scenari mentre salvi i dati.

Link rilevanti:

Demo sul comportamento predefinito
Documentazione ufficiale Android .


1

Ora ha senso fare 2 modi nel modello di visualizzazione. se si desidera salvare la prima come istanza salvata: è possibile aggiungere un parametro di stato nel modello di visualizzazione come questo https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

oppure è possibile salvare variabili o oggetti nel modello di visualizzazione, in questo caso il modello di visualizzazione manterrà il ciclo di vita fino a quando l'attività non viene distrutta.

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

hai ragione, ma questa libreria è ancora in uscita, quindi penso che dovremmo aspettare ...
Zhar

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.