Gérer les cycles de vie à l'aide de composants tenant compte des cycles de vie Fait partie d'Android Jetpack.
Les composants tenant compte des cycles de vie effectuent des actions en réponse à une modification de l'état d'un autre composant (par exemple, des activités ou des fragments). Ces composants vous aident à produire un code mieux organisé, souvent plus léger, et plus facile à gérer.
Un modèle courant consiste à implémenter les actions des composants dépendants dans les méthodes de cycle de vie des activités et des fragments. Toutefois, ce modèle conduit à une mauvaise organisation du code et à la prolifération des erreurs. En utilisant des composants tenant compte des cycles de vie, vous pouvez déplacer le code des composants dépendants des méthodes du cycle de vie vers les composants eux-mêmes.
Le package androidx.lifecycle
fournit des classes et des interfaces qui vous permettent de créer des composants tenant compte des cycles de vie. Ces composants peuvent ajuster automatiquement leur comportement en fonction de l'état actuel du cycle de vie d'une activité ou d'un fragment.
La plupart des composants d'application définis dans le framework Android sont associés à des cycles de vie. Les cycles de vie sont gérés par le système d'exploitation ou le code du framework qui s'exécute dans votre processus. Ils sont essentiels au fonctionnement d'Android, et votre application doit les respecter. Sinon, vous risquez de provoquer des fuites de mémoire ou même des plantages de l'application.
Imaginez que nous ayons une activité qui indique la position de l'appareil à l'écran. Une implémentation courante peut se présenter comme suit :
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 } }
Bien que cet exemple semble correct, dans une application réelle, vous avez trop d'appels gérant l'interface utilisateur et d'autres composants en réponse à l'état actuel du cycle de vie. La gestion de plusieurs composants place une quantité considérable de code dans les méthodes de cycle de vie, telles que onStart()
et onStop()
, ce qui les rend difficiles à gérer.
De plus, rien ne garantit que le composant démarrera avant l'arrêt de l'activité ou du fragment. Cela est particulièrement vrai si nous devons effectuer une opération de longue durée telle qu'une vérification de la configuration dans onStart()
. Cela peut entraîner une condition de concurrence où la méthode onStop()
se termine avant onStart()
, ce qui maintient le composant actif plus longtemps que nécessaire.
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(); } }
Le package androidx.lifecycle
fournit des classes et des interfaces qui vous aident à résoudre ces problèmes de manière résiliente et isolée.
Lifecycle (Cycle de vie)
Lifecycle
est une classe qui contient les informations sur l'état de cycle de vie d'un composant (comme une activité ou un fragment) et permet à d'autres objets d'observer cet état.
Lifecycle
utilise deux énumérations principales pour suivre l'état du cycle de vie du composant associé :
- Événement
- Les événements de cycle de vie qui sont déclenchés à partir du framework et de la classe
Lifecycle
. Ces événements sont mappés aux événements de rappel dans les activités et les fragments. - État
- État actuel du composant suivi par l'objet
Lifecycle
.
Considérez les états comme les nœuds d'un graphe et les événements comme les arêtes entre ces nœuds.
Une classe peut surveiller l'état du cycle de vie du composant en implémentant DefaultLifecycleObserver
et en forçant les méthodes correspondantes telles que onCreate
, onStart
, etc. Vous pouvez ensuite ajouter un observateur en appelant la méthode addObserver()
de la classe Lifecycle
et en transmettant une instance de votre observateur, comme illustré dans l'exemple suivant :
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());
Dans l'exemple ci-dessus, l'objet myLifecycleOwner
implémente l'interface LifecycleOwner
, comme expliqué dans la section suivante.
LifecycleOwner
LifecycleOwner
est une interface à méthode unique qui indique que la classe possède un Lifecycle
. Cette interface possède une méthode, getLifecycle()
, qui doit être implémentée par la classe.
Si vous essayez de gérer le cycle de vie de l'ensemble d'un processus d'application, consultez ProcessLifecycleOwner
.
Cette interface élimine la propriété d'un objet Lifecycle
de classes individuelles, telles que Fragment
et AppCompatActivity
, et autorise l'écriture de composants compatibles. Toute classe d'application personnalisée peut implémenter l'interface LifecycleOwner
.
Les composants qui implémentent DefaultLifecycleObserver
fonctionnent de manière fluide avec ceux qui implémentent LifecycleOwner
, car un propriétaire peut fournir un cycle de vie qui peut être enregistré par un observateur pour observation.
Pour l'exemple de suivi de la position, nous pouvons faire en sorte que la classe MyLocationListener
implémente DefaultLifecycleObserver
, puis l'initialiser avec le paramètre Lifecycle
de l'activité dans la méthode onCreate()
. Cela permet à la classe MyLocationListener
d'être autonome, c'est-à-dire que la logique de réaction aux changements d'état du cycle de vie est déclarée dans MyLocationListener
plutôt que dans l'activité. Le fait que les composants individuels stockent leur propre logique facilite la gestion de la logique des activités et des fragments.
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 cas d'utilisation courant consiste à éviter d'effectuer certains rappels si Lifecycle
n'est pas dans un état favorable. Par exemple, si le rappel exécute une transaction de fragment après l'enregistrement de l'état de l'activité, cela déclenche un plantage. Par conséquent, ce rappel est toujours à éviter.
Pour faciliter ce cas d'utilisation, la classe Lifecycle
permet à d'autres objets d'interroger l'état actuel.
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 } }
Avec cette implémentation, notre classe LocationListener
tient entièrement compte des cycles de vie. Si vous devez utiliser notre LocationListener
à partir d'une autre activité ou d'un autre fragment, il vous suffit de l'initialiser. Toutes les opérations de configuration et de suppression sont gérées par la classe elle-même.
Si une bibliothèque fournit des classes qui doivent fonctionner avec le cycle de vie Android, nous vous recommandons d'utiliser des composants qui tiennent compte des cycles de vie. Les bibliothèques clientes peuvent facilement intégrer ces composants sans gestion manuelle du cycle de vie côté client.
Implémenter un LifecycleOwner personnalisé
Les fragments et les activités de la bibliothèque Support 26.1.0 et versions ultérieures implémentent déjà l'interface LifecycleOwner
.
Si vous souhaitez créer une interface LifecycleOwner
à partir d'une classe personnalisée, vous pouvez utiliser la classe LifecycleRegistry, mais vous devez transférer les événements dans cette classe, comme illustré dans l'exemple de code suivant :
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; } }
Bonnes pratiques concernant les composants tenant compte des cycles de vie
- Faites en sorte que vos contrôleurs d'interface utilisateur (activités et fragments) soient aussi légers que possible. Ils ne doivent pas essayer d'acquérir leurs propres données. Pour ce faire, utilisez un objet
ViewModel
et observez un objetLiveData
qui reflète les modifications apportées sur les vues. - Essayez de créer des interfaces utilisateur basées sur les données pour lesquelles votre contrôleur d'interface utilisateur est chargé de mettre à jour les vues à mesure que les données changent, ou de transmettre les actions des utilisateurs au
ViewModel
. - Placez votre logique de données dans la classe
ViewModel
.ViewModel
doit servir de connecteur entre votre contrôleur d'interface utilisateur et le reste de votre application. Soyez prudent, carViewModel
n'est pas responsable de la récupération de données (à partir d'un réseau, par exemple). Au lieu de cela,ViewModel
doit appeler le composant approprié pour récupérer les données, puis renvoyer le résultat au contrôleur d'interface utilisateur. - Utilisez la liaison de données pour maintenir une interface propre entre vos vues et le contrôleur d'interface utilisateur. Cela vous permet de rendre vos vues plus déclaratives et de réduire le code de mise à jour que vous devez écrire dans vos activités et fragments. Si vous préférez effectuer cette opération dans le langage de programmation Java, utilisez une bibliothèque telle que Butter Knife, qui permet d'éviter l'utilisation de code récurrent et d'obtenir une meilleure abstraction.
- Si votre interface utilisateur est complexe, envisagez de créer une classe presenter pour gérer les modifications de l'interface utilisateur. Cette tâche peut être laborieuse, mais elle peut faciliter le test des composants de votre interface utilisateur.
- Évitez de référencer un contexte
View
ouActivity
dans votreViewModel
. SiViewModel
survit à l'activité (en cas de modification de la configuration), votre activité fuit et n'est pas correctement éliminée par le récupérateur de mémoire. - Utilisez les coroutines Kotlin pour gérer les tâches de longue durée et les autres opérations pouvant s'exécuter de manière asynchrone.
Cas d'utilisation des composants tenant compte des cycles de vie
Les composants tenant compte des cycles de vie peuvent faciliter considérablement la gestion des cycles de vie dans de nombreux cas. Voici quelques exemples :
- Alterner entre des mises à jour de position plus ou moins précises. Utilisez des composants tenant compte du cycle de vie pour obtenir des mises à jour précises de la position lorsque votre application de localisation est visible et des notifications moins précises lorsque l'application est en arrière-plan.
LiveData
, un composant qui tient compte des cycles de vie, permet à votre application d'actualiser automatiquement l'interface utilisateur lorsque l'utilisateur change de position. - Arrêt et démarrage de la mise en mémoire tampon de vidéos. Utilisez des composants qui tiennent compte des cycles de vie pour lancer la mise en mémoire tampon de vidéos dès que possible, mais différez la lecture jusqu'à ce que l'application soit complètement démarrée. Vous pouvez également utiliser des composants qui tiennent compte des cycles de vie pour arrêter la mise en mémoire tampon lorsque votre application est détruite.
- Démarrage et arrêt de la connectivité réseau. Utilisez des composants qui tiennent compte des cycles de vie pour permettre la mise à jour en direct (streaming) des données réseau lorsqu'une application est au premier plan, et également pour la mettre en pause automatiquement lorsque l'application passe en arrière-plan.
- Mise en pause et réactivation des drawables animés. Utilisez des composants qui tiennent compte des cycles de vie pour gérer la mise en pause des drawables lorsque l'application est en arrière-plan, et reprendre les drawables une fois qu'elle passe au premier plan.
Gérer les événements d'arrêt
Lorsqu'un Lifecycle
appartient à une AppCompatActivity
ou à un Fragment
, l'état du Lifecycle
passe à CREATED
et l'événement ON_STOP
est déclenché lorsque la onSaveInstanceState()
de la AppCompatActivity
ou du Fragment
est appelée.
Lorsqu'un Fragment
ou l'état de la AppCompatActivity
est enregistré via onSaveInstanceState()
, son interface utilisateur est considérée comme immuable jusqu'à ce que ON_START
soit appelé. Toute modification de l'interface utilisateur après l'enregistrement de l'état risque de provoquer des incohérences dans l'état de navigation de votre application. C'est pourquoi FragmentManager
génère une exception si l'application exécute une FragmentTransaction
une fois l'état enregistré. Pour en savoir plus, consultez commit()
.
LiveData
permet d'éviter ce cas de figure en s'abstenant d'appeler son observateur si le Lifecycle
associé à celui-ci n'est pas au moins STARTED
.
En arrière-plan, il appelle isAtLeast()
avant de décider d'appeler son observateur.
Malheureusement, la méthode onStop()
de AppCompatActivity
est appelée après onSaveInstanceState()
, ce qui laisse un intervalle de temps au cours duquel les changements d'état de l'interface utilisateur ne sont pas autorisés, alors que le Lifecycle
n'a pas encore été défini sur l'état CREATED
.
Pour éviter ce problème, la classe Lifecycle
de la version beta2
et des versions inférieures marque l'état comme CREATED
sans déclencher l'événement. Ainsi, tout code qui vérifie l'état actuel obtient la valeur réelle, même si l'événement n'est pas déclenché tant que onStop()
n'a pas été appelé par le système.
Malheureusement, cette solution présente deux problèmes majeurs :
- Au niveau d'API 23 et inférieur, le système Android enregistre l'état d'une activité, même si elle est partiellement couverte par une autre activité. En d'autres termes, le système Android appelle
onSaveInstanceState()
, mais pas nécessairementonStop()
. Cela crée un intervalle potentiellement long pendant lequel l'observateur pense que le cycle de vie est actif, même si l'état de son interface utilisateur ne peut pas être modifié. - Toute classe qui souhaite exposer un comportement semblable à la classe
LiveData
doit implémenter la solution de secours fournie parLifecycle
versionbeta 2
et inférieure.
Ressources supplémentaires
Pour en savoir plus sur la gestion des cycles de vie avec des composants qui tiennent compte des cycles de vie, consultez les ressources supplémentaires suivantes.
Exemples
- Sunflower, une application de démonstration qui montre les bonnes pratiques associées aux composants d'architecture
Ateliers de programmation
Blogs
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Présentation de LiveData
- Utiliser des coroutines Kotlin avec des composants tenant compte du cycle de vie
- Module Saved State pour ViewModel