Gestione dei cicli di vita con componenti sensibili al ciclo di vita Componente di Android Jetpack.

I componenti sensibili al ciclo di vita eseguono azioni in risposta a un cambiamento nello stato del ciclo di vita di un altro componente, ad esempio attività e frammenti. Questi componenti ti aiutano a produrre codice meglio organizzato e spesso più leggero, che è più facile da gestire.

Un pattern comune è implementare le azioni dei componenti dipendenti nei metodi del ciclo di vita di attività e frammenti. Tuttavia, questo pattern porta a una scarsa organizzazione del codice e alla proliferazione di errori. Utilizzando i componenti sensibili al ciclo di vita, puoi spostare il codice dei componenti dipendenti dai metodi del ciclo di vita ai componenti stessi.

Il pacchetto androidx.lifecycle fornisce classi e interfacce che ti consentono di creare componenti consapevoli del ciclo di vita, ovvero componenti in grado di regolare automaticamente il proprio comportamento in base allo stato attuale del ciclo di vita di un'attività o di un frammento.

Alla maggior parte dei componenti delle app definiti nel framework Android sono collegati cicli di vita. I cicli di vita sono gestiti dal sistema operativo o dal codice del framework in esecuzione nel processo. Sono fondamentali per il funzionamento di Android e l'applicazione deve rispettarli. In caso contrario, potrebbero verificarsi perdite di memoria o arresti anomali dell'applicazione.

Immagina di avere un'attività che mostra la posizione del dispositivo sullo schermo. Un'implementazione comune potrebbe essere la seguente:

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -> Unit
) {

    fun start() {
        // connect to system location service
    }

    fun stop() {
        // disconnect from system location service
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        myLocationListener.start()
        // manage other components that need to respond
        // to the activity lifecycle
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Java

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Anche se questo esempio sembra corretto, in un'app reale potresti avere troppe chiamate che gestiscono la UI e altri componenti in risposta allo stato attuale del ciclo di vita. La gestione di più componenti comporta una notevole quantità di codice nei metodi del ciclo di vita, come onStart() e onStop(), il che li rende difficili da mantenere.

Inoltre, non vi è alcuna garanzia che il componente venga avviato prima dell'interruzione dell'attività o del frammento. Ciò è particolarmente vero se dobbiamo eseguire un'operazione a lunga esecuzione, come un controllo della configurazione in onStart(). Ciò può causare una race condizione in cui il metodo onStop() termina prima del onStart(), mantenendo il componente in vita più a lungo del necessario.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        Util.checkUserStatus { result ->
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start()
            }
        }
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
    }

}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}

Il pacchetto androidx.lifecycle fornisce classi e interfacce che ti consentono di affrontare questi problemi in modo resiliente e isolato.

Ciclo di vita

Lifecycle è una classe che contiene le informazioni sullo stato del ciclo di vita di un componente (come un'attività o un frammento) e consente ad altri oggetti di osservare questo stato.

Lifecycle utilizza due enumerazioni principali per monitorare lo stato del ciclo di vita del componente associato:

Evento
Gli eventi del ciclo di vita inviati dal framework e dalla classe Lifecycle. Questi eventi sono mappati agli eventi di callback in attività e frammenti.
Stato
Lo stato attuale del componente monitorato dall'oggetto Lifecycle.
Diagramma degli stati del ciclo di vita
Figura 1. Stati ed eventi che compongono il ciclo di vita delle attività Android

Pensa agli stati come nodi di un grafico e agli eventi come ai bordi tra questi nodi.

Una classe può monitorare lo stato del ciclo di vita del componente implementando DefaultLifecycleObserver e sostituendo i metodi corrispondenti, come onCreate, onStart e così via. A questo punto, puoi aggiungere un osservatore chiamando il metodo addObserver() della classe Lifecycle e passando un'istanza dell'osservatore, come mostrato nell'esempio seguente:

Kotlin

class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

Java

public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }

    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

Nell'esempio precedente, l'oggetto myLifecycleOwner implementa l'interfaccia LifecycleOwner, come spiegato nella sezione seguente.

Proprietario ciclo di vita

LifecycleOwner è un'interfaccia a metodo singolo che indica che la classe ha un Lifecycle. Ha un solo metodo, getLifecycle(), che deve essere implementato dalla classe. Se invece stai cercando di gestire il ciclo di vita di un intero processo di applicazione, consulta ProcessLifecycleOwner.

