Il ciclo di vita dell'attività (visualizzazioni)

Concetti e implementazione di Jetpack Compose

Man mano che un utente naviga all'interno della tua app, esce e poi torna, le istanze Activity della tua app passano attraverso diversi stati del loro ciclo di vita. La classe Activity fornisce una serie di callback che consentono all'attività di sapere quando uno stato cambia o che il sistema sta creando, arrestando o riprendendo un'attività o distruggendo il processo in cui risiede l'attività.

All'interno dei metodi di callback del ciclo di vita, puoi dichiarare il comportamento dell'attività quando l'utente esce e rientra nell'attività. Ad esempio, se stai creando un video player in streaming, potresti mettere in pausa il video e interrompere la connessione di rete quando l'utente passa a un'altra app. Quando l'utente torna, puoi riconnetterti alla rete e consentire all'utente di riprendere la riproduzione del video dallo stesso punto.

Ogni callback consente di eseguire un lavoro specifico appropriato per un determinato cambiamento di stato. Eseguire il lavoro giusto al momento giusto e gestire correttamente le transizioni rende la tua app più solida e performante. Ad esempio, una buona implementazione dei callback del ciclo di vita può aiutare la tua app a evitare quanto segue:

  • Arresto anomalo se l'utente riceve una chiamata o passa a un'altra app mentre utilizza la tua app.
  • Consumare risorse di sistema preziose quando l'utente non lo utilizza attivamente.
  • Perdita dei progressi dell'utente se esce dall'app e vi fa ritorno in un secondo momento.
  • Arresto anomalo o perdita dei progressi dell'utente quando lo schermo ruota tra orientamento orizzontale e verticale.

Questo documento spiega in dettaglio il ciclo di vita dell'attività. Il documento inizia con la descrizione del paradigma del ciclo di vita. Poi, spiega ogni callback: cosa succede internamente durante l'esecuzione e cosa devi implementare durante l'esecuzione.

Viene quindi introdotta brevemente la relazione tra lo stato dell'attività e la vulnerabilità di un processo all'interruzione da parte del sistema. Infine, vengono trattati diversi argomenti relativi alle transizioni tra gli stati di attività.

Per informazioni sulla gestione dei cicli di vita, incluse indicazioni sulle best practice, consulta Gestire i cicli di vita con componenti sensibili al ciclo di vita e Salvare gli stati dell'interfaccia utente. Per scoprire come progettare un'app solida e di qualità di produzione utilizzando le attività in combinazione con i componenti dell'architettura, consulta la Guida all'architettura dell'app.

Concetti del ciclo di vita dell'attività

Per spostarsi tra le transizioni delle fasi del ciclo di vita dell'attività, la classe Activity fornisce un insieme di base di sei callback: onCreate, onStart, onResume, onPause, onStop e onDestroy. Il sistema richiama ognuna di queste callback quando l'attività entra in un nuovo stato.

La figura 1 mostra una rappresentazione visiva di questo paradigma.

Figura 1. Un'illustrazione semplificata del ciclo di vita dell'attività.

Quando l'utente inizia a uscire dall'attività, il sistema chiama i metodi per smantellare l'attività. In alcuni casi, l'attività viene smontata solo parzialmente e rimane in memoria, ad esempio quando l'utente passa a un'altra app. In questi casi, l'attività può comunque tornare in primo piano.

Se l'utente torna all'attività, questa riprende da dove l'utente l'aveva interrotta. Con alcune eccezioni, le app non possono avviare attività quando vengono eseguite in background.

La probabilità che il sistema termini un determinato processo, insieme alle attività al suo interno, dipende dallo stato dell'attività in quel momento. Per saperne di più sul rapporto tra stato e vulnerabilità all'espulsione, consulta la sezione relativa allo stato dell'attività e all'espulsione dalla memoria.

