Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Cross-plateforme : Android – Part 3: Activité et cycle de vie

[new:30/05/2013]Les parties 1 et 2 ont planté le décor : pourquoi utiliser Android dans une solution cross-plateforme et qu’est que Android. Aujourd’hui nous entrons dans le vif du sujet : la notion d’activité et son cycle de vie.

Android et les Activités (activity)

Les activités sont les éléments fondamentaux des applications Android. Elles existent sous différents états, de leur création à leur fin.

Une activité est un concept simple ciblé sur ce que l’utilisateur peut faire, d’où son nom. De base toutes les activités interagissent donc avec l’utilisateur (il y a des exceptions) et c’est la classe Activity qui s’occupe de créer une fenêtre pour le que le développeur puisse y charger l’UI relative à l’activité en question.

Le plus souvent les activités sont présentées à l’utilisateur en mode plein écran, mais elles peuvent aussi apparaitre sous la forme de fenêtres flottantes ou même être incorporées à l’intérieur d’autres activités.

Chaque activité est autonome et possède donc une partie code et une partie visuelle.

Le cycle de vie d’une activité commence avec son instanciation et se termine par sa destruction. Entre les deux il existe de nombreux états intermédiaires. Lorsque l'activité change d'état la méthode d'évènement du cycle de vie appropriée est appelée pour avertir l'activité de la modification imminente de son état et lui permettre d'exécuter éventuellement du code pour s'adapter à ces changements.

On retrouve là un mécanisme propre à tous les OS mobiles comme iOS ou Windows Phone (voire même WinRT sur PC). Ce mécanisme s’est imposé partout car il permet de gérer de nombreuses applications de façon fluide sans pour autant consommer trop de puissance. Ainsi une application qui n’a plus l’avant-plan se trouve-t-elle écartée par l’OS qui la place dans une sorte de coma. L’application a eu le temps de sauvegarder son état et lorsque l’application est rappelée par l’utilisateur l’OS la sort de ce coma et lui offre la possibilité de se “réhydrater” : l’utilisateur croit reprendre le cours de ce qu’il avait arrêté, l’UX est donc de bonne qualité, mais entre temps, pendant son “coma”, l’application n’a pas consommé de ressources, ce qui ménage l’unité mobile. J’utilise ici le terme de “coma” en écho à un terme américain utilisé pour décrire cette mise en sommeil, le “tombstoning”, littéralement “pierre tombale – isation”.
De même je rappelle que lorsque je parle de “mobiles” je parle “d’unités mobiles” et que cela intègre aussi bien les smartphones que les tablettes.

Le concept

Les activités sont un concept de programmation inhabituel totalement spécifique à Android mais simple à comprendre.
Dans le développement d'applications traditionnelles il y a généralement une méthode statique “main” qui est exécutée pour lancer l'application. C’est le cas en mode console sous Windows par exemple, ou même dans une application WPF.
Avec Android, cependant, les choses sont différentes; les applications Android peuvent être lancées via n'importe quelle activité enregistré au sein d'une application, chacune jouant le rôle d’un point d’entrée “main” en quelque sorte. Dans la pratique la plupart des applications n'ont qu'une seule activité spécifique qui est défini comme le point d'entrée unique de l'application. Toutefois, si une application se bloque ou est terminée par l'OS ce dernier peut essayer de la redémarrer sur la dernière activité ouverte ou n'importe où ailleurs dans la pile de l'activité précédente. En outre, les activités peuvent être mises en pause par l’OS quand elles ne sont pas actives voire même être supprimées totalement de la mémoire si cette dernière vient à manquer.
Une attention particulière doit être portée pour permettre à l'application de restaurer correctement son état dans le cas où elle est redémarré et dans le cas où son fonctionnement repose sur des données provenant des activités précédentes.

Le cycle de vie d’une activité est représenté par un ensemble de méthodes que le système d'exploitation appelle tout au long du cycle de vie de cette activité pour la tenir au courant de son état à venir. Ces méthodes permettent au développeur d'implémenter les fonctionnalités nécessaires pour satisfaire aux exigences de gestion de l'état et des ressources de son application.

Il est extrêmement important pour le développeur de l'application d’analyser les besoins de chaque activité pour déterminer les méthodes exposées par la gestion du cycle de vie qui doivent être prises en charge. Ne pas le faire peut entrainer une instabilité de l'application, des bogues, des problèmes avec les ressources et peut-être même dans certains cas avoir une incidence sur la stabilité de l’OS.

La gestion du cycle de vie

