Android Kotlin Fundamentals 04.2: situazioni del ciclo di vita complesse

Questo codelab fa parte del corso Android Kotlin Fundamentals. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab. Tutti i codelab del corso sono elencati nella pagina di destinazione di Android Kotlin Fundamentals.

Introduzione

Nell'ultimo codelab, hai appreso i cicli di vita dei Activity e delle Fragment e hai esplorato i metodi chiamati quando lo stato del ciclo di vita cambia in attività e frammenti. In questo codelab puoi esplorare più in dettaglio il ciclo di vita dell'attività. Scopri anche la libreria del ciclo di vita di Android Jetpack, che può aiutarti a gestire eventi del ciclo di vita con un codice più organizzato e facile da gestire.

Cosa dovresti sapere

  • Che cos'è un'attività e come crearne una nell'app.
  • Nozioni di base sui cicli di vita Activity e Fragment e i callback che vengono richiamati quando un'attività si sposta tra gli stati.
  • Come sostituire i metodi di callback del ciclo di vita onCreate() e onStop() per eseguire operazioni in momenti diversi nell'attività o nel ciclo di vita dei frammenti.

Cosa imparerai a fare:

  • Come configurare, avviare e interrompere parti dell'app nel 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 le chiusure dei processi Android incidono sui dati presenti nella tua app e come salvare e ripristinare automaticamente tali dati alla chiusura dell'app.
  • Il modo in cui la rotazione del dispositivo e altre modifiche alla configurazione creano modifiche agli stati del ciclo di vita e influiscono sullo stato della tua app.

Attività previste

  • Modifica l'app DessertClicker per includere una funzione timer e avviare e interrompere il timer in vari momenti del ciclo di vita dell'attività.
  • Modifica l'app per utilizzare la libreria del ciclo di vita di 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 successivamente.
  • Implementa il metodo onSaveInstanceState() per conservare i dati dell'app che potrebbero andare persi se l'app viene chiusa inaspettatamente. Aggiungi codice per ripristinare questi dati all'avvio dell'app.

In questo codelab, si espande l'app DessertClicker del codelab precedente. Aggiungi un timer in background e poi converti l'app in modo che utilizzi la libreria del ciclo di vita di Android.

Nel codelab precedente, hai imparato come osservare i cicli di vita delle attività e dei frammenti sostituendo vari callback di ciclo di vita e registrando quando il sistema richiama i callback. In questa attività esplorerai un esempio più complesso di gestione delle attività del ciclo di vita nell'app DessertClicker. Userai un timer che stampa un'istruzione di log ogni secondo, con il numero di secondi di esecuzione.

Passaggio 1: configura DessertTimer

  1. Apri l'app DessertClicker dall'ultimo codelab. Se non hai l'app, scarica DessertClickerLogs qui.
  2. Nella visualizzazione Progetto, espandi java > com.example.android.dessertclicker e apri DessertTimer.kt. Tieni presente che ora tutto il codice è commentato, non viene eseguito nell'app.
  3. Seleziona tutto il codice nella finestra dell'editor. Seleziona Codice > Commenta con commento riga oppure premi Control+/ (Command+/ su un Mac). Questo comando annulla il commento di tutto il codice nel file. Android Studio potrebbe mostrare errori di riferimento non risolti finché non ricrei l'app.
  4. Tieni presente che la classe DessertTimer include startTimer() e stopTimer(), che avviano e interrompono il timer. Quando startTimer() è in esecuzione, il timer stampa un messaggio di log ogni secondo, con il numero totale di secondi trascorsi dall'esecuzione. A sua volta, il metodo stopTimer() interrompe il timer e le istruzioni dei log.
  1. Apri MainActivity.kt. Nella parte superiore del corso, 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 disponi di un oggetto timer del dessert, valuta da dove iniziare e interrompi il timer affinché venga eseguito solo quando l'attività è sullo schermo. Consulta alcune opzioni nei passaggi successivi.

Passaggio 2: avvia e interrompi il timer

