Come utilizzare il ciclo di vita e lo stato delle attività

1. Ti diamo il benvenuto

Questo codelab pratico fa parte dell'Unità 1: Inizia il corso Android Developer Fundamentals (versione 2). Otterrai il massimo da questo corso se segui i codelab in sequenza:

  • Per l'elenco completo dei codelab presenti nel corso, vedi Codelab per Android Developer Fundamentals (V2).
  • Per informazioni dettagliate sul corso, inclusi i link a tutte le sezioni concettuali, alle app e alle slide, consulta la pagina Concetti fondamentali per gli sviluppatori di Android (versione 2).

Introduzione

In questo tutorial scoprirai di più sul ciclo di vita delle attività. Il ciclo di vita è l'insieme di stati in cui può trovarsi un'attività durante la sua intera durata, da quando viene creata a quella in cui viene eliminata e il sistema recupera le risorse. Quando un utente naviga tra le attività nella tua app (e dentro e fuori dall'app), le attività passano da uno stato all'altro nel suo ciclo di vita.

IDoppio problema

A ogni fase del ciclo di vita di un'attività corrisponde un metodo di callback: onCreate(), onStart(), onPause() e così via. Quando un'attività cambia stato, viene richiamato il metodo di callback associato. Hai già visualizzato uno di questi metodi: onCreate(). Sostituendo uno qualsiasi dei metodi di callback del ciclo di vita nelle classi di attività, puoi modificare il comportamento predefinito dell'attività in risposta alle azioni dell'utente o del sistema.

Lo stato dell'attività può cambiare anche in risposta alle modifiche della configurazione del dispositivo, ad esempio quando l'utente ruota il dispositivo dalla posizione verticale a quella orizzontale. Quando vengono apportate queste modifiche alla configurazione, l'attività viene eliminata e ricreata nello stato predefinito e l'utente potrebbe perdere le informazioni inserite nell'attività. Per evitare di confondere gli utenti, è importante sviluppare la tua app per evitare perdite di dati impreviste. Più avanti in questa pratica proverai le modifiche alla configurazione e imparerai a conservare lo stato di un'attività in risposta a modifiche alla configurazione del dispositivo e ad altri eventi del ciclo di vita delle attività.

In questa pratica aggiungerai istruzioni di logging all'app TwoActivities e osserverai le modifiche del ciclo di vita delle attività mentre utilizzi l'app. Successivamente, inizierai a lavorare con queste modifiche e scoprirai come gestire l'input utente in queste condizioni.

Prerequisiti

Dovresti essere in grado di:

  • Crea ed esegui un progetto di app in Android Studio.
  • Aggiungi istruzioni di log alla tua app e visualizza i log nel riquadro Logcat.
  • Comprendere e lavorare con un'attività e un'intenzione e abituarsi a interagire con loro.

Obiettivi didattici

  • Come funziona il ciclo di vita delle attività.
  • Quando un'attività inizia, si mette in pausa, si arresta ed viene eliminata.
  • Informazioni sui metodi di callback del ciclo di vita associati alle modifiche alle attività.
  • L'effetto di azioni (come le modifiche alla configurazione) che possono generare eventi del ciclo di vita delle attività.
  • Come conservare lo stato dell'attività durante gli eventi del ciclo di vita.

In questo lab proverai a:

  • Aggiungi all'app TwoActivities codice del precedente comando per implementare i vari callback del ciclo di vita delle attività in modo da includere le istruzioni di logging.
  • Osserva i cambiamenti di stato durante l'esecuzione dell'app e l'interazione con ogni attività nell'app.
  • Modifica l'app per mantenere lo stato dell'istanza di un'attività che viene ricreata inaspettatamente in risposta al comportamento dell'utente o alla modifica della configurazione sul dispositivo.

2. Panoramica app

In questa pratica aggiungi l'app TwoActivities. L'app ha aspetto e comportamento più o meno come nell'ultimo codelab. Contiene due implementazioni di attività e consente all'utente di passare da una all'altra. Le modifiche che apporti all'app in questa pratica non influiranno sul suo comportamento visibile degli utenti.

3. 3. Attività 1: aggiungi callback del ciclo di vita a TwoActivities

In questa attività implementerai tutti i metodi di callback del ciclo di vita delle attività per stampare i messaggi in logcat quando questi metodi vengono richiamati. Questi messaggi di log ti consentono di vedere quando lo stato del ciclo di vita delle attività cambia e in che modo le modifiche dello stato del ciclo di vita influiscono sulla tua app durante l'esecuzione.

1.1 (Facoltativo) Copia il progetto TwoActivities

Per le attività di questa pratica, modificherai il progetto TwoActivities esistente che hai creato nell'ultimo progetto pratico. Se preferisci mantenere intatto il progetto TwoActivities precedente, segui i passaggi descritti in Appendice: Utilità per creare una copia del progetto.

1.2 Implementare i callback in MainActivity

  1. Apri il progetto TwoActivities in Android Studio e apri MainActivity nel riquadro Progetto > Android.
  2. Nel metodo onCreate(), aggiungi le seguenti istruzioni di log:
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
  1. Aggiungi una sostituzione per il callback onStart(), con un'istruzione nel log per quell'evento:
@Override
public void onStart(){
    super.onStart();
    Log.d(LOG_TAG, "onStart");
}

Come scorciatoia, seleziona Codice > Metodi di override in Android Studio. Viene visualizzata una finestra di dialogo con tutti i possibili metodi che puoi sostituire nel tuo corso. Scegliendo uno o più metodi di callback dall'elenco, viene inserito un modello completo per questi metodi, inclusa la chiamata richiesta alla superclasse.

  1. Utilizzare il metodo onStart() come modello per implementare i callback del ciclo di vita onPause(), onRiavvia(), onRiprendi(), onStop() e onDestroy()

Tutti i metodi di callback hanno le stesse firme (tranne il nome). Se copi e incolli onStart() per creare questi altri metodi di callback, non dimenticare di aggiornare i contenuti per chiamare il metodo giusto nella superclasse e registrare il metodo corretto.

  1. Esegui l'app.
  2. Fai clic sulla scheda Logcat nella parte inferiore di Android Studio per visualizzare il riquadro Logcat. Dovresti vedere tre messaggi di log che mostrano i tre stati del ciclo di vita attraverso cui è stata eseguita la transizione dell'attività all'inizio:
D/MainActivity: -------
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume

1.3 Implementa i callback del ciclo di vita in SecondActivity

Ora che hai implementato i metodi di callback del ciclo di vita per MainActivity, ripeti l'operazione per SecondActivity.

  1. Apri SecondActivity.
  2. All'inizio della classe, aggiungi una costante per la variabile LOG_TAG:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
  1. Aggiungi le istruzioni di log e i callback del ciclo di vita alla seconda attività. Puoi copiare e incollare i metodi di callback da MainActivity.
  2. Aggiungi un'istruzione di log al metodoreturnRispondi() subito prima del metodo finish():
Log.d(LOG_TAG, "End SecondActivity");

1.4 Osserva il log durante l'esecuzione dell'app**

  1. Esegui l'app.
  2. Fai clic sulla scheda Logcat nella parte inferiore di Android Studio per visualizzare il riquadro Logcat.
  3. Inserisci Attività nella casella di ricerca. Il logcat di Android può essere molto lungo e disordinato. Poiché la variabile LOG_TAG in ogni classe contiene le parole MainActivity o SecondActivity, questa parola chiave ti consente di filtrare il log solo per ciò che ti interessa.

IDoppio problema

Fai degli esperimenti con la tua app e nota che gli eventi del ciclo di vita che si verificano in risposta ad azioni diverse. In particolare, prova a procedere nel seguente modo:

  • Utilizza l'app normalmente (invia un messaggio, rispondi con un altro messaggio).
  • Utilizza il pulsante Indietro per tornare dalla seconda attività a quella principale.
  • Utilizza la Freccia su nella barra delle app per tornare dalla seconda attività a quella principale.
  • Ruota il dispositivo sulla seconda Attività principale e sulla seconda Attività in momenti diversi nell'app e osserva cosa succede nel log e sullo schermo.
  • Premi il pulsante Panoramica (il pulsante quadrato a destra della schermata Home) e chiudi l'app (tocca la X).
  • Torna alla schermata Home e riavvia l'app.

SUGGERIMENTO: se esegui l'app in un emulatore, puoi simulare la rotazione premendo Ctrl+F11 o Ctrl+Funzione+F11.

Codice soluzione attività 1

I seguenti snippet di codice mostrano il codice della soluzione per la prima attività.

MainActivity

I seguenti snippet di codice mostrano il codice aggiunto in MainActivity, ma non l'intera classe.

Il metodo onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Log the start of the onCreate() method.
        Log.d(LOG_TAG, "-------");
        Log.d(LOG_TAG, "onCreate");

        // Initialize all the view variables.
        mMessageEditText = findViewById(R.id.editText_main);
        mReplyHeadTextView = findViewById(R.id.text_header_reply);
        mReplyTextView = findViewById(R.id.text_message_reply);
}