La gestion du cycle de vie des applications de Android se matérialise pour le développeur par un ensemble de méthodes exposées par la classe Activity, ses méthodes fournissent un cadre de gestion des ressources. La bonne gestion du cycle de vie est l’ossature d’une application Android (comme sous iOS ou Windows Phone), c’est une colonne vertébrale d’où partent les “côtes” qui vont structurer le squelette et en assurer la cohérence.

Les différents états

Le système d'exploitation Android utilise une file de priorité pour aider à gérer les activités exécutées sur la machine. Selon l'état dans lequel se trouve une activité particulière Android lui attribue une certaine priorité au sein de l'OS. Ce système de priorité permet à Android d’identifier les activités qui ne sont plus en cours d'utilisation, ce qui lui de récupérer de la mémoire et des ressources. Le schéma suivant illustre les états par lesquels une activité peut passer au cours de sa durée de vie :

image

(de haut en bas : Démarrage de l’activité, activité en mode de fonctionnement, activité en mode pause, activité stoppée, activitée arrêtée. Légende à gauche : processus détruit. Légende à droite : processus en sommeil).

On retrouve ici un schéma classique sur les OS mobiles qu’on peut résumer à trois groupes principaux :

Actif ou en fonctionnement

Les activités sont considérées comme “actives” ou “en cours de fonctionnement” si elles sont au premier plan qu’on appelle aussi “sommet de la pile d’activité”. L’activité au premier plan est celle possédant la plus haute priorité dans la piles des activités gérée par l’OS. Elle conservera se statut particulier sauf si elle est tuée par l’OS dans des circonstances bien précises et extrêmes : si l’application tente de consommer plus de mémoire que n’en possède l’appareil par exemple car cela pourrait provoquer un blocage complet de l’interface utilisateur. Une bonne UX est parfois obligée de reposer sur des choix douloureux de type “peste ou choléra”. Dans ce cas précis se trouver face à une application qui “plante” n’est pas une bonne UX mais elle est “moins pire” que de se retrouver devant une machine qui ne répond plus du tout.

Mode Pause

Lorsque l’unité mobile se met en veille ou qu’une activité est encore visible mais partiellement cachée par une nouvelle activité non “full size” ou transparente, l’activité est alors considérée (et mise) en mode pause.

Lorsqu’elle sont en pause les activités sont encore “en vie”. Cela signifie qu’elles conservent toutes leurs informations en mémoire et qu’elles restent attachées au gestionnaire de fenêtre. Techniquement une application en pause est considérée comme la deuxième activité prioritaire sur la pile des activités et à ce titre ne sera tuée par l’OS que si l’activité active (premier plan) réclame plus de ressources et qu’il n’y a plus d’autres moyens de lui en fournir pour qu’elle reste stable et réactive.

Mode Arrêt

Les activités qui sont complètement masquées par d’autres sont considérées comme arrêtées. Cet arrêt est soit total soit partiel si l’activité en question produit un travail d’arrière-plan. Android tente de maintenir autant qu’il le peut les informations et ressources des applications arrêtées, mais ce mode est celui qui possède la plus basse priorité dans la pile des activité de l’OS. A ce titre elles feront partie des premières activités tuées par l’OS dès qu’un besoin de ressources se fera sentir.

Effet des touches de navigation

On retrouve sous Android deux boutons importants : l’accueil et la navigation arrière (le troisième appelle les paramètres). Quel est l’effet de l’appui sur ces boutons vis-à-vis du cycle de vie des applications ?

La logique est simple : si la touche de navigation arrière est utilisée Android considère que l’activité qui vient d’être quittée n’est plus en premier plan. Ce qui est une lapalissade. De ce fait, et à la vue de ce que nous avons vu plus haut, elle passe à un niveau de priorité faible et peut être tuée à tout moment pour récupérer les ressources qu’elle utilise.

Pour mettre en œuvre le multitâche sous Android il faut ainsi utiliser la touche d’accueil qui permet par exemple de basculer entre les applications qui occupent virtuellement le premier plan. Quitter une application par la navigation arrière la condamne à courte échéance, quitter une application par la touche accueil la fait passer en mode arrière-plan. Les priorités ne sont pas les mêmes, les ressources d’une application en arrière-plan sont conservées plus longtemps ce qui offre une meilleure réactivité côté utilisateur.

Les différentes méthodes du cycle de vie

Le SDK de Android (et par extension le framework fournit par Xamarin) fournit un modèle complet pour gérer l’état des activités au sein d’une application. Lorsque l’état de l’activité est sur le point de changer celle-ci est notifiée par l’OS et les méthodes associées à ce changement dans la classe Activity sont alors appelées.