A seconda della complessità dell'attività, probabilmente non devi implementare tutti i metodi del ciclo di vita. Tuttavia, è importante che tu comprenda ciascuno di questi intent e implementi quelli che fanno sì che la tua app si comporti nel modo in cui gli utenti si aspettano.

Callback del ciclo di vita

Questa sezione fornisce informazioni concettuali e di implementazione sui metodi di callback utilizzati durante il ciclo di vita dell'attività.

Alcune azioni appartengono ai metodi del ciclo di vita dell'attività. Tuttavia, inserisci il codice che implementa le azioni di un componente dipendente nel componente, anziché nel metodo del ciclo di vita dell'attività. Per farlo, devi rendere il componente dipendente consapevole del ciclo di vita. Per scoprire come rendere i componenti dipendenti consapevoli del ciclo di vita, consulta la sezione Gestire i cicli di vita con componenti consapevoli del ciclo di vita.

onCreate

Devi implementare questo callback, che viene attivato quando il sistema crea per la prima volta l'attività. Al momento della creazione, l'attività entra nello stato Creata. Nel metodo onCreate, esegui la logica di avvio di base dell'applicazione che si verifica una sola volta per l'intera durata dell'attività.

Ad esempio, l'implementazione di onCreate potrebbe associare i dati agli elenchi, associare l'attività a un ViewModel e creare un'istanza di alcune variabili di ambito della classe. Questo metodo riceve il parametro savedInstanceState, che è un oggetto Bundle contenente lo stato dell'attività salvato in precedenza. Se l'attività non è mai esistita prima, il valore dell'oggetto Bundle è null.

Se hai un componente sensibile al ciclo di vita collegato al ciclo di vita della tua attività, riceve l'evento ON_CREATE. Il metodo annotato con @OnLifecycleEvent viene chiamato in modo che il componente sensibile al ciclo di vita possa eseguire il codice di configurazione necessario per lo stato creato.

L'esempio seguente del metodo onCreate mostra la configurazione di base per l'attività, ad esempio la dichiarazione dell'interfaccia utente (definita in un file di layout XML), la definizione delle variabili membro e la configurazione di parte dell'interfaccia utente. In questo esempio, il file di layout XML passa l'ID risorsa del file R.layout.main_activity a setContentView.

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;
// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

In alternativa alla definizione del file XML e al relativo passaggio a setContentView, puoi creare nuovi oggetti View nel codice dell'attività e creare una gerarchia di oggetti View inserendo nuovi oggetti View in un ViewGroup. Poi utilizzi questo layout passando la radice ViewGroup a setContentView. Per saperne di più sulla creazione di un'interfaccia utente, consulta la documentazione relativa all'interfaccia utente.

La tua attività non rimane nello stato Creato. Al termine dell'esecuzione del metodo onCreate, l'attività entra nello stato Started e il sistema chiama i metodi onStart e onResume in rapida successione.

onStart

Quando l'attività entra nello stato Avviata, il sistema richiama onStart. Questa chiamata rende l'attività visibile all'utente mentre l'app si prepara a portare l'attività in primo piano e renderla interattiva. Ad esempio, in questo metodo viene inizializzato il codice che gestisce la UI.

Quando l'attività passa allo stato Avviata, qualsiasi componente sensibile al ciclo di vita collegato al ciclo di vita dell'attività riceve l'evento ON_START.

Il metodo onStart viene completato rapidamente e, come per lo stato Created, l'attività non rimane nello stato Started. Al termine di questo callback, l'attività entra nello stato Resumed e il sistema richiama il metodo onResume.

onResume

Quando l'attività entra nello stato Resumed, viene portata in primo piano e il sistema richiama il callback onResume. Questo è lo stato in cui l'app interagisce con l'utente. L'app rimane in questo stato finché non si verifica un evento che toglie il focus dall'app, ad esempio il dispositivo riceve una chiamata, l'utente passa a un'altra Activity o lo schermo del dispositivo si spegne.