Gli altri metodi del ciclo di vita:

@Override
protected void onStart() {
        super.onStart();
        Log.d(LOG_TAG, "onStart");
}

@Override
protected void onPause() {
        super.onPause();
        Log.d(LOG_TAG, "onPause");
}

@Override
protected void onRestart() {
        super.onRestart();
        Log.d(LOG_TAG, "onRestart");
}

@Override
protected void onResume() {
        super.onResume();
        Log.d(LOG_TAG, "onResume");
}

@Override
protected void onStop() {
        super.onStop();
        Log.d(LOG_TAG, "onStop");
}

@Override
protected void onDestroy() {
        super.onDestroy();
        Log.d(LOG_TAG, "onDestroy");
}

SecondActivity

I seguenti snippet di codice mostrano il codice aggiunto in SecondActivity, ma non l'intera classe.

Nella parte superiore della classe SecondActivity:

private static final String LOG_TAG = SecondActivity.class.getSimpleName();

Il metodoreturnRispondi():

public void returnReply(View view) {
        String reply = mReply.getText().toString();
        Intent replyIntent = new Intent();
        replyIntent.putExtra(EXTRA_REPLY, reply);
        setResult(RESULT_OK, replyIntent);
        Log.d(LOG_TAG, "End SecondActivity");
        finish();
}