Note: On remarquera que Android gère le cycle de vie avec une granularité très fine puisque c’est au niveau de chaque activité que le mécanisme s’applique. Une application peut avoir plusieurs activités et chacune peut donc se trouver dans un état particulier qui n’est pas global à l’application. Si les application simples ne comportent généralement qu’une seule activité, ce n’est pas le cas des applications plus étoffées. Ces dernières profitent alors de ce mode de gestion des ressources particulièrement économe.

Voici le schéma qui illustre les différents états et leurs transitions entrainant les appels aux méthodes de la classe Activity dans le cadre de la gestion du cycle de vie de chaque activité :

image

La gestion de ces méthodes s’effectue tout simplement par surcharge. Toutes ne sont pas à prendre en compte dans chaque activité, comme expliqué plus haut c’est au développeur d’analyser les besoins de son application et de décider pour chaque activité de la pertinence de la prise en compte des états.

OnCreate

C’est la première méthode de gestion du cycle de vie d’une activité. Elle est appelée lorsque cette dernière est créée par l’OS. L’application doit au minimum surcharger cette méthode pour charge sa Vue principale. S’il y a d’autres besoins d’initialisations c’est bien entendu aussi à cet endroit qu’elles prendront place.

Le paramètre passé à la méthode est de type “Bundle”. C’est un dictionnaire qui permet de stocker et de passer des informations entre les activités. En programmation Windows on appellerait plutôt cela un “contexte”. Si le paquet Bundle est non nul cela indique que l’activité est reprise depuis un état antérieur et qu’elle peut être ré-hydratée.

L’application peut aussi utiliser le conteneur “Extras” de l’objet “Intent” pour restaurer des données précédemment enregistrées ou des données passées entre les activités.

L’extrait de code ci-dessous montre comment l’activité détecte une reprise pour se ré-hydrater via le Bundle mais aussi comment elle accède aux Extras de l’Intent pour récupérer d’autres données.

(ce code est un exemple en Xamarin puisque nous n’aborderons la programmation Android qu’au travers de C# et via cet outil)

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    string intentString;
    bool intentBool;
    string extraString;
    bool extraBool;
    if (bundle != null)
    {
        //Si l'activité est reprise (resume)
        intentString = bundle.GetString("myString");
        intentBool = bundle.GetBoolean("myBool");
    }
 
    // Accès aux valeurs dans le Intent Extras
    extraString = Intent.GetStringExtra("myString");
    extraBool = Intent.GetBooleanExtra("myBool", false);
    // Initialisation de la vue "main" depuis les ressources de layout
    SetContentView(Resource.Layout.Main);
}

 

OnStart

Cette méthode est appelée lorsque l'activité est sur le point de devenir visible à l'utilisateur. Les activités doivent remplacer cette méthode si elles ont besoin d’effectuer des tâches spécifiques juste avant l’affichage, tels que: le rafraichissement des valeurs des vues au sein de l'activité, ou faire du ramping up de la vitesse des frames (une tâche habituelle dans la construction d’un jeu ou le nombre de FPS doit être calibré finement pour assurer la jouabilité).

OnPause

Cette méthode est appelée lorsque le système est sur le point de mettre l'activité au second plan. Les activités doivent surcharger cette méthode si elles ont besoin de valider les modifications non sauvegardées des données persistantes, de détruire ou nettoyer d'autres objets qui consomment des ressources ou qui abaissent les FPS, etc.
L’implémentation de cette méthode doit retourner le plus rapidement possible car aucune activité ne sera reprise jusqu'à ce que cette méthode retourne. L'exemple suivant illustre comment surcharger la méthode OnPause pour sauvegarder les données dans le conteneur Extras afin qu’elles puissent être transmises entre les activités :

protected override void OnPause()
{
    Intent.PutExtra("myString", "Xamarin.Android OnPause exécuté !");
    Intent.PutExtra("myBool", true);
    base.OnPause();
}

 

Windows Phone et WinRT en général imposent des limites de temps très courtes aux actions de ce type, Android est plus souple mais cela ne veut pas dire qu’il ne faut pas s’en préoccuper ! Il peut ainsi s’avérer nécessaire de mettre en place une stratégie de sauvegarde des données plus sophistiquées dans le cas où ces données sont importantes et peuvent prendre du temps à écrire sur “disque”, par exemple en enregistrant les données modifiées au fur et à mesure de leur saisie par l’utilisateur. Ainsi le travail à fournir dans OnPause sera très court, voire nul.

OnResume

Cette méthode est appelée lorsque l'activité commencera à interagir avec l'utilisateur juste après avoir été dans un état de pause. Lorsque cette méthode est appelée l'activité se déplace vers le haut de la pile d'activité et elle reçoit alors les entrées de l'utilisateur. Les activités peuvent surcharger cette méthode si elles ont besoin d’exécuter des tâches après que l'activité commence à accepter les entrées utilisateur.
Android offre plus de souplesse que Windows Phone dans le cycle de vie, cela signifie aussi qu’il faut faire plus attention aux méthodes qu’on choisit pour exécuter tel ou tel code !