Quando l'attività passa allo stato Ripresa, qualsiasi componente sensibile al ciclo di vita collegato al ciclo di vita dell'attività riceve l'evento ON_RESUME. È qui che i componenti del ciclo di vita possono attivare qualsiasi funzionalità che deve essere eseguita mentre il componente è visibile e in primo piano, ad esempio l'avvio di un'anteprima della videocamera.

Quando si verifica un evento interruttivo, l'attività entra nello stato In pausa e il sistema richiama il callback onPause.

Se l'attività torna allo stato Ripresa dallo stato In pausa, il sistema chiama di nuovo il metodo onResume. Per questo motivo, implementa onResume per inizializzare i componenti che rilasci durante onPause ed eseguire qualsiasi altra inizializzazione che deve verificarsi ogni volta che l'attività entra nello stato Resumed.

Ecco un esempio di componente sensibile al ciclo di vita che accede alla videocamera quando il componente riceve l'evento ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

Il codice precedente inizializza la videocamera una volta che LifecycleObserver riceve l'evento ON_RESUME. In modalità multi-finestra, tuttavia, la tua attività potrebbe essere completamente visibile anche quando è in stato di pausa. Ad esempio, quando l'app è in modalità multi-finestra e l'utente tocca la finestra che non contiene la tua attività, quest'ultima passa allo stato Sospesa.

Se vuoi che la videocamera sia attiva solo quando l'app è ripresa (visibile e attiva in primo piano), inizializza la videocamera dopo l'evento ON_RESUME mostrato in precedenza. Se vuoi mantenere la videocamera attiva mentre l'attività è in pausa ma visibile, ad esempio in modalità multi-finestra, inizializza la videocamera dopo l'evento ON_START.

Tuttavia, se la videocamera è attiva mentre l'attività è in pausa, potrebbe negare l'accesso alla videocamera a un'altra app ripresa in modalità multi-finestra. A volte è necessario mantenere attiva la videocamera mentre l'attività è in pausa, ma ciò potrebbe peggiorare l'esperienza utente complessiva.

Per questo motivo, pensa attentamente a quale punto del ciclo di vita è più opportuno assumere il controllo delle risorse di sistema condivise nel contesto della modalità multi-finestra. Per saperne di più sul supporto della modalità multi-finestra, vedi Supportare la modalità multi-finestra.

Indipendentemente dall'evento di accumulo in cui scegli di eseguire un'operazione di inizializzazione, assicurati di utilizzare l'evento del ciclo di vita corrispondente per rilasciare la risorsa. Se inizializzi qualcosa dopo l'evento ON_START, rilascialo o termina dopo l'evento ON_STOP. Se inizializzi dopo l'evento ON_RESUME, rilascia dopo l'evento ON_PAUSE.

Lo snippet di codice precedente inserisce il codice di inizializzazione della videocamera in un componente compatibile con il ciclo di vita. Puoi invece inserire questo codice direttamente nei callback del ciclo di vita dell'attività, come onStart e onStop, ma non lo consigliamo. L'aggiunta di questa logica a un componente indipendente e consapevole del ciclo di vita ti consente di riutilizzare il componente in più attività senza dover duplicare il codice. Per scoprire come creare un componente sensibile al ciclo di vita, consulta Gestire i cicli di vita con i componenti sensibili al ciclo di vita (Visualizzazioni).

onPause

Il sistema chiama questo metodo come prima indicazione che l'utente sta abbandonando l'attività, anche se non sempre significa che l'attività viene eliminata. Indica che l'attività non è più in primo piano, ma è ancora visibile se l'utente è in modalità multi-finestra. Ci sono diversi motivi per cui un'attività potrebbe entrare in questo stato:

  • Un evento che interrompe l'esecuzione dell'app, come descritto nella sezione relativa al callback onResume, mette in pausa l'attività corrente. Questo è il caso più comune.
  • In modalità multi-finestra, solo un'app è selezionata alla volta e il sistema mette in pausa tutte le altre app.
  • L'apertura di una nuova attività semitrasparente, ad esempio una finestra di dialogo, mette in pausa l'attività che copre. Finché l'attività è parzialmente visibile ma non in primo piano, rimane in pausa.