Gli altri metodi del ciclo di vita:

Come per MainActivity, sopra.

4. 4. Attività 2: salva e ripristina lo stato dell'istanza Attività

A seconda delle risorse di sistema e del comportamento degli utenti, ogni attività nella tua app potrebbe essere eliminata e ricostruita molto più spesso di quanto pensi.

Potresti aver notato questo comportamento nell'ultima sezione, quando hai ruotato il dispositivo o l'emulatore. La rotazione del dispositivo è un esempio di modifica della configurazione del dispositivo. Sebbene la rotazione sia quella più comune, tutte le modifiche alla configurazione comportano l'eliminazione e la nuova creazione dell'attività attuale come se fosse nuova. Se non tieni conto di questo comportamento nel codice, quando si verifica una modifica alla configurazione, il layout delle attività potrebbe ripristinare l'aspetto e i valori iniziali predefiniti e gli utenti potrebbero perdere la posizione, i dati o lo stato dei progressi nell'app.

Lo stato di ogni attività viene archiviato come insieme di coppie chiave/valore in un oggetto Bundle denominato stato dell'istanza dell'attività. Il sistema salva le informazioni sullo stato predefinito nel bundle di stato dell'istanza appena prima dell'interruzione dell'attività e passa il bundle alla nuova istanza dell'attività per il ripristino.

Per evitare di perdere dati in un'attività quando questa viene eliminata e ricreata inaspettatamente, è necessario implementare il metodo onSaveInstanceState(). Il sistema chiama questo metodo sulla tua Attività (tra onPause() e onStop()) quando c'è la possibilità che l'attività venga eliminata e ricreata.

I dati che salvi nello stato dell'istanza sono specifici solo per questa istanza di questa attività specifica durante la sessione corrente dell'app. Quando interrompi e riavvii una nuova sessione dell'app, lo stato dell'istanza dell'attività viene perso e l'attività torna all'aspetto predefinito. Se devi salvare i dati utente tra le sessioni dell'app, utilizza le preferenze condivise o un database. Parleremo di entrambi in una sezione successiva.

2.1 Salva lo stato dell'istanza dell'attività con onSaveInstanceState()

Avrai notato che la rotazione del dispositivo non influisce affatto sullo stato della seconda attività. Questo perché il secondo layout e stato dell'attività vengono generati dal layout e dall'intent che lo ha attivato. Anche se l'attività viene ricreata, l'intent è ancora presente e i dati al suo interno vengono comunque utilizzati ogni volta che viene chiamato il metodo onCreate() nella seconda attività.