Il metodo onStart() viene chiamato poco prima che l'attività diventi visibile. Il metodo onStop() viene chiamato dopo che l'attività smette di essere visibile. Questi callback sembrano essere i candidati giusti per quando avviare e interrompere il timer.

  1. Nel corso 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 di Logcat, inserisci dessertclicker, che filtra in base alle classi MainActivity e DessertTimer. Tieni presente che all'avvio dell'app anche il timer viene avviato immediatamente.
  2. Fai clic sul pulsante Indietro e nota che il timer si interrompe di nuovo. Il timer si interrompe perché sia l'attività sia il timer che hai controllato sono stati eliminati.
  3. Usa la schermata recente per tornare all'app. Noterai in Logcat che il timer viene riavviato da 0.
  4. Fai clic sul pulsante Condividi. Noterai in Logcat che il timer è ancora in esecuzione.

  5. Fai clic sul pulsante Home. Nota in Logcat che il timer viene interrotto.
  6. Usa la schermata recente per tornare all'app. Noterai in Logcat che il timer si è riavviato da dove si era interrotto.
  7. In MainActivity, nel metodo onStop(), commenta la chiamata al numero stopTimer(). L'aggiunta di commenti a stopTimer() dimostra il caso in cui avvii un'operazione in onStart(), ma ti dimentichi di interromperla di nuovo tra 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 continua a utilizzare le risorse di sistema. Avere un timer che continua a funzionare è una perdita di memoria per la tua app, e probabilmente non è il comportamento che vuoi.

    Lo schema generale consiste nel fatto che quando configuri o avvii qualcosa in un callback, interrompi o rimuovi l'elemento nel callback corrispondente. In questo modo, eviterai di pubblicare contenuti quando non sono più necessari.
  1. Rimuovi il commento dalla riga onStop() in cui interrompi il timer.
  2. Taglia e incolla la chiamata startTimer() da onStart() a onCreate(). Questa modifica dimostra 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. Tieni presente che il timer inizia a funzionare, come previsto.
  4. Fai clic su Home per interrompere l'app. Il timer viene interrotto come previsto.
  5. Utilizza la schermata recente 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 dopo quando un'app torna in primo piano.

Aspetti fondamentali da ricordare:

  • Quando configuri una risorsa in un callback di ciclo di vita, elimina anche la risorsa.
  • Esegui la configurazione e lo smantellamento nei metodi corrispondenti.
  • Se configuri qualcosa in onStart(), interrompi o arresta nuovamente la funzionalità in onStop().

Nell'app DessertClicker, è abbastanza facile notare che se hai avviato il timer in onStart(), devi interrompere il timer in onStop(). C'è un solo timer, quindi non è facile ricordarlo.

In un'app Android più complessa, puoi configurare molti elementi in onStart() o onCreate() e poi eliminarli tutti in onStop() o onDestroy(). Ad esempio, potresti avere animazioni, musica, sensori o timer che devi configurare e eliminare e iniziare e interrompere. Se te ne dimentichi uno, si verificano bug e mal di testa.

La raccolta del ciclo di vita, che fa parte di Android Jetpack, semplifica questa attività. La libreria è particolarmente utile nei casi in cui sia necessario monitorare molte parti mobili, alcune delle quali si trovano in stati del ciclo di vita diversi. La libreria capovolge il funzionamento dei cicli di vita: di solito l'attività o il frammento indica a un componente (come DessertTimer) cosa fare quando si verifica un callback del ciclo di vita. Tuttavia, se utilizzi la libreria del ciclo di vita, il componente stesso controlla le modifiche del ciclo di vita, quindi fa tutto ciò che è necessario quando si verificano tali cambiamenti.

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

  • proprietari del ciclo di vita, che sono i componenti che hanno un ciclo di vita (e quindi "autonomo"). Activity e Fragment sono proprietari del ciclo di vita. I proprietari del ciclo di vita implementano l'interfaccia LifecycleOwner.
  • La classe Lifecycle, che mantiene lo stato effettivo di un proprietario del ciclo di vita e attiva gli eventi quando si verificano cambiamenti durante il ciclo di vita.
  • Osservatori del ciclo di vita, che osservano lo stato del ciclo di vita ed eseguono attività al variare del ciclo di vita. Gli osservatori del ciclo di vita implementano l'interfaccia di LifecycleObserver.

In questa attività, convertirai l'app DessertClicker in modo che utilizzi la libreria del ciclo di vita di Android e scoprirai in che modo la libreria semplifica l'utilizzo dei cicli di vita delle attività e dei frammenti per Android.