Quando un'attività passa allo stato Paused, qualsiasi componente sensibile al ciclo di vita collegato al ciclo di vita dell'attività riceve l'evento ON_PAUSE. È qui che i componenti del ciclo di vita possono interrompere qualsiasi funzionalità che non deve essere eseguita mentre il componente non è in primo piano, ad esempio l'interruzione dell'anteprima di una videocamera.

Utilizza il metodo onPause per mettere in pausa o modificare le operazioni che non possono continuare o potrebbero continuare in moderazione mentre Activity è nello stato In pausa e che prevedi di riprendere a breve.

Puoi anche utilizzare il metodo onPause per rilasciare risorse di sistema, handle per sensori (come il GPS) o qualsiasi risorsa che influisce sulla durata della batteria mentre l'attività è in pausa e l'utente non ne ha bisogno.

Tuttavia, come indicato nella sezione relativa a onResume, un'attività in pausa potrebbe essere ancora completamente visibile se l'app è in modalità multi-finestra. Valuta la possibilità di utilizzare onStop anziché onPause per rilasciare o modificare completamente le risorse e le operazioni correlate all'interfaccia utente per supportare meglio la modalità multi-finestra.

Il seguente esempio di LifecycleObserver che reagisce all'evento ON_PAUSE è la controparte dell'esempio di evento ON_RESUME precedente, che rilascia la videocamera che si inizializza dopo la ricezione dell'evento ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

Questo esempio inserisce il codice di rilascio della videocamera dopo che l'evento ON_PAUSE è stato ricevuto da LifecycleObserver.

L'esecuzione di onPause è molto breve e non offre necessariamente tempo sufficiente per eseguire le operazioni di salvataggio. Per questo motivo, non utilizzare onPause per salvare dati di applicazioni o utenti, effettuare chiamate di rete o eseguire transazioni di database. Queste operazioni potrebbero non essere completate prima del termine del metodo.

Esegui invece le operazioni di arresto con carico elevato durante onStop. Per saperne di più sulle operazioni adatte da eseguire durante onStop, consulta la sezione successiva. Per saperne di più sul salvataggio dei dati, consulta la sezione relativa al salvataggio e al ripristino dello stato.

Il completamento del metodo onPause non significa che l'attività esce dallo stato In pausa. L'attività rimane in questo stato finché non riprende o non diventa completamente invisibile all'utente. Se l'attività riprende, il sistema richiama di nuovo il callback onResume.

Se l'attività passa dallo stato Paused (In pausa) allo stato Resumed (Ripresa), il sistema mantiene l'istanza Activity residente in memoria, richiamandola quando il sistema richiama onResume. In questo scenario, non è necessario reinizializzare i componenti creati durante uno dei metodi di callback che portano allo stato Resumed. Se l'attività diventa completamente invisibile, il sistema chiama onStop.

onStop

Quando l'attività non è più visibile all'utente, entra nello stato Interrotto e il sistema richiama il callback onStop. Ciò può verificarsi quando un'attività appena avviata copre l'intero schermo. Il sistema chiama anche onStop quando l'attività termina l'esecuzione e sta per essere terminata.

Quando l'attività passa allo stato Interrotto, qualsiasi componente sensibile al ciclo di vita collegato al ciclo di vita dell'attività riceve l'evento ON_STOP. È qui che i componenti del ciclo di vita possono interrompere qualsiasi funzionalità che non deve essere eseguita mentre il componente non è visibile sullo schermo.

Nel metodo onStop, rilascia o regola le risorse non necessarie mentre l'app non è visibile all'utente. Ad esempio, l'app potrebbe mettere in pausa le animazioni o passare da aggiornamenti della posizione granulari ad aggiornamenti della posizione approssimativi. L'utilizzo di onStop anziché di onPause significa che il lavoro correlato all'interfaccia utente continua, anche quando l'utente visualizza la tua attività in modalità multi-finestra.