OnStop

Cette méthode est appelée lorsque l'activité n'est plus visible pour l'utilisateur car une autre activité a été reprise ou commencée et qu’elle recouvre visuellement celle-ci. Cela peut se produire parce que l'activité est terminée (la méthode Finish a été appelée) ou parce que le système est en train de détruire cette instance de l'activité pour économiser des ressources, soit parce qu'un changement d'orientation a eu lieu pour le périphérique. Ces conditions sont très différentes ! Vous pouvez les distinguer en utilisant la propriété IsFinishing.
Une activité doit surcharger cette méthode si elle a besoin d'effectuer des tâches spécifiques avant d'être détruite ou si elle est sur le point de lancer la construction d’une nouvelle interface utilisateur après un changement d'orientation.

OnRestart

Cette méthode est appelée après que l’activité a été arrêtée et avant d'être relancée. Cette méthode est toujours suivie par OnStart. Une activité doit surcharger OnRestart si elle a besoin d'exécuter des tâches immédiatement avant OnStart. Par exemple si l'activité a déjà été envoyée en arrière-plan et que OnStop a été appelé, mais que le processus de l'activité n'a pas encore été détruit par le système d'exploitation. Dans ce cas la méthode OnRestart doit être neutralisée.

Un bon exemple de ce cas de figure est quand l'utilisateur appuie sur le bouton Accueil alors qu’il utilise l'application. OnPause puis la méthode OnStop sont appelés mais l'activité n'est pas détruite. Si l'utilisateur veut restaurer l'application en utilisant le gestionnaire de tâches (ou une méthode similaire) la méthode OnRestart de l'activité sera appelée par le système d'exploitation lors de la réactivation des activités.

OnDestroy

C'est la dernière méthode qui est appelée sur une activité avant qu'elle ne soit détruite. Après cette méthode l’activité sera tuée et purgée des pools de ressources de l'appareil. Le système d'exploitation va détruire définitivement les données d'état d'une activité après l’exécution de cette méthode. OnDestroy est un peu le dernier bar avant le désert… si une activité doit sauvegarder des données c’est le dernier emplacement pour le faire.

OnSaveInstanceState

Cette méthode est fournie par le gestionnaire de cycle de vie Android pour donner à une activité la possibilité de sauvegarder ses données, par exemple sur un changement d’orientation de l'écran. Le code suivant illustre comment les activités peuvent surcharger la méthode OnSaveInstanceState pour enregistrer les données en vue d’une réhydratation lorsque la méthode OnCreate sera appelée :

protected override void OnSaveInstanceState(Bundle outState)
{
    outState.PutString("myString", Xamarin.Android OnSaveInstanceState");
    outState.PutBoolean("myBool", true);
    base.OnSaveInstanceState(outState);
}

 

Conclusion

La gestion des activités et de leur cycle de vie est un point essentiel à maitriser pour développer sur Android. Le reste c’est du code ou de l’affichage comme on peut en faire dans des tas d’OS. Il reste beaucoup choses à connaitre pour être un expert Android, c’est vrai, mais une telle expertise commence par la compréhension totale et complète du cycle de vie.

Le “tombstonig” qui implique la sauvegarde et la réhydratation des applications, la gestion particulière sous Android d’être prévenu d’un changement d’orientation de l’appareil, la notion même d’activité sont des concepts de base qui structurent l’écriture du code, qui dictent ce qui est possible de faire et comment le réaliser. Puisque nous utilisons C#, le reste n’est que du code tel qu’on peut en écrire pour n’importe quelle application.

Le deuxième aspect fondamental à comprendre est la gestion de l’interface utilisateur puisque là, hélas, il n’y a pas d’autre solution que d’être natif… Et Android utilise sa propre logique. Nous verrons que malgré tout on retrouve des concepts habituels, qu’ils proviennent de HTML ou de XAML.

Plus on utilise d’OS plus en apprendre de nouveaux est simple car au final on s’aperçoit que tous doivent régler les mêmes problèmes et fournissent sensiblement les mêmes moyens de le faire. La gestion du cycle de vie des activités que nous venons d’appréhender dans ce billet le montre bien pour qui sait comment d’autres OS tel que Windows Phone le font. Les similitudes sont grandes. Toute l’astuce est donc de bien comprendre les différences et leurs impacts sur le code à écrire !

J’y reviendrais forcément dans les prochains billets…

Stay Tuned !

blog comments powered by Disqus