Passaggio 1: trasforma DessertTimer in un LifecycleObserver

Una parte 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 l'attività o il ciclo di vita dei frammenti e di avviarsi e arrestarsi in risposta alle modifiche di tali stati del ciclo di vita. Con un osservatore del ciclo di vita puoi rimuovere la responsabilità di avviare e arrestare oggetti dai metodi attività e frammenti.

  1. Apri il corso DesertTimer.kt.
  2. Cambia la firma della classe DessertTimer in modo che abbia questo aspetto:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Questa nuova definizione di corso prevede due operazioni:

  • Il costruttore utilizza un oggetto Lifecycle, che è il ciclo di vita rilevato dal timer.
  • La definizione della classe implementa l'interfaccia LifecycleObserver.
  1. Sotto la variabile runnable, aggiungi un blocco init alla definizione della classe. Nel blocco init, usa il metodo addObserver() per connettere l'oggetto del 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 sono nella classe Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Esegui la stessa azione per stopTimer() utilizzando l'evento ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Passaggio 2: modifica MainActivity

La tua classe MainActivity è già un proprietario del ciclo di vita per ereditarietà, perché la superclasse FragmentActivity implementa LifecycleOwner. Pertanto, non devi intraprendere alcuna azione per rendere la tua attività sensibile al ciclo di vita. Devi solo passare l'oggetto del ciclo di vita dell'attività nel 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 al numero startTimer() in onCreate() e la chiamata al numero stopTimer() in onStop(). Non devi più dire a DessertTimer cosa fare nell'attività, perché ora DessertTimer osserva il ciclo di vita stesso e viene inviata una notifica automatica quando lo stato del ciclo di vita cambia. Ora, tutto quello che fai in questi callback è registrare un messaggio.
  2. Compila ed esegui l'app, quindi apri Logcat. Noterai che il timer è iniziato, come previsto.
  3. Fai clic sul pulsante Home per mettere l'app in background. Nota che il timer si è interrotto, come previsto.

Cosa succede alla tua app e ai suoi dati se Android la chiude mentre è in background? È importante comprendere questa complessa distinzione.

Quando la tua app passa in background, non viene eliminata, si interrompe e attende che l'utente torni. Ma una delle principali preoccupazioni del sistema operativo Android è il corretto funzionamento dell'attività in primo piano. Ad esempio, se un utente utilizza un'app GPS per aiutarlo a prendere un autobus, è importante rendere l'app GPS rapidamente e continuare a mostrare le indicazioni stradali. È meno importante mantenere l'app DessertClicker, che l'utente potrebbe non avere osservato per alcuni giorni, in esecuzione in background in modo ottimale.

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

Talvolta Android interrompe persino un intero processo dell'app, che include tutte le attività associate all'app. Android esegue questo tipo di arresto quando il sistema è stressato e rischia di mostrare visivamente ritardi, perciò al momento non viene eseguito nessun callback o codice aggiuntivi. La procedura dell'app viene semplicemente disattivata, silenziosamente, in background. Ma per l'utente non sembra che l'app sia stata chiusa. Quando l'utente torna a un'app arrestata dal sistema operativo Android, Android la riavvia.

In questa attività simula una chiusura dei processi Android ed esamina ciò che accade alla tua app all'avvio.

Passaggio 1: usa adb per simulare la chiusura di un processo