Inoltre, utilizza onStop per eseguire operazioni di spegnimento che richiedono un utilizzo relativamente elevato della CPU. Ad esempio, se non riesci a trovare un momento migliore per salvare le informazioni in un database, potresti farlo durante onStop. L'esempio seguente mostra un'implementazione di onStop che salva i contenuti di una nota bozza in uno spazio di archiviazione permanente:

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

L'esempio di codice precedente utilizza direttamente SQLite. Tuttavia, ti consigliamo di utilizzare Room, una libreria di persistenza che fornisce un livello di astrazione su SQLite. Per scoprire di più sui vantaggi dell'utilizzo di Room e su come implementarlo nella tua app, consulta la guida alla libreria di persistenza Room.

Quando l'attività entra nello stato Interrotto, l'oggetto Activity viene mantenuto in memoria: mantiene tutte le informazioni su stato e membri, ma non è collegato al gestore finestre. Quando l'attività riprende, vengono richiamate queste informazioni.

Non è necessario reinizializzare i componenti creati durante uno dei metodi di callback che portano allo stato Resumed. Il sistema tiene traccia anche dello stato attuale di ogni oggetto View nel layout, quindi se l'utente inserisce testo in un widget EditText, i contenuti vengono conservati, quindi non è necessario salvarli e ripristinarli.

Dallo stato Arrestato, l'attività torna a interagire con l'utente oppure termina l'esecuzione e scompare. Se l'attività viene ripristinata, il sistema richiama onRestart. Se l'esecuzione di Activity è terminata, il sistema chiama onDestroy.

onDestroy

onDestroy viene chiamato prima che l'attività venga eliminata. Il sistema richiama questo callback per uno dei due motivi seguenti:

  1. L'attività sta per terminare perché l'utente l'ha chiusa completamente o perché è stato chiamato finish.
  2. Il sistema sta distruggendo temporaneamente l'attività a causa di una modifica alla configurazione, ad esempio la rotazione del dispositivo o l'attivazione della modalità multi-finestra.

Quando l'attività passa allo stato di distruzione, qualsiasi componente consapevole del ciclo di vita associato al ciclo di vita dell'attività riceve l'evento ON_DESTROY. È qui che i componenti del ciclo di vita possono liberare spazio per tutto ciò che devono prima che Activity venga distrutto.

Anziché inserire la logica in Activity per determinare il motivo per cui viene eliminato, utilizza un oggetto ViewModel per contenere i dati della visualizzazione pertinenti per Activity. Se Activity viene ricreato a causa di una modifica alla configurazione, ViewModel non deve fare nulla, poiché viene conservato e fornito alla successiva istanza Activity.

Se Activity non viene ricreato, ViewModel chiama il metodo onCleared, in cui può liberare spazio dai dati necessari prima di essere eliminato. Puoi distinguere questi due scenari con il metodo isFinishing.

Se l'attività sta per terminare, onDestroy è l'ultimo callback del ciclo di vita che riceve. Se onDestroy viene chiamato in seguito a una modifica della configurazione, il sistema crea immediatamente una nuova istanza di attività e poi chiama onCreate su questa nuova istanza nella nuova configurazione.

Il callback onDestroy rilascia tutte le risorse non rilasciate dai callback precedenti, ad esempio onStop.

Salvataggio e ripristino dello stato temporaneo della UI

Un utente si aspetta che lo stato dell'interfaccia utente di un'attività rimanga invariato durante una modifica della configurazione, ad esempio la rotazione o il passaggio alla modalità multi-finestra. Tuttavia, il sistema distrugge l'attività per impostazione predefinita quando si verifica una modifica di configurazione, eliminando qualsiasi stato dell'interfaccia utente memorizzato nell'istanza dell'attività.

