Domanda

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 sia semplice come ignorare onPause o qualcosa del genere, ma ho cercato nella documentazione per circa 30 minuti e non ho trovato nulla di ovvio.

È stato utile?

Soluzione

Devi sovrascrivere onSaveInstanceState (Bundle savedInstanceState) e scrivere i valori dello stato dell'applicazione che desideri modificare nel parametro Bundle 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 (" Name-Value Pair ") e verrà passata a onCreate () e anche onRestoreInstanceState () dove quindi estrarre i valori in questo modo:

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

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

Altri suggerimenti

Il savedInstanceState serve solo per salvare lo stato associato a un'istanza corrente di un'attività, ad esempio informazioni di navigazione o di selezione correnti, in modo che se Android distrugge e ricrea un'attività, può tornare come era prima. Consulta la documentazione per onCreate e onSaveInstanceState

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

Nota che NOT è sicuro usare onSaveInstanceState e onRestoreInstanceState per dati persistenti , in base alla documentazione relativa agli stati di attività in http://developer.android. com / riferimento / android / app / Activity.html .

Gli stati del documento (nella sezione "Ciclo di vita delle attività"):

  

Nota che è importante salvare   invece i dati persistenti in onPause ()   di onSaveInstanceState (Bundle)   perché il successivo non fa parte del   richiamate del ciclo di vita, quindi non lo sarà   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 () !

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

  

Questo metodo viene chiamato prima che un'attività possa essere uccisa in modo tale che quando   ritorna qualche tempo in futuro può ripristinare il suo stato. Per   esempio, se l'attività B viene avviata prima dell'attività A e in alcuni casi   punto attività A viene ucciso per recuperare risorse, l'attività A avrà   una possibilità per salvare lo stato corrente della sua interfaccia utente tramite questo   in modo che quando l'utente ritorna all'attività A, lo stato di   l'interfaccia utente può essere ripristinata tramite onCreate (Bundle) o    onRestoreInstanceState (Bundle) .

Il mio collega ha scritto un articolo che spiega lo stato dell'applicazione sui dispositivi Android, comprese le spiegazioni sul ciclo di vita delle attività e le informazioni sullo stato, come archiviare le informazioni sullo stato e il salvataggio nello stato Bundle e SharedPreferences e dai un'occhiata qui .

L'articolo tratta tre approcci:

Archivia i dati di controllo della variabile locale / dell'interfaccia utente per la durata dell'applicazione (vale a dire 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 in modo permanente) 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 usando 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();
}

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 i rapporti 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/ problemi / dettaglio? id = 2373

http://code.google.com/p/android/ problemi / dettaglio? 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 che abita nello stesso stack ... che diventa molto strano molto velocemente. Questo sembra accadere quando avvii la tua app durante lo sviluppo (ad esempio 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 ottimo commento e soluzione alternativa (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;
    }
}

onSaveInstanceState viene chiamato quando il sistema ha bisogno di memoria e uccide un'applicazione. Non viene chiamato quando l'utente chiude l'applicazione. Quindi penso che lo stato dell'applicazione dovrebbe essere salvato anche in onPause Dovrebbe essere salvato in un archivio permanente come Preferenze o Sqlite

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 dell'ultima sessione & # 8211; 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) in onSaveInstanceState () e onRestoreInstanceState () è generalmente 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 nello stesso utente & # 8216; session & # 8217; (Uso il termine sessione liberamente) ma non tra & # 8216; sessioni & # 8217 ;.

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

Lo stato di salvataggio è, nella migliore delle ipotesi, un kludge. Se devi salvare dati persistenti, utilizza semplicemente un database SQLite . Android rende SOOO semplice.

Qualcosa del genere:

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

Dopo una semplice chiamata

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

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

Supponi di avere due attività, attività1 e attività2 e sto navigando da attività1 a attività2 (ho fatto alcuni lavori in attività2) e di nuovo all'attività 1 facendo clic su un pulsante in 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à activity1 sul pulsante click event 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 perdita effettuata nell'attività2 non andrà persa, e possiamo vedere l'attività2 nello stesso stato in cui l'abbiamo lasciata in precedenza.

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

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

  

onSaveInstanceState () viene chiamato da Android se l'attività viene interrotta e potrebbe essere uccisa prima che venga ripresa! 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.

Davvero onSaveInstance indica callen quando l'attività passa in background

Citazione dai documenti: " viene chiamato il metodo onSaveInstanceState (Bundle) prima di mettere l'attività in tale stato di background "

Per aiutare a ridurre il boilerplate, uso le seguenti interfaccia e class per leggere / scrivere in un Bundle per 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 in base a < a href = "https://raw.githubusercontent.com/CardinalNow/AndroidAutowire/master/LICENSE"> licenza MIT .

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 qualsiasi modifica 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 si sia trattato di un'apertura iniziale con riavvii intermedi o di una riapertura a causa dello stack posteriore.

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 solo 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 premi semplicemente Home e riavvii nuovamente l'app, non sarà necessario ricreare l'attività. Esiste già in memoria, quindi onCreate () non verrà chiamato.