Questa interfaccia astrae la proprietà di un elemento Lifecycle da singole classi, come Fragment e AppCompatActivity, e consente di scrivere componenti compatibili. Qualsiasi classe di applicazione personalizzata può implementare l'interfaccia LifecycleOwner.

I componenti che implementano DefaultLifecycleObserver funzionano perfettamente con quelli che implementano LifecycleOwner perché un proprietario può fornire un ciclo di vita che un osservatore può registrare per guardare.

Per l'esempio di monitoraggio della località, possiamo fare in modo che la classe MyLocationListener implementa DefaultLifecycleObserver e quindi la inizializziamo con il valore Lifecycle dell'attività nel metodo onCreate(). Ciò consente alla classe MyLocationListener di essere autosufficiente, il che significa che la logica per reagire ai cambiamenti nello stato del ciclo di vita viene dichiarata in MyLocationListener anziché nell'attività. L'archiviazione della logica dei singoli componenti semplifica la gestione delle attività e dei frammenti.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this, lifecycle) { location ->
            // update UI
        }
        Util.checkUserStatus { result ->
            if (result) {
                myLocationListener.enable()
            }
        }
    }
}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.enable();
            }
        });
  }
}

Un caso d'uso comune è evitare di richiamare determinati callback se Lifecycle non è in uno stato buono in questo momento. Ad esempio, se il callback esegue una transazione di frammento dopo il salvataggio dello stato dell'attività, si attiverà un arresto anomalo, quindi non vogliamo mai richiamare il callback.

Per semplificare questo caso d'uso, la classe Lifecycle consente ad altri oggetti di eseguire query sullo stato attuale.

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // connect
        }
    }

    fun enable() {
        enabled = true
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            // connect if not connected
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        // disconnect if connected
    }
}

Java

class MyLocationListener implements DefaultLifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @Override
    public void onStart(LifecycleOwner owner) {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @Override
    public void onStop(LifecycleOwner owner) {
        // disconnect if connected
    }
}

Con questa implementazione, la nostra classe LocationListener è completamente consapevole al ciclo di vita. Se dobbiamo utilizzare LocationListener da un'altra attività o frammento, basta inizializzarlo. Tutte le operazioni di configurazione e rimozione sono gestite dalla classe stessa.

Se una libreria fornisce classi che devono funzionare con il ciclo di vita di Android, ti consigliamo di utilizzare componenti sensibili al ciclo di vita. I tuoi client libreria possono facilmente integrare questi componenti senza la gestione manuale del ciclo di vita sul lato client.

Implementazione di un LifecycleOwner personalizzato

I frammenti e le attività nella Support Library 26.1.0 e versioni successive implementano già l'interfaccia LifecycleOwner.

Se hai una classe personalizzata che vuoi trasformare in LifecycleOwner, puoi utilizzare la classe LifecycleRegistry, ma devi inoltrare gli eventi a quella classe, come mostrato nell'esempio di codice riportato di seguito:

Kotlin

class MyActivity : Activity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        lifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

Best practice per i componenti sensibili al ciclo di vita

  • Mantieni i controller UI (attività e frammenti) più snelli possibile. Non devono provare ad acquisire i propri dati, ma devono utilizzare ViewModel per farlo e osservare un oggetto LiveData per riflettere le modifiche apportate alle viste.
  • Prova a scrivere UI basate sui dati in cui la responsabilità del controller UI è aggiornare le viste come modifiche ai dati o notificare le azioni degli utenti a ViewModel.
  • Inserisci la logica dei dati nel tuo corso ViewModel. ViewModel deve fungere da connettore tra il controller UI e il resto dell'app. Fai attenzione, però, che non spetta a ViewModel recuperare i dati (ad esempio da una rete). ViewModel deve invece chiamare il componente appropriato per recuperare i dati, quindi restituire il risultato al controller UI.
  • Utilizza l'associazione di dati per mantenere un'interfaccia pulita tra le tue viste e il controller UI. Ciò ti consente di rendere le tue viste più dichiarative e di ridurre al minimo il codice di aggiornamento necessario per scrivere nelle attività e nei frammenti. Se preferisci eseguire questa operazione nel linguaggio di programmazione Java, utilizza una libreria come Butter Knife per evitare il codice boilerplate e ottenere una migliore astrazione.
  • Se la tua UI è complessa, valuta la possibilità di creare una classe presenter per gestire le modifiche all'interfaccia utente. Potrebbe essere un'attività difficoltosa, ma può semplificare il test dei componenti dell'interfaccia utente.
  • Evita di fare riferimento a un contesto View o Activity in ViewModel. Se ViewModel supera l'attività (in caso di modifiche alla configurazione), l'attività è permessa e non viene eliminata correttamente dal garbage collector.
  • Utilizza le coroutine Kotlin per gestire attività di lunga durata e altre operazioni che possono essere eseguite in modo asincrono.