Allo stesso modo, un utente si aspetta che lo stato dell'interfaccia utente rimanga invariato se passa temporaneamente dalla tua app a un'altra e poi torna alla tua app in un secondo momento. Tuttavia, il sistema può eliminare il processo dell'applicazione mentre l'utente non è presente e la tua attività è interrotta.

Quando i vincoli di sistema eliminano l'attività, conserva lo stato dell'interfaccia utente temporanea dell'utente utilizzando una combinazione di ViewModel, onSaveInstanceState e/o spazio di archiviazione locale. Per scoprire di più sulle aspettative degli utenti rispetto al comportamento del sistema e su come conservare al meglio i dati complessi sullo stato dell'interfaccia utente in caso di interruzione di attività e processi avviati dal sistema, consulta Salvare gli stati dell'interfaccia utente.

Questa sezione descrive lo stato dell'istanza e come implementare il metodo onSaveInstance, che è un callback sull'attività stessa. Se i dati dell'UI sono leggeri, puoi utilizzare solo onSaveInstance per mantenere lo stato dell'UI sia in caso di modifiche alla configurazione sia in caso di interruzione del processo avviata dal sistema. Tuttavia, poiché onSaveInstance comporta costi di serializzazione/deserializzazione, nella maggior parte dei casi utilizzi sia ViewModel sia onSaveInstance, come descritto in Salvare gli stati dell'interfaccia utente.

Stato istanza

Esistono alcuni scenari in cui l'attività viene eliminata a causa del normale comportamento dell'app, ad esempio quando l'utente preme il pulsante Indietro o l'attività segnala la propria eliminazione chiamando il metodo finish.

Quando l'attività viene eliminata perché l'utente preme Indietro o perché l'attività termina autonomamente, sia il concetto di sistema che quello dell'utente di quell'istanza di Activity scompare per sempre. In questi scenari, l'aspettativa dell'utente corrisponde al comportamento del sistema e non devi fare altro.

Tuttavia, se il sistema distrugge l'attività a causa di vincoli di sistema (ad esempio una modifica alla configurazione o una pressione sulla memoria), anche se l'istanza Activity effettiva non è più presente, il sistema ricorda che esisteva. Se l'utente tenta di tornare all'attività, il sistema crea una nuova istanza dell'attività utilizzando un insieme di dati salvati che descrivono lo stato dell'attività quando è stata eliminata.

I dati salvati che il sistema utilizza per ripristinare lo stato precedente sono chiamati stato dell'istanza. Si tratta di una raccolta di coppie chiave-valore archiviate in un oggetto Bundle. Per impostazione predefinita, il sistema utilizza lo stato dell'istanza Bundle per salvare le informazioni su ogni oggetto View nel layout dell'attività, ad esempio il valore di testo inserito in un widget EditText.

Pertanto, se l'istanza dell'attività viene eliminata e ricreata, lo stato del layout viene ripristinato al suo stato precedente senza che tu debba scrivere codice. Tuttavia, la tua attività potrebbe avere più informazioni sullo stato che vorresti ripristinare, ad esempio variabili membro che monitorano i progressi dell'utente nell'attività.

Un oggetto Bundle non è adatto a conservare una quantità di dati superiore a una quantità banale, perché richiede la serializzazione sul thread principale e consuma la memoria del processo di sistema. Per conservare una quantità di dati superiore a una quantità molto piccola, adotta un approccio combinato per la conservazione dei dati, utilizzando l'archiviazione locale permanente, il metodo onSaveInstanceState e la classe ViewModel, come descritto in Salva stati UI.

Salva lo stato dell'interfaccia utente semplice e leggera utilizzando onSaveInstanceState

Quando l'attività inizia a interrompersi, il sistema chiama il metodo onSaveInstanceState in modo che l'attività possa salvare le informazioni sullo stato in un bundle di stato dell'istanza. L'implementazione predefinita di questo metodo salva informazioni temporanee sullo stato della gerarchia di oggetti View dell'attività, ad esempio il testo in un widget EditText o la posizione di scorrimento di un widget ListView.

