Gestione dei cicli di vita con componenti consapevoli del ciclo di vita Componente di Android Jetpack.
I componenti consapevoli del ciclo di vita eseguono azioni in risposta a una modifica dello stato del ciclo di vita di un altro componente, ad esempio attività e frammenti. Questi componenti ti aiutano a produrre codice più organizzato e spesso più leggero, che è più facile da gestire.
Un pattern comune è implementare le azioni dei componenti dipendenti nei metodi di ciclo di vita di attività e frammenti. Tuttavia, questo modello porta a una cattiva 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 nei componenti stessi.
Il pacchetto androidx.lifecycle
fornisce classi e interfacce che ti consentono di creare componenti consapevoli del ciclo di vita, ovvero componenti che possono regolare automaticamente il loro
comportamento in base allo stato corrente del ciclo di vita di un'attività o di un frammento.
La maggior parte dei componenti dell'app definiti nel framework Android ha associati dei 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 la tua applicazione deve rispettarle. In caso contrario, potrebbero verificarsi perdite di memoria o persino arresti anomali dell'applicazione.
Immaginiamo 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 finisci per avere troppe chiamate che gestiscono l'interfaccia utente e altri componenti in risposta allo stato corrente del ciclo di vita. La gestione di più componenti inserisce una quantità considerevole di codice nei metodi del ciclo di vita, come onStart()
e
onStop()
, il che li rende difficili da gestire.
Inoltre, non è garantito che il componente venga avviato prima dell'interruzione dell'attività o del frammento. Ciò è particolarmente vero se dobbiamo eseguire un'operazione di lunga durata, ad esempio un controllo di configurazione in onStart()
. Ciò può causare una condizione di gara in cui il metodo onStop()
termina prima di onStart()
, mantenendo il componente attivo 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 aiutano ad 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 (ad esempio 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 di ciclo di vita inviati dal framework e dalla classe
Lifecycle
. Questi eventi vengono mappati agli eventi di callback in attività e frammenti. - Stato
- Lo stato attuale del componente monitorato dall'oggetto
Lifecycle
.
Pensa agli stati come ai nodi di un grafico e agli eventi come alle frecce 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.
Poi 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
, illustrata nella sezione seguente.
LifecycleOwner
LifecycleOwner
è un'interfaccia con un singolo metodo che indica che la classe ha un Lifecycle
. Ha un 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 esegue l'astrazione della proprietà di un
Lifecycle
dalle singole
classi, come Fragment
e AppCompatActivity
, e consente di scrivere componenti che
funzionano con queste. Qualsiasi classe di applicazione personalizzata può implementare l'interfaccia
LifecycleOwner
.
I componenti che implementano
DefaultLifecycleObserver
funzionano perfettamente con i componenti che implementano
LifecycleOwner
perché un proprietario può fornire un ciclo di vita che un osservatore può registrare per
osservarlo.
Per l'esempio di monitoraggio della posizione, possiamo fare in modo che la classe MyLocationListener
implementi DefaultLifecycleObserver
e poi la inizializzia con Lifecycle
dell'attività nel metodo onCreate()
. In questo modo, la classe MyLocationListener
è autosufficiente, il che significa che la logica per reagire alle modifiche dello stato del ciclo di vita è dichiarata in MyLocationListener
anziché nell'attività. La memorizzazione della logica dei singoli componenti semplifica la gestione della logica 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 il valore Lifecycle
non è in buono stato. Ad esempio, se il callback esegue una transazione del frammento dopo
la salvataggio dello stato dell'attività, attiverebbe un arresto anomalo, quindi non vogliamo mai invocare questo callback.
Per semplificare questo caso d'uso, la classe Lifecycle
consente ad altri oggetti di eseguire query sullo stato corrente.
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 del ciclo di vita. Se dobbiamo utilizzare LocationListener
da un'altra attività o da un altro frammento, dobbiamo solo inizializzalo. Tutte le operazioni di configurazione e smantellamento sono gestite dal corso stesso.
Se una libreria fornisce classi che devono funzionare con il ciclo di vita di Android, consigliamo di utilizzare componenti consapevoli del ciclo di vita. I client della libreria possono integrare facilmente questi componenti senza la gestione manuale del ciclo di vita lato client.
Implementazione di un LifecycleOwner personalizzato
I frammenti e le attività nella libreria di supporto 26.1.0 e versioni successive implementano già l'interfaccia LifecycleOwner
.
Se hai una classe personalizzata di cui vuoi creare un
LifecycleOwner
, puoi utilizzare la classe
LifecycleRegistry, ma devi inoltrare gli eventi a quella classe, come mostrato nell'esempio di codice seguente:
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 consapevoli del ciclo di vita
- Mantieni i controller dell'interfaccia utente (attività e frammenti) il più semplici possibile. Non devono tentare di acquisire i propri dati, ma devono utilizzare un
ViewModel
per farlo e osservare un oggettoLiveData
per riflettere le modifiche nelle visualizzazioni. - Prova a scrivere UI basate sui dati in cui è responsabilità del controller dell'interfaccia utente aggiornare le visualizzazioni man mano che i dati cambiano o notificare le azioni dell'utente al
ViewModel
. - Inserisci la logica dei dati nella classe
ViewModel
.ViewModel
deve fungere da connettore tra il controller dell'interfaccia utente e il resto dell'app. Fai attenzione, però: non è compito diViewModel
recuperare i dati (ad esempio da una rete). Invece,ViewModel
deve chiamare il componente appropriato per recuperare i dati, quindi fornire nuovamente il risultato al controller dell'interfaccia utente. - Utilizza il Data Binding per mantenere un'interfaccia chiara tra le visualizzazioni e il controller dell'interfaccia utente. In questo modo puoi trovare le visualizzazioni più dichiarative e ridurre al minimo il codice di aggiornamento da scrivere nelle attività e nei frammenti. Se preferisci farlo nel linguaggio di programmazione Java, utilizza una libreria come Butter Knife per evitare il codice boilerplate e avere un'astrazione migliore.
- Se la tua UI è complessa, valuta la possibilità di creare una classe presentatore per gestire le modifiche dell'interfaccia utente. Questa potrebbe essere un'attività laboriosa, ma può facilitare il test dei componenti dell'interfaccia utente.
- Evita di fare riferimento a un contesto
View
oActivity
nelViewModel
. SeViewModel
sopravvive all'attività (in caso di modifiche alla configurazione), la tua attività viene persa e non viene smaltita correttamente dal garbage collector. - Utilizza le coroutine Kotlin per gestire attività a lunga esecuzione e altre operazioni che possono essere eseguite in modo asincrono.
Casi d'uso per i componenti consapevoli del ciclo di vita
I componenti consapevoli del ciclo di vita possono semplificare notevolmente la gestione dei cicli di vita in una serie di casi. Ecco alcuni esempi:
- Passare da aggiornamenti della posizione approssimativi a aggiornamenti granulari e viceversa. Utilizza
componenti attenti al ciclo di vita per attivare aggiornamenti della posizione granulari mentre la
sua app di geolocalizzazione è visibile e passa agli aggiornamenti meno granulari quando l'app è
in background.
LiveData
, un componente che tiene conto del ciclo di vita, consente all'app di aggiornare automaticamente l'interfaccia utente quando l'utente cambia posizione. - Arresto e avvio del buffering dei video. Utilizza componenti consapevoli del ciclo di vita per avviare il buffering dei video il prima possibile, ma rimanda la riproduzione fino all'avvio completo dell'app. Puoi anche utilizzare componenti attenti al ciclo di vita per interrompere il buffering quando l'app viene distrutta.
- Avvio e arresto della connettività di rete. Utilizza componenti consapevoli del ciclo di vita per attivare l'aggiornamento in tempo reale (streaming) dei dati di rete mentre un'app è in primo piano e anche per mettere in pausa automaticamente l'app quando passa in background.
- Mettere in pausa e riprendere gli elementi drawable animati. Utilizza componenti consapevoli del ciclo di vita per gestire la messa in pausa degli elementi drawable animati quando l'app è in background e riprendere gli elementi drawable dopo che l'app è in primo piano.
Gestione degli eventi di interruzione
Quando un Lifecycle
appartiene a un AppCompatActivity
o a un Fragment
, lo stato di Lifecycle
diventa CREATED
e l'evento ON_STOP
viene inviato quando viene chiamato onSaveInstanceState()
di AppCompatActivity
o Fragment
.
Quando lo stato di un Fragment
o di un AppCompatActivity
viene salvato tramite
onSaveInstanceState()
, la sua UI
viene considerata immutabile fino a quando
non viene invocato
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 maggiori dettagli, visita la pagina commit()
.
LiveData
impedisce questo caso limite in modo predefinito evitando di chiamare il suo osservatore se il valore Lifecycle
associato all'osservatore non è almeno STARTED
.
Dietro le quinte, chiama
isAtLeast()
prima di decidere di invocare il suo osservatore.
Purtroppo, il metodo onStop()
di AppCompatActivity
viene chiamato dopo
onSaveInstanceState()
,
il che lascia un vuoto in cui le modifiche dello stato dell'interfaccia utente non sono consentite, ma il valore
Lifecycle
non è ancora stato spostato nello stato
CREATED
.
Per evitare questo problema, la classe Lifecycle
nella versione beta2
e versioni precedenti contrassegna lo stato come
CREATED
senza inviare l'evento in modo che qualsiasi codice che controlli lo stato corrente 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 partire dal livello API 23 e versioni precedenti, il sistema Android salva effettivamente lo stato di un'attività anche se è parzialmente coperta da un'altra attività. In altre
parole, il sistema Android chiama
onSaveInstanceState()
ma non chiama necessariamenteonStop()
. Ciò crea un intervallo potenzialmente lungo in cui l'osservatore ritiene ancora che il ciclo di vita sia attivo anche se lo stato dell'interfaccia utente non può essere modificato. - Qualsiasi classe che voglia esporre un comportamento simile a quello della classe
LiveData
deve implementare la soluzione alternativa fornita dallaLifecycle
versionebeta 2
e precedenti.
Risorse aggiuntive
Per scoprire di più sulla gestione dei cicli di vita con componenti consapevoli del ciclo di vita, consulta le seguenti risorse aggiuntive.
Campioni
- Sunflower, un'app di dimostrazione che mostra le best practice con i componenti dell'architettura
Codelab
Blog
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Panoramica di LiveData
- Utilizzare le coroutine Kotlin con componenti attenti al ciclo di vita
- Modulo Stato salvato per ViewModel