Casi d'uso per i componenti sensibili al ciclo di vita

I componenti sensibili al ciclo di vita possono semplificare la gestione dei cicli di vita in numerosi casi. Ecco alcuni esempi:

  • Passare dagli aggiornamenti della posizione approssimativi a quelli granulari. Utilizza i componenti sensibili al ciclo di vita per attivare aggiornamenti granulari della posizione mentre la tua app di posizione è visibile e passa agli aggiornamenti granulari quando l'app è in background. LiveData, un componente sensibile al ciclo di vita, consente alla tua app di aggiornare automaticamente l'interfaccia utente quando l'utente cambia le località.
  • Interruzione e avvio del buffering video. Utilizza componenti sensibili al ciclo di vita per avviare il buffering video il prima possibile, ma posticipa la riproduzione fino al completamento dell'avvio dell'app. Puoi anche usare componenti sensibili al ciclo di vita per terminare il buffering quando l'app viene eliminata.
  • Avvio e interruzione della connettività di rete. Utilizza i componenti sensibili al ciclo di vita per abilitare l'aggiornamento in tempo reale (flussi di dati) dei dati di rete mentre un'app è in primo piano e anche per metterla automaticamente in pausa quando l'app entra in background.
  • Mettendo in pausa e riprendo i disegnabili animati. Utilizza i componenti sensibili al ciclo di vita per gestire la messa in pausa dei disegnabili animati quando l'app è in background e il ripristino dei disegnabili dopo che l'app è in primo piano.

Gestione degli eventi di interruzione

Quando un elemento Lifecycle appartiene a AppCompatActivity o Fragment, lo stato di Lifecycle passa a CREATED e l'evento ON_STOP viene inviato quando si chiama AppCompatActivity o Fragment onSaveInstanceState().

Quando lo stato di un elemento Fragment o AppCompatActivity viene salvato tramite onSaveInstanceState(), la sua UI viene considerata immutabile fino a quando non viene chiamata ON_START. Il tentativo di modificare l'interfaccia utente dopo il salvataggio dello stato potrebbe causare incoerenze nello stato di navigazione dell'applicazione, motivo per cui FragmentManager genera un'eccezione se l'app esegue un FragmentTransaction dopo il salvataggio dello stato. Per informazioni dettagliate, visita la pagina commit().

LiveData evita questo caso limite da subito, evitando di chiamare l'osservatore se il valore Lifecycle associato all'osservatore non è almeno STARTED. Dietro le quinte chiama isAtLeast() prima di decidere di richiamare il proprio osservatore.

Purtroppo il metodo onStop() di AppCompatActivity viene chiamato dopo onSaveInstanceState(), il che lascia un divario in cui le modifiche dello stato dell'interfaccia utente non sono consentite, ma il Lifecycle non è ancora stato spostato allo stato CREATED.

Per evitare questo problema, la classe Lifecycle nella versione beta2 e inferiore contrassegna lo stato come CREATED senza inviare l'evento, in modo che qualsiasi codice che verifica lo stato attuale riceva il valore reale anche se l'evento non viene inviato finché onStop() non viene chiamato dal sistema.

Purtroppo questa soluzione presenta due problemi principali:

  • A livello API 23 e livelli precedenti, il sistema Android salva in realtà lo stato di un'attività anche se è parzialmente coperta da un'altra attività. In altre parole, il sistema Android chiama onSaveInstanceState(), ma non chiama necessariamente onStop(). Questo crea un intervallo potenzialmente lungo in cui l'osservatore continua a pensare che il ciclo di vita sia attivo anche se non è possibile modificare lo stato dell'UI.
  • Qualsiasi classe che voglia esporre un comportamento simile alla classe LiveData deve implementare la soluzione alternativa fornita da Lifecycle versione beta 2 e precedenti.

Risorse aggiuntive

Per saperne di più sulla gestione dei cicli di vita con componenti sensibili al ciclo di vita, consulta le seguenti risorse aggiuntive.

Samples

Codelab

Blog