Per salvare ulteriori informazioni sullo stato dell'istanza per la tua attività, esegui l'override di onSaveInstanceState e aggiungi coppie chiave-valore all'oggetto Bundle che viene salvato nel caso in cui l'attività venga eliminata in modo imprevisto. Quando esegui l'override di onSaveInstanceState, devi chiamare l'implementazione della superclasse se vuoi che l'implementazione predefinita salvi lo stato della gerarchia di oggetti View. Ciò è mostrato nel seguente esempio:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

Per salvare dati persistenti, come le preferenze dell'utente o i dati per un database, cogli le opportunità appropriate quando la tua attività è in primo piano. Se non si presenta un'opportunità di questo tipo, salva i dati permanenti durante il metodo onStop.

Ripristina lo stato dell'interfaccia utente dell'attività utilizzando lo stato dell'istanza salvato

Quando l'attività viene ricreata dopo essere stata eliminata in precedenza, puoi recuperare lo stato dell'istanza salvata da Bundle che il sistema passa all'attività. Entrambi i metodi di callback onCreate e onRestoreInstanceState ricevono lo stesso Bundle che contiene le informazioni sullo stato dell'istanza.

Poiché il metodo onCreate viene chiamato indipendentemente dal fatto che il sistema stia creando una nuova istanza dell'attività o ricreandone una precedente, devi controllare se lo stato Bundle è nullo prima di tentare di leggerlo. Se è null, il sistema sta creando una nuova istanza dell'attività, anziché ripristinare una precedente che è stata eliminata.

Il seguente snippet di codice mostra come ripristinare alcuni dati di stato in onCreate:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Anziché ripristinare lo stato durante onCreate, puoi scegliere di implementare onRestoreInstanceState, che il sistema chiama dopo il metodo onStart. Il sistema chiama onRestoreInstanceState solo se esiste uno stato salvato da ripristinare, quindi non devi controllare se Bundle è null.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

È probabile che un'app entri e esca da un'attività, forse molte volte, durante il ciclo di vita dell'app, ad esempio quando l'utente tocca il pulsante Indietro del dispositivo o quando l'attività avvia un'altra attività.

Questa sezione tratta gli argomenti che devi conoscere per implementare transizioni di attività efficaci. Questi argomenti includono l'avvio di un'attività da un'altra attività, il salvataggio dello stato dell'attività e il ripristino dello stato dell'attività.

Avviare un'attività da un'altra

Spesso un'attività deve avviare un'altra attività a un certo punto. Questa necessità si presenta, ad esempio, quando un'app deve passare dalla schermata corrente a una nuova.

A seconda che l'attività richieda o meno un risultato dalla nuova attività che sta per iniziare, avvia la nuova attività utilizzando il metodo startActivity o il metodo startActivityForResult. In entrambi i casi, passi un oggetto Intent.

L'oggetto Intent specifica l'attività esatta che vuoi avviare o descrive il tipo di azione che vuoi eseguire. Il sistema seleziona l'attività più adatta a te, che può anche provenire da un'applicazione diversa. Un oggetto Intent può anche contenere piccole quantità di dati da utilizzare nell'attività che viene avviata. Per saperne di più sulla classe Intent, consulta Intent e filtri per intent.

startActivity

Se l'attività appena avviata non deve restituire un risultato, l'attività corrente può avviarla chiamando il metodo startActivity.

Quando lavori all'interno della tua applicazione, spesso devi semplicemente avviare un'attività nota. Ad esempio, il seguente snippet di codice mostra come avviare un'attività chiamata SignInActivity.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

La tua applicazione potrebbe anche voler eseguire un'azione, ad esempio inviare un'email, un messaggio o un aggiornamento di stato, utilizzando i dati della tua attività. In questo caso, la tua applicazione potrebbe non avere attività proprie per eseguire queste azioni, quindi puoi invece sfruttare le attività fornite da altre applicazioni sul dispositivo, che possono eseguire le azioni per te.