Android Debug Bridge (adb) è uno strumento a riga di comando che ti consente di inviare istruzioni a emulatori e dispositivi collegati al computer. In questo passaggio, utilizzerai adb per chiudere la procedura della tua app e vedere cosa succede quando Android chiude l'app.

  1. Compila ed esegui l'app. Fai clic sul cupcake alcune volte.
  2. Premi il pulsante Home per mettere la tua app in background. L'app è stata interrotta e l'app è soggetta a chiusura se Android ha bisogno delle risorse in uso.
  3. In Android Studio, fai clic sulla scheda Terminale per aprire il terminale a riga di comando.
  4. Digita adb e premi Invio.

    Se vedi molto output che inizia con Android Debug Bridge version X.XX.X e termina con tags to be used by logcat (see logcat —help) è tutto a posto. Se invece vedi adb: command not found, assicurati che il comando adb sia disponibile nel percorso di esecuzione. Per le istruzioni, vedi "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 qualsiasi dispositivo connesso o emulatore di interrompere il processo con il nome del pacchetto dessertclicker, ma solo se l'app è in background. La tua app era in background, pertanto sullo schermo dell'emulatore o del dispositivo non viene visualizzato alcun elemento per indicare che la procedura è stata interrotta. In Android Studio, fai clic sulla scheda Esegui per visualizzare il messaggio "Applicazione terminata". Fai clic sulla scheda Logcat per verificare che il callback onDestroy() non sia mai stato eseguito: la tua attività è appena terminata.

  1. Usa la schermata recente per tornare all'app. L'app è visualizzata di recente se è stata messa in background o se è stata interrotta. Quando usi la schermata delle recenti per tornare all'app, l'attività viene riavviata. L'attività passa attraverso l'intero insieme di callback di ciclo di vita di avvio, tra cui onCreate().
  2. Tieni presente che, al riavvio dell'app, viene reimpostato il "punteggio" (sia il numero di dessert venduti sia il dollaro totale) sui valori predefiniti (0). Se Android ha chiuso la tua app, perché non lo ha salvato?

    Quando il sistema operativo la riavvia, Android fa il possibile per ripristinare lo stato precedente. Android acquisisce lo stato di alcune visualizzazioni e le salva in un gruppo ogni volta che esci dall'attività. Alcuni esempi di dati che vengono salvati automaticamente sono il testo di un EditText (purché presenti un ID impostato nel layout) e lo stack posteriore dell'attività.

    A volte il sistema operativo Android non è a conoscenza di tutti i dati. Ad esempio, se hai una variabile personalizzata come revenue nell'app DessertClicker, il sistema operativo Android non è a conoscenza di questi dati o della sua importanza per la tua attività. Devi aggiungere personalmente questi dati al gruppo.

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

Il metodo onSaveInstanceState() è il callback che utilizzi per salvare tutti i dati di cui potresti avere bisogno se il sistema operativo Android distrugge la tua app. Nel diagramma di callback del ciclo di vita, onSaveInstanceState() viene chiamato dopo che l'attività è stata interrotta. Ogni volta che la tua app passa in background,

Pensa alla chiamata onSaveInstanceState() come a una misura di sicurezza: ti dà la possibilità di salvare una piccola quantità di informazioni in un gruppo quando l'attività va in primo piano. Il sistema salva questi dati ora perché, se aspettava di chiudere l'app, il sistema operativo potrebbe essere sotto pressione. Il salvataggio dei dati ogni volta assicura che i dati di aggiornamento del pacchetto siano disponibili per il ripristino, se necessario.

  1. In MainActivity, sostituisci il 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 posizionarla in background. Tieni presente che la richiamata a onSaveInstanceState() avviene subito dopo le onPause() e le onStop():
  2. Nella parte superiore del file, subito prima della definizione del corso, 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 sia per recuperare i dati dal bundle di stato dell'istanza.

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

    Un bundle è una raccolta di coppie chiave-valore in cui le chiavi sono sempre stringhe. È possibile inserire nel pacchetto valori primitivi, come i valori int e boolean.
    Dal momento che il sistema mantiene questo set di dati in RAM, è una best practice che consente di mantenere piccoli i dati del bundle. Anche le dimensioni di questo pacchetto sono limitate, anche se le dimensioni variano in base al dispositivo. In genere, dovresti archiviare meno di 100.000, altrimenti rischi di arrestare l'app con l'errore TransactionTooLargeException.
  2. In onSaveInstanceState(), inserisci il valore revenue (un numero intero) nel set con il metodo putInt():
outState.putInt(KEY_REVENUE, revenue)

