Android Kotlin Fundamentals 04.2: Complex lifecycle situations

Questo codelab fa parte del corso Android Kotlin Fundamentals. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Android Kotlin Fundamentals.

Introduzione

Nell'ultimo codelab hai imparato a conoscere i cicli di vita di Activity e Fragment e hai esplorato i metodi chiamati quando lo stato del ciclo di vita cambia in attività e fragment. In questo codelab, esplorerai il ciclo di vita dell'attività in modo più dettagliato. Scopri anche la libreria del ciclo di vita di Android Jetpack, che può aiutarti a gestire gli eventi del ciclo di vita con un codice meglio organizzato e più facile da gestire.

Cosa devi già sapere

  • Che cos'è un'attività e come crearne una nella tua app.
  • Le nozioni di base dei cicli di vita di Activity e Fragment e i callback richiamati quando un'attività passa da uno stato all'altro.
  • Come eseguire l'override dei metodi di callback del ciclo di vita onCreate() e onStop() per eseguire operazioni in momenti diversi del ciclo di vita dell'attività o del frammento.

Cosa imparerai a fare

  • Come configurare, avviare e arrestare parti dell'app nei callback del ciclo di vita.
  • Come utilizzare la libreria del ciclo di vita di Android per creare un osservatore del ciclo di vita e semplificare la gestione del ciclo di vita di attività e frammenti.
  • In che modo gli arresti anomali di Android influiscono sui dati della tua app e come salvare e ripristinare automaticamente questi dati quando Android chiude l'app.
  • In che modo la rotazione del dispositivo e altre modifiche alla configurazione creano modifiche agli stati del ciclo di vita e influiscono sullo stato dell'app.

Attività previste

  • Modifica l'app DessertClicker in modo da includere una funzione timer e avvia e interrompi il timer in vari momenti del ciclo di vita dell'attività.
  • Modifica l'app per utilizzare la libreria del ciclo di vita Android e converti la classe DessertTimer in un osservatore del ciclo di vita.
  • Configura e utilizza Android Debug Bridge (adb) per simulare l'arresto del processo della tua app e i callback del ciclo di vita che si verificano in quel momento.
  • Implementa il metodo onSaveInstanceState() per conservare i dati delle app che potrebbero essere persi se l'app viene chiusa inaspettatamente. Aggiungi il codice per ripristinare i dati quando l'app viene riavviata.

In questo codelab, espandi l'app DessertClicker del codelab precedente. Aggiungi un timer in background, poi converti l'app per utilizzare la libreria del ciclo di vita di Android.

Nel codelab precedente hai imparato a osservare i cicli di vita di attività e fragment eseguendo l'override di vari callback del ciclo di vita e registrando quando il sistema richiama questi callback. In questa attività, esplorerai un esempio più complesso di gestione delle attività del ciclo di vita nell'app DessertClicker. Utilizzerai un timer che stampa un'istruzione di log ogni secondo, con il conteggio del numero di secondi di esecuzione.

Passaggio 1: configura DessertTimer

  1. Apri l'app DessertClicker dell'ultimo codelab. Se non hai l'app, puoi scaricare DessertClickerLogs qui.
  2. Nella visualizzazione Progetto, espandi java > com.example.android.dessertclicker e apri DessertTimer.kt. Nota che al momento tutto il codice è commentato, quindi non viene eseguito come parte dell'app.
  3. Seleziona tutto il codice nella finestra dell'editor. Seleziona Codice > Commento con commento di riga o premi Control+/ (Command+/ su Mac). Questo comando rimuove il commento da tutto il codice nel file. Android Studio potrebbe mostrare errori di riferimento non risolti finché non ricompili l'app.
  4. Tieni presente che la classe DessertTimer include startTimer() e stopTimer(), che avviano e arrestano il timer. Quando startTimer() è in esecuzione, il timer stampa un messaggio di log ogni secondo, con il conteggio totale dei secondi di esecuzione. Il metodo stopTimer(), a sua volta, interrompe il timer e le istruzioni di log.
  1. Apri MainActivity.kt. Nella parte superiore della classe, appena sotto la variabile dessertsSold, aggiungi una variabile per il timer:
private lateinit var dessertTimer : DessertTimer;
  1. Scorri verso il basso fino a onCreate() e crea un nuovo oggetto DessertTimer subito dopo la chiamata a setOnClickListener():
dessertTimer = DessertTimer()


Ora che hai un oggetto timer per il dolce, valuta dove dovresti avviare e interrompere il timer per farlo funzionare solo quando l'attività è sullo schermo. Ne esaminerai alcune nei passaggi successivi.

Passaggio 2: avvia e interrompi il timer

Il metodo onStart() viene chiamato appena prima che l'attività diventi visibile. Il metodo onStop() viene chiamato dopo che l'attività non è più visibile. Questi callback sembrano buoni candidati per l'avvio e l'interruzione del timer.

  1. Nella classe MainActivity, avvia il timer nel callback onStart():
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. Interrompi il timer tra onStop():
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. Compila ed esegui l'app. In Android Studio, fai clic sul riquadro Logcat. Nella casella di ricerca Logcat, inserisci dessertclicker, che filtrerà in base alle classi MainActivity e DessertTimer. Tieni presente che una volta avviata l'app, il timer inizia a funzionare immediatamente.
  2. Fai clic sul pulsante Indietro e noterai che il timer si ferma di nuovo. Il timer si interrompe perché sia l'attività che il timer che controlla sono stati eliminati.
  3. Usa la schermata Recenti per tornare all'app. In Logcat, noterai che il timer riparte da 0.
  4. Fai clic sul pulsante Condividi. In Logcat noterai che il timer è ancora in esecuzione.

  5. Fai clic sul pulsante Home. Nota in Logcat che il timer smette di funzionare.
  6. Usa la schermata Recenti per tornare all'app. Nota in Logcat che il timer riprende da dove era stato interrotto.
  7. In MainActivity, nel metodo onStop(), commenta la chiamata a stopTimer(). Il commento stopTimer() mostra il caso in cui avvii un'operazione in onStart(), ma dimentichi di interromperla di nuovo in onStop().
  8. Compila ed esegui l'app, quindi fai clic sul pulsante Home dopo l'avvio del timer. Anche se l'app è in background, il timer è in esecuzione e utilizza continuamente le risorse di sistema. Se il timer continua a funzionare, si verifica una perdita di memoria per l'app e probabilmente non è il comportamento che vuoi.

    Il modello generale prevede che quando configuri o avvii qualcosa in un callback, lo interrompi o lo rimuovi nel callback corrispondente. In questo modo, eviti di avere qualcosa in esecuzione quando non è più necessario.
  1. Rimuovi il commento dalla riga in onStop() in cui interrompi il timer.
  2. Taglia e incolla la chiamata startTimer() da onStart() a onCreate(). Questa modifica mostra il caso in cui inizializzi e avvii una risorsa in onCreate(), anziché utilizzare onCreate() per inizializzarla e onStart() per avviarla.
  3. Compila ed esegui l'app. Nota che il timer inizia a funzionare, come previsto.
  4. Fai clic su Home per arrestare l'app. Il timer si interrompe, come previsto.
  5. Utilizza la schermata Recenti per tornare all'app. Tieni presente che il timer non viene riavviato in questo caso, perché onCreate() viene chiamato solo all'avvio dell'app, non quando un'app torna in primo piano.

Punti chiave da ricordare:

  • Quando configuri una risorsa in un callback del ciclo di vita, smontala.
  • Esegui la configurazione e l'eliminazione nei metodi corrispondenti.
  • Se configuri qualcosa in onStart(), interrompilo o rimuovilo di nuovo in onStop().

Nell'app DessertClicker è abbastanza facile capire che se hai avviato il timer in onStart(), devi arrestarlo in onStop(). C'è un solo timer, quindi non è difficile ricordarsi come interromperlo.

In un'app per Android più complessa, potresti configurare molte cose in onStart() o onCreate(), per poi smantellarle in onStop() o onDestroy(). Ad esempio, potresti avere animazioni, musica, sensori o timer che devi configurare e smontare, avviare e interrompere. Se ne dimentichi una, si verificano bug e problemi.