Inoltre, potresti notare che in ogni Attività il testo digitato negli elementi EditText dei messaggi o delle risposte viene conservato anche quando il dispositivo viene ruotato. Questo perché le informazioni sullo stato di alcuni elementi View del layout vengono salvate automaticamente durante le modifiche alla configurazione e il valore corrente di un elemento EditText è uno di questi casi.

Quindi l'unico stato dell'attività che ti interessa sono gli elementi TextView per l'intestazione della risposta e il testo della risposta nell'attività principale. Entrambi gli elementi TextView sono invisibili per impostazione predefinita, ma vengono visualizzati solo dopo aver inviato di nuovo un messaggio all'attività principale dalla seconda attività.

In questa attività aggiungi codice per mantenere lo stato dell'istanza di questi due elementi TextView utilizzando onSaveInstanceState().

  1. Apri MainActivity.
  2. Aggiungi questa implementazione di schema di onSaveInstanceState() all'attività oppure utilizza Codice > Metodi di override per inserire un override dello scheletro.
@Override
public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
}
  1. Controlla se l'intestazione è attualmente visibile e, in questo caso, inserisci lo stato di visibilità nel pacchetto di stato con il metodo putBoolean() e la chiave "reply_visible".
 if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
        outState.putBoolean("reply_visible", true);
    }

Ricorda che l'intestazione e il testo della risposta sono contrassegnati come invisibili fino a quando non arriva una risposta dalla seconda attività. Se l'intestazione è visibile, significa che esistono dati sulle risposte che devono essere salvati. Tieni presente che ci interessa solo lo stato di visibilità: non è necessario salvare il testo effettivo dell'intestazione, perché questo testo non cambia.

  1. All'interno dello stesso controllo, aggiungi il testo della risposta al bundle.
outState.putString("reply_text",mReplyTextView.getText().toString());

Se l'intestazione è visibile, puoi presumere che lo sia anche il messaggio di risposta stesso. Non è necessario verificare o salvare lo stato di visibilità attuale del messaggio di risposta. Solo il testo effettivo del messaggio va nel bundle di stato con la chiave "reply_text".

Salva solo lo stato degli elementi View che potrebbero cambiare dopo la creazione dell'attività. Gli altri elementi View dell'app (EditText, Button) possono essere ricreati dal layout predefinito in qualsiasi momento.

Tieni presente che il sistema salverà lo stato di alcuni elementi View, ad esempio i contenuti dell'elemento EditText.

2.2 Ripristina lo stato dell'istanza dell'attività in onCreate()

Una volta salvato lo stato dell'istanza dell'attività, dovrai ripristinarlo anche quando l'attività viene ricreata. Puoi farlo in onCreate() o implementando il callback on robustInstanceState(), che viene richiamato dopo onStart() dopo la creazione dell'attività.

La maggior parte delle volte il modo migliore per ripristinare lo stato dell'attività è onCreate(), per garantire che l'interfaccia utente, incluso lo stato, sia disponibile il prima possibile. A volte è pratico eseguire questa operazione in onRipristinaInstanceState() dopo che l'inizializzazione è completata o consentire alle sottoclassi di decidere se utilizzare l'implementazione predefinita.

  1. Nel metodo onCreate(), dopo che le variabili View sono state inizializzate con findViewById(), aggiungi un test per assicurarti che savedInstanceState non sia null.
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);

// Restore the state.
if (savedInstanceState != null) {
}

Quando viene creata l'attività, il sistema passa il bundle di stato a onCreate() come unico argomento. La prima volta che onCreate() viene chiamato e l'app viene avviata, il bundle è nullo: non esiste uno stato esistente al primo avvio dell'app. Le chiamate successive a onCreate() prevedono un bundle compilato con i dati archiviati in onSaveInstanceState().

  1. All'interno di questo controllo, recupera la visibilità corrente (true o false) dal bundle con la chiave "reply_visible".
if (savedInstanceState != null) {
    boolean isVisible = 
                     savedInstanceState.getBoolean("reply_visible");
}
  1. Aggiungi un test sotto la riga precedente per la variabile isVisibile.
if (isVisible) {
}

Se nel bundle dello stato è presente una chiave Reply_visible (e di conseguenza isVisibile è true), dovrai ripristinare lo stato.

  1. All'interno del test isVisibile, rendi visibile l'intestazione.
mReplyHeadTextView.setVisibility(View.VISIBLE);
  1. Ottieni il messaggio di risposta dal bundle con la chiave "reply_text" e imposta il campo TextView di risposta in modo che mostri la stringa.
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
  1. Rendi visibile anche la risposta TextView:
mReplyTextView.setVisibility(View.VISIBLE);
  1. Esegui l'app. Prova a ruotare il dispositivo o l'emulatore per assicurarti che il messaggio di risposta (se presente) rimanga sullo schermo dopo la nuova attività dell'attività.

Codice soluzione attività 2

I seguenti snippet di codice mostrano il codice della soluzione per questa attività.

MainActivity

I seguenti snippet di codice mostrano il codice aggiunto in MainActivity, ma non l'intera classe.

Il metodo onSaveInstanceState():

@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   // If the heading is visible, message needs to be saved.
   // Otherwise we're still using default layout.
   if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
       outState.putBoolean("reply_visible", true);
       outState.putString("reply_text", 
                      mReplyTextView.getText().toString());
   }
}

Il metodo onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   Log.d(LOG_TAG, "-------");
   Log.d(LOG_TAG, "onCreate");

   // Initialize all the view variables.
   mMessageEditText = findViewById(R.id.editText_main);
   mReplyHeadTextView = findViewById(R.id.text_header_reply);
   mReplyTextView = findViewById(R.id.text_message_reply);

   // Restore the saved state. 
   // See onSaveInstanceState() for what gets saved.
   if (savedInstanceState != null) {
       boolean isVisible = 
                     savedInstanceState.getBoolean("reply_visible");
       // Show both the header and the message views. If isVisible is
       // false or missing from the bundle, use the default layout.
       if (isVisible) {
           mReplyHeadTextView.setVisibility(View.VISIBLE);
           mReplyTextView.setText(savedInstanceState
                                  .getString("reply_text"));
           mReplyTextView.setVisibility(View.VISIBLE);
       }
   }
}

Il progetto completo:

Progetto Android Studio: TwoActivitiesLifecycle

5. Codifica

Sfida: crea una semplice app per la lista della spesa con un'attività principale per la lista che l'utente sta creando e una seconda attività per un elenco degli acquisti più comuni.

  • L'attività principale deve contenere l'elenco da creare, che dovrebbe essere composto da dieci elementi TextView vuoti.
  • Un pulsante Aggiungi articolo sull'attività principale avvia una seconda attività contenente un elenco di articoli comuni per la spesa (formaggio, riso, mele e così via). Utilizza gli elementi Pulsante per visualizzare gli elementi.
  • La scelta di un elemento riporta l'utente all'attività principale e aggiorna un TextView vuoto per includere l'elemento scelto.

Utilizzare un intent per trasferire informazioni da un'attività all'altra. Assicurati che lo stato corrente della lista della spesa venga salvato quando l'utente ruota il dispositivo.

6. Riepilogo

  • Il ciclo di vita dell'attività è un insieme di stati attraverso cui viene eseguita la migrazione di un'attività, a partire dalla sua creazione fino al momento in cui il sistema Android recupera le risorse per quell'attività.
  • Quando l'utente passa da un'attività all'altra, all'interno e all'esterno della tua app, ogni attività si sposta da uno stato all'altro nel suo ciclo di vita.
  • A ogni stato del ciclo di vita delle attività corrisponde un metodo di callback che puoi sostituire nella classe Attività.
  • I metodi del ciclo di vita sono onCreate(), onStart(), onPause(), onRiavvia(), onRiprendi(), onStop(), onDestroy().
  • L'override di un metodo di callback del ciclo di vita ti consente di aggiungere un comportamento che si verifica quando l'attività passa a quello stato.
  • Puoi aggiungere metodi di override dello schema ai tuoi corsi in Android Studio tramite Codice > Override.
  • Le modifiche alla configurazione del dispositivo, ad esempio la rotazione, comportano l'eliminazione e la nuova creazione dell'attività come se fosse nuova.
  • Una parte dello stato dell'attività viene conservata durante una modifica alla configurazione, inclusi i valori correnti degli elementi EditText. Per tutti gli altri dati, dovrai salvarli esplicitamente tu.
  • Salva lo stato dell'istanza dell'attività nel metodo onSaveInstanceState().
  • I dati sullo stato dell'istanza vengono archiviati come semplici coppie chiave/valore in un bundle. Utilizza i metodi dei bundle per inserire e recuperare i dati dal bundle.
  • Ripristina lo stato dell'istanza in onCreate(), che è il metodo preferito, o on careInstanceState(). Indietro