C'è un'opzione in Impostazioni - > Opzioni sviluppatore chiamate " Non mantenere le 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.

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é viene chiamato il metodo onSaveInstanceState () ma onCreate (bundle) e onRestoreInstanceState (bundle) è non invocato di nuovo.
Per una maggiore persistenza utilizzare le preferenze condivise. leggi questo articolo

Il mio problema era che avevo bisogno di persistenza solo durante la vita dell'applicazione (ovvero una singola esecuzione incluso 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 a SaveInstanceState 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 onSaveInstanceState e onRestoreInstanceState come suggerito sopra, ma suppongo che potrei anche o in alternativa utilizzare il mio metodo per salvare la variabile quando cambia (ad esempio utilizzando putBoolean )

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 annotazioni 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 salva il suo stato con un Bundle .

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 in " outState " Raggruppa un oggetto come outState.putString (" chiave ", " Bentornato ") e risparmia 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");
    }

Esistono sostanzialmente due modi per implementare questa modifica.

  1. utilizzando onSaveInstanceState () e onRestoreInstanceState () .
  2. In manifest android: configChanges = " orientamento | screenSize " .

In realtà non consiglio 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.

Usando il primo metodo sopra menzionato, possiamo mantenere i dati quando l'orientamento viene modificato o si verifica una 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. crea una classe 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 procedi come 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); 

}

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

  

@VVK - In parte non sono d'accordo. Alcuni modi per uscire da un'app non si attivano   onSaveInstanceState (oSIS). Ciò limita l'utilità di oSIS. Suo   vale la pena supportare, per risorse minime del sistema operativo, ma se un'app vuole   riporta l'utente allo stato in cui si trovava, indipendentemente dall'app   uscito, è invece necessario utilizzare un approccio di archiviazione persistente.    Uso onCreate per verificare la presenza di bundle e, se manca, quindi controlla    archiviazione persistente. Questo centralizza il processo decisionale. io posso   ripristina da un arresto anomalo o dall'uscita del pulsante Indietro o dalla voce di menu personalizzata Esci o   tornare allo schermo l'utente era attivo molti giorni dopo. & # 8211; ToolmakerSteve Sep   19 '15 alle 10:38

Codice Kotlin:

salva:

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

e quindi in onCreate () o onRestoreInstanceState ()

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

Aggiungi valori predefiniti se non vuoi avere Optionals

Per ottenere i dati sullo stato delle attività memorizzati nel onCreate () , per prima cosa devi salvare i dati in saveInstanceState sovrascrivendo il metodo SaveInstanceState (Bundle savedInstanceState) .

Quando l'attività distrugge il metodo SaveInstanceState (Bundle savedInstanceState) viene chiamato e lì si salvano i dati che si desidera salvare. E ottieni lo stesso in onCreate () al riavvio dell'attività (saveInstanceState non sarà nullo poiché hai salvato alcuni dati prima che l'attività venga distrutta)

Semplice e veloce per risolvere questo problema sta usando IcePick

Innanzitutto, imposta la libreria in 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 di seguito questo esempio su 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 debba 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
}

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 il vantaggio aggiuntivo di applicare il principio di progettazione Separazione delle preoccupazioni in quanto tutte le logiche aziendali delle applicazioni possono essere spostate nel servizio che riduce la logica duplicata su più viste e consente alla vista di applicare un altro importante principio di progettazione, Responsabilità unica.

Ora Android fornisce ViewModels per salvare lo stato, dovresti provare per usarlo invece di saveInstanceState.

Cosa salvare e cosa non fare?

Vi siete mai chiesti perché il testo nel EditText viene salvato automaticamente mentre cambia l'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 una serie di dati salvati del vecchio stato attività ( stato istanza ).

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

  

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

  • Testo in Modifica testo
  • Scorri la posizione in un ListView , ecc.

Se devi salvare un'altra variabile come parte dello stato dell'istanza, dovresti OVERRIDE onSavedInstanceState (Bundle savedinstaneState) .

Ad 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 metodo onCreate (Bundle savedInstanceState) 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);
}
  

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

Bonus

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

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à richiamato dal sistema. Indica che dovresti considerare tutti gli scenari mentre salvi i dati.

Link rilevanti:

Demo sul comportamento predefinito
Documentazione ufficiale Android .

Kotlin

Devi sovrascrivere onSaveInstanceState e onRestoreInstanceState per archiviare e recuperare le variabili che desideri siano 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
}

Ho un'idea migliore. Questo sarebbe meglio per salvare i tuoi dati senza chiamare nuovamente OnCreate. Puoi disabilitarlo dall'attività quando l'orientamento sta cambiando.

Nel tuo manifest:

<activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize">

Modifica questa soluzione non funzionerà quando Android uccide il processo a causa della memoria insufficiente. se è necessario conservare i dati con certezza, in questo caso è necessario utilizzare SavedInstanceState, altrimenti suggerisco di utilizzare in questo modo. bello e facile da usare!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top