La libreria del ciclo di vita, che fa parte di Android Jetpack, semplifica questa attività. La libreria è particolarmente utile nei casi in cui devi monitorare molte parti mobili, alcune delle quali si trovano in stati del ciclo di vita diversi. La libreria inverte il funzionamento dei cicli di vita: di solito l'attività o il fragment indica a un componente (ad esempio DessertTimer) cosa fare quando si verifica un callback del ciclo di vita. Tuttavia, quando utilizzi la libreria del ciclo di vita, il componente stesso monitora le modifiche del ciclo di vita e poi esegue le operazioni necessarie quando si verificano queste modifiche.

La libreria del ciclo di vita è composta da tre parti principali:

  • Proprietari del ciclo di vita, ovvero i componenti che hanno (e quindi "possiedono") un ciclo di vita. Activity e Fragment sono proprietari del ciclo di vita. I proprietari del ciclo di vita implementano l'interfaccia LifecycleOwner.
  • La classe Lifecycle, che contiene lo stato effettivo di un proprietario del ciclo di vita e attiva eventi quando si verificano modifiche del ciclo di vita.
  • Osservatori del ciclo di vita, che osservano lo stato del ciclo di vita ed eseguono attività quando il ciclo di vita cambia. Gli osservatori del ciclo di vita implementano l'interfaccia LifecycleObserver.

In questa attività, converti l'app DessertClicker in modo che utilizzi la libreria del ciclo di vita Android e scopri come la libreria semplifica la gestione dei cicli di vita di attività e frammenti Android.

Passaggio 1: trasforma DessertTimer in un LifecycleObserver