Il metodo putInt() (e altri 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 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 di un arresto del processo, il bundle salvato viene trasmesso a onCreate(). Se la tua attività è iniziata da zero, questo bundle in onCreate() è null. Quindi, se il pacchetto non è null, sai che stai"ricreando"l'attività da un punto che in precedenza era noto.

  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 la presenza di dati nel pacchetto o se il pacchetto è null, il che a sua volta indica se l'app è stata riavviata o è stata ricreata dopo un arresto. Questo test è un modello comune per il ripristino di dati dal bundle.

Nota che la chiave che hai utilizzato qui (KEY_REVENUE) è la stessa che hai usato per putInt(). Per assicurarti di utilizzare la stessa chiave ogni volta, è consigliabile definire tali chiavi come costanti. Usi getInt() per estrarre i dati dal gruppo, proprio come hai fatto tu con putInt() per inserire dati nel gruppo. Il metodo getInt() prevede 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 alcun valore per la chiave nel bundle.

Il numero intero che ottieni dal bundle viene quindi assegnato alla variabile revenue e la UI lo utilizzerà.

  1. Aggiungi metodi getInt() per ripristinare il numero di dessert 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 il cupcake almeno cinque volte finché non passa a 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 delle recenti per tornare all'app. Nota che questa volta l'app restituisce i valori del prodotto venduti correttamente e le entrate corrette. Ma noti anche che il dessert è tornato a un cupcake. C'è ancora un'ultima cosa da fare per assicurarti che l'app torni da una chiusura esattamente come l'avevi lasciata.
  2. In MainActivity, esamina il metodo showCurrentDessert(). Nota che questo metodo determina quale immagine di dessert deve essere visualizzata nell'attività in base al numero corrente di dolci venduti e all'elenco dei dessert nella variabile allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Questo metodo si basa sul numero di dessert venduti per scegliere l'immagine giusta. Pertanto, non devi fare nulla per memorizzare un riferimento all'immagine nel bundle in onSaveInstanceState(). In questo set stai già conservando il numero di dessert 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, quindi inseriscila in background. Utilizza adb per arrestare la procedura. Usa la schermata recente per tornare all'app. Tieni presente che ora sono stati ripristinati sia i valori relativi ai dessert indicati, sia le entrate totali.

C'è 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 delle tue attività e dei tuoi frammenti.

Una modifica alla configurazione si verifica quando lo stato del dispositivo cambia in modo così radicale che il modo più semplice per risolvere il cambiamento è arrestare completamente e ricreare l'attività. Ad esempio, se l'utente cambia la lingua del dispositivo, potrebbe essere necessario modificare l'intero layout in base alle diverse indicazioni di testo. Se l'utente collega il dispositivo a un dock o aggiunge una tastiera fisica, il layout dell'app potrebbe dover sfruttare dimensioni o layout del display diversi. E se l'orientamento del dispositivo cambia, se il dispositivo viene ruotato da verticale a orizzontale o viceversa, potrebbe essere necessario modificare il layout in base al nuovo orientamento.

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

  1. Compila ed esegui l'app, quindi apri Logcat.
  2. Ruota il dispositivo o l'emulatore in modalità Orizzontale. Puoi ruotare l'emulatore verso sinistra o verso destra con i pulsanti di rotazione oppure con i tasti Control e Freccia (Command e con la tastiera del Mac).
  3. Esamina l'output in Logcat. Filtra l'output su MainActivity.
    Nota che quando il dispositivo o l'emulatore ruota lo schermo, il sistema chiama tutti i callback del ciclo di vita per interrompere l'attività. Quindi, mentre 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 un po' di volte il cupcake e ruota il dispositivo o l'emulatore. Questa volta, quando il dispositivo viene ruotato e l'attività viene arrestata e ricreata, l'attività inizia con i valori predefiniti.

    Quando si verifica una modifica della configurazione, Android utilizza lo stesso bundle di stato dell'istanza che hai appreso nell'attività precedente per salvare e ripristinare lo stato dell'app. Come per la chiusura del processo, usa il onSaveInstanceState() per inserire i dati dell'app nel bundle. Quindi ripristina i dati in onCreate() per evitare di perdere i dati relativi allo stato dell'attività se il dispositivo viene ruotato.
  6. In MainActivity, annulla il commento del metodo onSaveInstanceState(), esegui l'app, fai clic sul cupcake e ruota l'app o il dispositivo. Questa volta i dati dei dolci vengono conservati per la rotazione delle attività,

Progetto Android Studio: DessertClickerFinal

Suggerimenti per il ciclo di vita

  • Se configuri o avvii qualcosa in un callback di ciclo di vita, interrompi o rimuovi l'elemento nel callback corrispondente. Fermandola, ti assicuri che non continui a funzionare quando non è più necessario. Ad esempio, se configuri un timer in onStart(), devi metterlo in pausa o interromperlo in onStop().
  • Usa onCreate() soltanto per inizializzare le parti dell'app che vengono eseguite una volta, al primo avvio dell'app. Usa 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.

Libreria del ciclo di vita

  • Usa la libreria del ciclo di vita di Android per spostare il controllo del ciclo di vita dall'attività o dal frammento al componente effettivo che deve essere sensibile al ciclo di vita.
  • I proprietari del ciclo di vita sono componenti che hanno (e quindi "personale") cicli di vita, tra cui 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 attività quando il ciclo di vita cambia. Gli osservatori del ciclo di vita implementano l'interfaccia di LifecycleObserver.
  • Gli oggetti Lifecycle contengono gli stati effettivi del ciclo di vita e attivano eventi quando cambia il ciclo di vita.

Per creare una classe sensibile al ciclo di vita:

  • Implementa l'interfaccia LifecycleObserver in classi che devono essere sensibili al ciclo di vita.
  • Inizializza una classe dell'osservatore del ciclo di vita con l'oggetto del ciclo di vita dall'attività o dal frammento.
  • Nella classe dello strumento di osservazione 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 guardando l'evento onStart del ciclo di vita.

Elaborare le chiusure e salvare lo stato delle attività

  • Android regola le app in esecuzione in background affinché l'app in primo piano possa essere eseguita senza problemi. Questa norma include la limitazione della quantità di elaborazione che le app possono eseguire in background e talvolta anche l'arresto dell'intero processo.
  • L'utente non è in grado di sapere se il sistema ha chiuso un'app in background. L'app viene ancora visualizzata nella schermata recente 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 a emulatori e dispositivi collegati al computer. Puoi utilizzare adb per simulare la chiusura di un processo nella tua app.
  • Quando Android interrompe il processo dell'app, non viene richiamato il metodo del ciclo di vita di onDestroy(). L'app si interrompe.

Conservazione di attività e stato dei frammenti

  • Quando la tua app passa in background, subito dopo la chiamata a onStop(), i dati dell'app vengono salvati in un gruppo. Alcuni dati dell'app, come i contenuti di un elemento EditText, vengono salvati automaticamente.
  • Il pacchetto è un'istanza di Bundle, che è una raccolta di chiavi e valori. Le chiavi sono sempre stringhe.
  • Usa il callback onSaveInstanceState() per salvare altri dati nel pacchetto che vuoi conservare, anche se l'app è stata chiusa automaticamente. Per inserire dati nel gruppo, utilizza i metodi che iniziano con put, ad esempio putInt().
  • Puoi recuperare i dati dal gruppo nel metodo onRestoreInstanceState() o più comunemente in 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 del bundle con una chiave, utilizza i metodi Bundle che iniziano con get, ad esempio getInt().

Modifiche alla configurazione

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

Corso Udacity:

Documentazione per gli sviluppatori Android:

Altro:

In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:

  • Assegna i compiti, se necessario.
  • Comunica agli studenti come inviare compiti.
  • Valuta i compiti.

Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.

Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.

Cambiare un'app

Apri l'app DiceRoller della lezione 1. Se non l'hai ancora fatto, puoi scaricare l'app qui. Compila ed esegui l'app e tieni presente che, se ruoti il dispositivo, il valore corrente dei dadi viene perso. Implementa onSaveInstanceState() per mantenere quel valore nel bundle e ripristinalo in onCreate().

Rispondi a queste domande

Domanda 1

La tua app contiene una simulazione di fisica che richiede un calcolo elevato. Successivamente, l'utente riceverà una telefonata. Quale delle seguenti affermazioni è vera?

  • Durante la telefonata dovresti continuare a calcolare la posizione degli oggetti nella simulazione di fisica.
  • Durante la telefonata, non devi più elaborare le posizioni degli oggetti nella simulazione di fisica.

Domanda 2

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

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

Domanda 3

Quale interfaccia deve essere implementata dalla classe in modo che sia sensibile al ciclo di vita tramite la libreria del ciclo di vita di Android?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Domanda 4

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

  • L'attività viene riavviata dopo la rotazione del dispositivo.
  • L'attività inizia da zero.
  • L'attività viene ripresa dopo il rientro in background.
  • Il dispositivo viene riavviato.

Inizia la lezione successiva: 5.1: ViewModel and ViewModelFactory

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