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
.
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 oggettoLiveData
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 aViewModel
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
oActivity
inViewModel
. SeViewModel
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 necessariamenteonStop()
. 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 daLifecycle
versionebeta 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
- Esempio di base dei componenti dell'architettura Android
- Sunflower, un'app demo che mostra le best practice per i componenti dell'architettura
Codelab
Blog
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Panoramica di LiveData
- Utilizzare le coroutine Kotlin con componenti sensibili al ciclo di vita
- Modulo Stato salvato per ViewModel