Un aspetto fondamentale della libreria del ciclo di vita è il concetto di osservazione del ciclo di vita. L'osservazione consente alle classi (ad esempio DessertTimer) di conoscere il ciclo di vita dell'attività o del fragment e di avviarsi e arrestarsi in risposta alle modifiche di questi stati del ciclo di vita. Con un osservatore del ciclo di vita, puoi rimuovere la responsabilità di avviare e arrestare gli oggetti dai metodi di attività e frammento.

  1. Apri il corso DesertTimer.kt.
  2. Modifica la firma della classe DessertTimer in modo che sia simile a questa:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Questa nuova definizione di classe svolge due funzioni:

  • Il costruttore accetta un oggetto Lifecycle, ovvero il ciclo di vita che il timer sta osservando.
  • La definizione della classe implementa l'interfaccia LifecycleObserver.
  1. Sotto la variabile runnable, aggiungi un blocco init alla definizione della classe. Nel blocco init, utilizza il metodo addObserver() per connettere l'oggetto ciclo di vita passato dal proprietario (l'attività) a questa classe (l'osservatore).
 init {
   lifecycle.addObserver(this)
}
  1. Annota startTimer() con @OnLifecycleEvent annotation e utilizza l'evento del ciclo di vita ON_START. Tutti gli eventi del ciclo di vita che l'osservatore del ciclo di vita può osservare si trovano nella classe Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Fai la stessa cosa per stopTimer(), utilizzando l'evento ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Passaggio 2: modifica MainActivity

La tua classe MainActivity è già proprietaria del ciclo di vita per ereditarietà, perché la superclasse FragmentActivity implementa LifecycleOwner. Pertanto, non devi fare nulla per rendere il ciclo di vita dell'attività consapevole. Tutto ciò che devi fare è passare l'oggetto del ciclo di vita dell'attività al costruttore DessertTimer.

  1. Apri MainActivity. Nel metodo onCreate(), modifica l'inizializzazione di DessertTimer in modo da includere this.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)

La proprietà lifecycle dell'attività contiene l'oggetto Lifecycle di proprietà di questa attività.

  1. Rimuovi la chiamata a startTimer() in onCreate() e la chiamata a stopTimer() in onStop(). Non devi più dire a DessertTimer cosa fare nell'attività, perché ora DessertTimer osserva il ciclo di vita e riceve una notifica automatica quando lo stato del ciclo di vita cambia. In questi callback, tutto ciò che devi fare è registrare un messaggio.
  2. Compila ed esegui l'app e apri Logcat. Nota che il timer è stato avviato, come previsto.
  3. Fai clic sul pulsante Home per mettere l'app in background. Nota che il timer ha smesso di funzionare, come previsto.

Cosa succede alla tua app e ai relativi dati se Android la chiude mentre è in background? È importante comprendere questo caso limite complesso.

Quando la tua app passa in background, non viene eliminata, ma solo interrotta e in attesa che l'utente torni a utilizzarla. Tuttavia, una delle principali preoccupazioni del sistema operativo Android è garantire il corretto funzionamento dell'attività in primo piano. Ad esempio, se l'utente utilizza un'app GPS per aiutarlo a prendere un autobus, è importante eseguire il rendering dell'app GPS rapidamente e continuare a mostrare le indicazioni. È meno importante mantenere in esecuzione senza problemi in background l'app DessertClicker, che l'utente potrebbe non aver utilizzato per alcuni giorni.

Android regola le app in background in modo che l'app in primo piano possa essere eseguita senza problemi. Ad esempio, Android limita la quantità di elaborazione che le app in esecuzione in background possono eseguire.

A volte Android chiude persino un intero processo dell'app, che include ogni attività associata all'app. Android esegue questo tipo di chiusura quando il sistema è sotto stress e rischia di subire ritardi visivi, quindi a questo punto non vengono eseguiti callback o codice aggiuntivi. Il processo dell'app viene semplicemente chiuso, silenziosamente, in background. Tuttavia, per l'utente non sembra che l'app sia stata chiusa. Quando l'utente torna a un'app che il sistema operativo Android ha chiuso, Android la riavvia.

In questa attività, simulerai l'arresto di un processo Android ed esaminerai cosa succede alla tua app quando viene riavviata.

Passaggio 1: utilizza adb per simulare l'arresto di un processo

Android Debug Bridge (adb) è uno strumento a riga di comando che ti consente di inviare istruzioni agli emulatori e ai dispositivi collegati al computer. In questo passaggio, utilizzi adb per chiudere il processo dell'app e vedere cosa succede quando Android arresta l'app.

  1. Compila ed esegui l'app. Fai clic sulla tortina alcune volte.
  2. Premi il pulsante Home per mettere l'app in background. L'app è ora arrestata e potrebbe essere chiusa se Android ha bisogno delle risorse che sta utilizzando.
  3. In Android Studio, fai clic sulla scheda Terminale per aprire il terminale della riga di comando.
  4. Digita adb e premi Invio.

    Se vedi molti output che iniziano con Android Debug Bridge version X.XX.X e terminano con tags to be used by logcat (see logcat —help), tutto va bene. Se invece vedi adb: command not found, assicurati che il comando adb sia disponibile nel percorso di esecuzione. Per istruzioni, consulta la sezione "Aggiungere adb al percorso di esecuzione" nel capitolo Utilità.
  5. Copia e incolla questo commento nella riga di comando e premi Invio:
adb shell am kill com.example.android.dessertclicker

Questo comando indica a tutti i dispositivi o emulatori connessi di interrompere il processo con il nome del pacchetto dessertclicker, ma solo se l'app è in background. Poiché l'app era in background, sullo schermo del dispositivo o dell'emulatore non viene visualizzato nulla che indichi che il processo è stato interrotto. In Android Studio, fai clic sulla scheda Esegui per visualizzare il messaggio "Applicazione terminata". Fai clic sulla scheda Logcat per vedere che il callback onDestroy() non è mai stato eseguito e che la tua attività è semplicemente terminata.

  1. Utilizza la schermata Recenti per tornare all'app. L'app viene visualizzata in Recenti sia che sia stata messa in background sia che sia stata interrotta del tutto. Quando utilizzi la schermata Recenti per tornare all'app, l'attività viene riavviata. L'attività passa attraverso l'intero insieme di callback del ciclo di vita di avvio, incluso onCreate().
  2. Nota che quando l'app è stata riavviata, il "punteggio" (sia il numero di dolci venduti sia il totale in dollari) è stato ripristinato ai valori predefiniti (0). Se Android ha chiuso la tua app, perché non ha salvato il suo stato?

    Quando il sistema operativo riavvia l'app, Android fa del suo meglio per ripristinarne lo stato precedente. Android prende lo stato di alcune delle tue visualizzazioni e lo salva in un bundle ogni volta che esci dall'attività. Alcuni esempi di dati salvati automaticamente sono il testo in un EditText (purché abbia un ID impostato nel layout) e lo stack indietro dell'attività.

    Tuttavia, a volte il sistema operativo Android non conosce tutti i tuoi dati. Ad esempio, se nell'app DessertClicker hai una variabile personalizzata come revenue, il sistema operativo Android non conosce questi dati né la loro importanza per la tua attività. Devi aggiungere questi dati al bundle manualmente.

Passaggio 2: utilizza onSaveInstanceState() per salvare i dati del bundle

Il metodo onSaveInstanceState() è il callback che utilizzi per salvare i dati che potrebbero servirti se il sistema operativo Android distrugge la tua app. Nel diagramma dei callback del ciclo di vita, onSaveInstanceState() viene chiamato dopo l'interruzione dell'attività. Viene chiamato ogni volta che la tua app passa in background.

Considera la chiamata onSaveInstanceState() come una misura di sicurezza: ti dà la possibilità di salvare una piccola quantità di informazioni in un bundle quando la tua attività esce dal primo piano. Il sistema salva questi dati ora perché, se aspettasse di chiudere l'app, il sistema operativo potrebbe essere sotto pressione delle risorse. Il salvataggio dei dati ogni volta garantisce che i dati di aggiornamento nel bundle siano disponibili per il ripristino, se necessario.

  1. In MainActivity, esegui l'override del callback onSaveInstanceState() e aggiungi un'istruzione di log Timber.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. Compila ed esegui l'app, quindi fai clic sul pulsante Home per metterla in background. Tieni presente che il callback onSaveInstanceState() si verifica subito dopo onPause() e onStop():
  2. All'inizio del file, appena prima della definizione della classe, aggiungi queste costanti:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

Utilizzerai queste chiavi sia per salvare che per recuperare i dati dal bundle dello stato dell'istanza.

  1. Scorri verso il basso fino a onSaveInstanceState() e nota il parametro outState, di tipo Bundle.

    Un bundle è una raccolta di coppie chiave-valore, in cui le chiavi sono sempre stringhe. Puoi inserire valori primitivi, come valori int e boolean, nel bundle.
    Poiché il sistema mantiene questo bundle nella RAM, è consigliabile mantenere i dati nel bundle di piccole dimensioni. Anche le dimensioni di questo bundle sono limitate, anche se variano da dispositivo a dispositivo. In genere, dovresti memorizzare molto meno di 100.000 elementi, altrimenti rischi di arrestare l'app con l'errore TransactionTooLargeException.
  2. In onSaveInstanceState(), inserisci il valore revenue (un numero intero) nel bundle con il metodo putInt():
outState.putInt(KEY_REVENUE, revenue)

Il metodo putInt() (e metodi simili della classe Bundle come putFloat() e putString()) accetta due argomenti: una stringa per la chiave (la costante KEY_REVENUE) e il valore effettivo da salvare.

  1. Ripeti la stessa procedura con il numero di dolci venduti e lo stato del timer:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Passaggio 3: utilizza onCreate() per ripristinare i dati del bundle

  1. Scorri verso l'alto fino a onCreate() ed esamina la firma del metodo:
override fun onCreate(savedInstanceState: Bundle) {

Tieni presente che onCreate() riceve un Bundle ogni volta che viene chiamato. Quando l'attività viene riavviata a causa dell'arresto di un processo, il bundle che hai salvato viene passato a onCreate(). Se la tua attività è iniziata da poco, questo pacchetto in onCreate() è null. Quindi, se il bundle non è null, sai che stai "ricreando " l'attività da un punto noto in precedenza.

  1. Aggiungi questo codice a onCreate(), dopo la configurazione di DessertTimer:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

Il test per null determina se sono presenti dati nel bundle o se il bundle è null, il che a sua volta indica se l'app è stata avviata da zero o è stata ricreata dopo un arresto. Questo test è un pattern comune per il ripristino dei dati dal bundle.

Nota che la chiave che hai utilizzato qui (KEY_REVENUE) è la stessa che hai utilizzato per putInt(). Per assicurarti di utilizzare la stessa chiave ogni volta, la best practice consiste nel definire queste chiavi come costanti. Utilizzi getInt() per estrarre i dati dal bundle, proprio come hai utilizzato putInt() per inserirli. Il metodo getInt() accetta due argomenti:

  • Una stringa che funge da chiave, ad esempio "key_revenue" per il valore delle entrate.
  • Un valore predefinito nel caso in cui non esista un valore per quella chiave nel bundle.

L'intero ottenuto dal bundle viene quindi assegnato alla variabile revenue e l'interfaccia utente utilizzerà questo valore.

  1. Aggiungi i metodi getInt() per ripristinare il numero di dolci venduti e il valore del timer:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. Compila ed esegui l'app. Premi la tortina almeno cinque volte finché non si trasforma in una ciambella. Fai clic su Home per mettere l'app in background.
  2. Nella scheda Terminale di Android Studio, esegui adb per arrestare il processo dell'app.
adb shell am kill com.example.android.dessertclicker
  1. Utilizza la schermata Recenti per tornare all'app. Nota che questa volta l'app torna con i valori corretti di entrate e dolci venduti del bundle. Ma nota anche che il dolce è tornato a essere un cupcake. Per assicurarti che l'app venga ripristinata dopo l'arresto esattamente come è stata lasciata, devi fare un'altra cosa.
  2. In MainActivity, esamina il metodo showCurrentDessert(). Tieni presente che questo metodo determina quale immagine del dolce deve essere visualizzata nell'attività in base al numero attuale di dolci venduti e all'elenco dei dolci nella variabile allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Questo metodo si basa sul numero di dolci venduti per scegliere l'immagine giusta. Pertanto, non devi fare nulla per memorizzare un riferimento all'immagine nel bundle in onSaveInstanceState(). In questo pacchetto, memorizzi già il numero di dolci venduti.

  1. In onCreate(), nel blocco che ripristina lo stato dal bundle, chiama showCurrentDessert():
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. Compila ed esegui l'app e mettila in background. Usa adb per interrompere la procedura. Utilizza la schermata Recenti per tornare all'app. Nota che ora i valori per i dolci venduti, le entrate totali e l'immagine dei dolci sono stati ripristinati correttamente.

Esiste un ultimo caso speciale nella gestione del ciclo di vita di attività e frammenti che è importante comprendere: in che modo le modifiche alla configurazione influiscono sul ciclo di vita di attività e frammenti.

Una modifica della configurazione si verifica quando lo stato del dispositivo cambia in modo così radicale che il modo più semplice per il sistema di risolvere la modifica è spegnere completamente e ricreare l'attività. Ad esempio, se l'utente cambia la lingua del dispositivo, potrebbe essere necessario modificare l'intero layout per adattarsi a diverse direzioni del testo. Se l'utente collega il dispositivo a una base di ricarica o aggiunge una tastiera fisica, il layout dell'app potrebbe dover sfruttare una dimensione o un layout di visualizzazione diverso. Se l'orientamento del dispositivo cambia, ad esempio se viene ruotato dalla modalità verticale a quella orizzontale o viceversa, potrebbe essere necessario modificare il layout per adattarlo al nuovo orientamento.

Passaggio 1: esplora la rotazione del dispositivo e i callback del ciclo di vita

  1. Compila ed esegui l'app e apri Logcat.
  2. Ruota il dispositivo o l'emulatore in modalità orizzontale. Puoi ruotare l'emulatore a sinistra o a destra con i pulsanti di rotazione oppure con i tasti Control e Freccia (Command e Freccia su Mac).
  3. Esamina l'output in Logcat. Filtra l'output in base a MainActivity.
    Tieni presente che quando il dispositivo o l'emulatore ruota lo schermo, il sistema chiama tutti i callback del ciclo di vita per arrestare l'attività. Poi, quando l'attività viene ricreata, il sistema chiama tutti i callback del ciclo di vita per avviare l'attività.
  4. In MainActivity, commenta l'intero metodo onSaveInstanceState().
  5. Compila ed esegui di nuovo l'app. Fai clic sulla tortina alcune volte e ruota il dispositivo o l'emulatore. Questa volta, quando il dispositivo viene ruotato e l'attività viene chiusa e ricreata, l'attività viene avviata con i valori predefiniti.

    Quando si verifica una modifica alla configurazione, Android utilizza lo stesso bundle di stato dell'istanza che hai imparato nella sezione precedente per salvare e ripristinare lo stato dell'app. Come per l'arresto di un processo, utilizza onSaveInstanceState() per inserire i dati dell'app nel bundle. Poi ripristina i dati in onCreate() per evitare di perdere i dati sullo stato dell'attività se il dispositivo viene ruotato.
  6. In MainActivity, rimuovi il commento dal metodo onSaveInstanceState(), esegui l'app, fai clic sul cupcake e ruota l'app o il dispositivo. Nota che questa volta i dati sui dolci vengono conservati durante la rotazione delle attività.

Progetto Android Studio: DessertClickerFinal

Suggerimenti per il ciclo di vita

  • Se configuri o avvii qualcosa in un callback del ciclo di vita, interrompi o rimuovi l'elemento nel callback corrispondente. Interrompendo l'operazione, ti assicuri che non continui a essere eseguita quando non è più necessaria. Ad esempio, se imposti un timer in onStart(), devi metterlo in pausa o interromperlo in onStop().
  • Utilizza onCreate() solo per inizializzare le parti dell'app che vengono eseguite una sola volta, al primo avvio dell'app. Utilizza onStart() per avviare le parti dell'app che vengono eseguite sia all'avvio dell'app sia ogni volta che l'app torna in primo piano.

Raccolta del ciclo di vita

  • Utilizza la libreria del ciclo di vita di Android per spostare il controllo del ciclo di vita dall'attività o dal fragment al componente effettivo che deve essere consapevole del ciclo di vita.
  • I proprietari del ciclo di vita sono componenti che hanno (e quindi "possiedono") cicli di vita, inclusi Activity e Fragment. I proprietari del ciclo di vita implementano l'interfaccia LifecycleOwner.
  • Gli osservatori del ciclo di vita prestano attenzione allo stato attuale del ciclo di vita ed eseguono le attività quando il ciclo di vita cambia. Gli osservatori del ciclo di vita implementano l'interfaccia LifecycleObserver.
  • Lifecycle contengono gli stati del ciclo di vita effettivi e attivano eventi quando il ciclo di vita cambia.

Per creare una classe consapevole del ciclo di vita:

  • Implementa l'interfaccia LifecycleObserver nelle classi che devono essere consapevoli del ciclo di vita.
  • Inizializza una classe di osservatore del ciclo di vita con l'oggetto ciclo di vita dell'attività o del fragmento.
  • Nella classe dell'observer del ciclo di vita, annota i metodi sensibili al ciclo di vita con la modifica dello stato del ciclo di vita a cui sono interessati.

    Ad esempio, l'annotazione @OnLifecycleEvent(Lifecycle.Event.ON_START) indica che il metodo sta monitorando l'evento del ciclo di vita onStart.

Chiusure dei processi e salvataggio dello stato dell'attività

  • Android regola le app in esecuzione in background in modo che l'app in primo piano possa essere eseguita senza problemi. Questo regolamento prevede la limitazione della quantità di elaborazione che le app in background possono eseguire e, a volte, anche l'arresto dell'intero processo dell'app.
  • L'utente non può sapere se il sistema ha chiuso un'app in background. L'app viene comunque visualizzata nella schermata Recenti e dovrebbe riavviarsi nello stesso stato in cui l'utente l'ha lasciata.
  • Android Debug Bridge (adb) è uno strumento a riga di comando che ti consente di inviare istruzioni agli emulatori e ai dispositivi collegati al computer. Puoi utilizzare adb per simulare l'arresto di un processo nella tua app.
  • Quando Android arresta il processo dell'app, il metodo del ciclo di vita onDestroy() non viene chiamato. L'app si arresta.

Preservare lo stato di attività e frammenti

  • Quando l'app passa in background, subito dopo la chiamata di onStop(), i dati dell'app vengono salvati in un bundle. Alcuni dati delle app, ad esempio i contenuti di un EditText, vengono salvati automaticamente.
  • Il bundle è un'istanza di Bundle, che è una raccolta di chiavi e valori. Le chiavi sono sempre stringhe.
  • Utilizza il callback onSaveInstanceState() per salvare altri dati nel bundle che vuoi conservare, anche se l'app è stata chiusa automaticamente. Per inserire i dati nel bundle, utilizza i metodi del bundle che iniziano con put, ad esempio putInt().
  • Puoi estrarre i dati dal bundle con il metodo onRestoreInstanceState() o, più comunemente, con onCreate(). Il metodo onCreate() ha un parametro savedInstanceState che contiene il bundle.
  • Se la variabile savedInstanceState contiene null, l'attività è stata avviata senza un bundle di stato e non ci sono dati di stato da recuperare.
  • Per recuperare i dati dal bundle con una chiave, utilizza i metodi Bundle che iniziano con get, ad esempio getInt().

Modifiche alla configurazione

  • Una modifica della configurazione si verifica quando lo stato del dispositivo cambia in modo così radicale che il modo più semplice per il sistema di risolvere la modifica è spegnere e ricreare l'attività.
  • L'esempio più comune di modifica della configurazione si verifica quando l'utente ruota il dispositivo dalla modalità verticale a quella orizzontale o viceversa. Una modifica alla configurazione può verificarsi anche quando la lingua del dispositivo cambia o viene collegata una tastiera hardware.
  • Quando si verifica una modifica alla configurazione, Android richiama tutti i callback di arresto del ciclo di vita dell'attività. Android riavvia l'attività da zero, eseguendo tutti i callback di avvio del ciclo di vita.
  • Quando Android arresta un'app a causa di una modifica alla configurazione, riavvia l'attività con il bundle di stato disponibile per onCreate().
  • Come per l'arresto del processo, salva lo stato dell'app nel bundle in onSaveInstanceState().

Corso Udacity:

Documentazione per sviluppatori Android:

Altro:

Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:

  • Assegna i compiti, se richiesto.
  • Comunica agli studenti come inviare i compiti.
  • Valuta i compiti a casa.

Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.

Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.

Cambiare un'app

Apri l'app DiceRoller della lezione 1. Se non l'hai ancora installata, puoi scaricarla da qui. Compila ed esegui l'app e tieni presente che se ruoti il dispositivo, il valore corrente del dado viene perso. Implementa onSaveInstanceState() per conservare questo valore nel bundle e ripristinarlo in onCreate().

Rispondi a queste domande

Domanda 1

La tua app contiene una simulazione fisica che richiede un calcolo complesso per essere visualizzata. Poi l'utente riceve una telefonata. Quale delle seguenti affermazioni è vera?

  • Durante la chiamata, devi continuare a calcolare le posizioni degli oggetti nella simulazione fisica.
  • Durante la chiamata, devi interrompere il calcolo delle posizioni degli oggetti nella simulazione fisica.

Domanda 2

Quale metodo del ciclo di vita devi eseguire l'override per mettere in pausa la simulazione quando l'app non è sullo schermo?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

Domanda 3

Per rendere una classe consapevole del ciclo di vita tramite la libreria del ciclo di vita di Android, quale interfaccia deve implementare la classe?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Domanda 4

In quali circostanze il metodo onCreate() nella tua attività riceve un Bundle con dati (ovvero, Bundle non è null)? Potrebbe essere applicabile più di una risposta.

  • L'attività viene riavviata dopo la rotazione del dispositivo.
  • L'attività viene avviata da zero.
  • L'attività viene ripresa dopo il ritorno dal background.
  • Il dispositivo viene riavviato.

Inizia la lezione successiva: 5.1: ViewModel e ViewModelFactory

Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.