È qui che gli intenti si rivelano davvero utili. Puoi creare un intent che descrive un'azione che vuoi eseguire e il sistema avvia l'attività appropriata da un'altra applicazione. Se sono presenti più attività che possono gestire l'intent, l'utente può selezionare quella da utilizzare. Ad esempio, se vuoi consentire all'utente di inviare un messaggio email, puoi creare il seguente intent:

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

L'extra EXTRA_EMAIL aggiunto all'intent è un array di stringhe di indirizzi email a cui deve essere inviata l'email. Quando un'applicazione email risponde a questo intent, legge l'array di stringhe fornito nell'extra e inserisce gli indirizzi nel campo "A" del modulo di composizione dell'email. In questa situazione, l'attività dell'applicazione email inizia e, al termine dell'utente, la tua attività riprende.

startActivityForResult

A volte, vuoi ricevere un risultato da un'attività al termine. Ad esempio, puoi avviare un'attività che consente all'utente di scegliere una persona in un elenco di contatti. Al termine, restituisce la persona selezionata. Per farlo, chiama il metodo startActivityForResult(Intent, int), in cui il parametro intero identifica la chiamata.

Questo identificatore ha lo scopo di distinguere tra più chiamate a startActivityForResult(Intent, int) dalla stessa attività. Non è un identificatore globale e non rischia di entrare in conflitto con altre app o attività. Il risultato viene restituito tramite il tuo metodo onActivityResult(int, int, Intent).

Quando un'attività secondaria viene chiusa, può chiamare setResult(int) per restituire i dati al relativo genitore. L'attività secondaria deve fornire un codice risultato, che può essere il codice standard RESULT_CANCELED, RESULT_OK o qualsiasi valore personalizzato a partire da RESULT_FIRST_USER.

Inoltre, l'attività del bambino può facoltativamente restituire un oggetto Intent contenente eventuali dati aggiuntivi che desidera. L'attività principale utilizza il metodo onActivityResult(int, int, Intent), insieme all'identificatore intero fornito originariamente dall'attività principale, per ricevere le informazioni.

Se un'attività secondaria non va a buon fine per qualsiasi motivo, ad esempio un arresto anomalo, l'attività principale riceve un risultato con il codice RESULT_CANCELED.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
    // ...

    static final int PICK_CONTACT_REQUEST = 0;

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                new Intent(Intent.ACTION_PICK,
                new Uri("content://contacts")),
                PICK_CONTACT_REQUEST);
            return true;
        }
        return false;
    }

    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == PICK_CONTACT_REQUEST) {
            if (resultCode == RESULT_OK) {
                // A contact was picked. Display it to the user.
                startActivity(new Intent(Intent.ACTION_VIEW, data));
            }
        }
    }
}

Coordinamento delle attività

Quando un'attività ne avvia un'altra, entrambe subiscono transizioni del ciclo di vita. La prima attività smette di funzionare e passa allo stato In pausa o Interrotto, mentre viene creata l'altra attività. Nel caso in cui queste attività condividano dati salvati su disco o altrove, è importante capire che la prima attività non viene interrotta completamente prima della creazione della seconda. Il processo di avvio del secondo si sovrappone a quello di interruzione del primo.

L'ordine dei callback del ciclo di vita è ben definito, in particolare quando le due attività si trovano nello stesso processo, ovvero nella stessa app, e una avvia l'altra. Ecco l'ordine delle operazioni che si verificano quando l'attività A avvia l'attività B:

  1. Viene eseguito il metodo onPause di Activity A.
  2. I metodi onCreate, onStart e onResume dell'attività B vengono eseguiti in sequenza. L'attività B ora è in primo piano.
  3. Se l'attività A non è più visibile sullo schermo, viene eseguito il relativo metodo onStop.

Questa sequenza di callback del ciclo di vita consente di gestire la transizione delle informazioni da un'attività all'altra.