Temps de démarrage de l'application

Les utilisateurs s'attendent à ce que les applications soient réactives et se chargent rapidement. Une application dont le démarrage est lent ne répond pas à cette attente et peut décevoir les utilisateurs. Cette mauvaise expérience peut conduire l'utilisateur à donner une mauvaise note à votre application sur le Play Store, voire à l'abandonner complètement.

Cette page fournit des informations pour vous aider à optimiser le délai de démarrage de votre application. Elle offre un aperçu des mécanismes internes du processus de lancement, explique comment profiler les performances de démarrage et présente certains problèmes courants de démarrage, ainsi que des conseils pour y remédier.

Comprendre les différents états de démarrage de l'application

Le lancement d'une application peut avoir l'un des trois états suivants : démarrage à froid, démarrage tiède ou démarrage à chaud. Chaque état a une incidence sur le délai nécessaire pour que votre application soit visible par l'utilisateur. À froid, votre application part de zéro. Dans les autres états, le système doit faire passer l'application en cours d'exécution de l'arrière-plan au premier plan.

Nous vous recommandons de toujours optimiser votre application comme si elle démarrait d'office à froid. Cela peut également améliorer les performances des démarrages tiède et à chaud.

Pour optimiser votre application afin qu'elle démarre rapidement, il est utile de comprendre ce qui se passe au niveau du système et de l'application, ainsi que la façon dont ils interagissent, dans chacun de ces états.

Le délai d'affichage initial (TTID) et le délai d'affichage complet (TTFD) sont deux métriques importantes pour déterminer le démarrage de l'application. Le TTID représente le délai nécessaire pour afficher la première image, et le TTFD correspond au délai nécessaire pour que l'application devienne entièrement interactive. Les deux sont tout aussi importants, car le TTID indique à l'utilisateur que l'application est en cours de chargement, et le TTFD indique à quel moment l'application est réellement utilisable. Si l'un de ces délais est trop long, l'utilisateur peut quitter l'application avant même qu'elle ne soit complètement chargée.

Pour obtenir des valeurs précises pour le délai d'affichage total, envoyez un signal lorsque l'application est à son état d'affichage complet afin que les délais précis soient mesurés. Pour savoir comment procéder, consultez la section Améliorer la précision du délai de démarrage.

Démarrage à froid

Le terme "démarrage à froid" désigne le démarrage d'une application à partir de zéro. Cela signifie que jusqu'au démarrage, le processus du système crée le processus de l'application. Les démarrages à froid se produisent dans les cas où votre application se lance pour la première fois depuis le démarrage de l'appareil ou lorsque le système a arrêté l'application.

Ce type de démarrage représente le plus grand défi pour réduire le délai de démarrage, car le système et l'application ont alors davantage de travail par rapport aux autres états de lancement.

Au début d'un démarrage à froid, le système doit effectuer les trois tâches suivantes :

  1. Charger et lancer l'application.
  2. Afficher une fenêtre de démarrage vide pour l'application immédiatement après le lancement.
  3. Créer le processus d'application.

Dès que le système a créé le processus d'application, ce processus est responsable des étapes suivantes :

  1. Créer l'objet d'application.
  2. Lancer le thread principal.
  3. Créer l'activité principale.
  4. Gonfler les vues.
  5. Mettre en page l'écran.
  6. Effectuer la visualisation initiale.

Une fois la première visualisation terminée, le processus système remplace la fenêtre d'arrière-plan affichée par l'activité principale. L'utilisateur peut alors commencer à utiliser l'application.

La figure 1 montre comment les processus du système et de l'application se passent la main.

Figure 1 : Représentation visuelle des principaux éléments du démarrage à froid d'une application

Des problèmes de performances peuvent survenir au moment où l'application et l'activité sont créées.

Création d'applications

Lorsque l'application est lancée, la fenêtre de démarrage vide reste affichée jusqu'à ce que le système ait fini d'afficher l'application. À ce stade, le processus système remplace la fenêtre de démarrage de votre application, ce qui permet à l'utilisateur d'interagir avec elle.

Si vous remplacez Application.onCreate() dans votre propre application, le système appelle la méthode onCreate() sur votre objet d'application. Par la suite, l'application génère le thread principal, également appelé thread UI, et le charge de créer votre activité principale.

À ce stade, les processus au niveau du système et de l'application se déroulent conformément aux étapes du cycle de vie de l'application.

Création de l'activité

Une fois que le processus de l'application a créé votre activité, celle-ci effectue les opérations suivantes :

  1. Initialisation des valeurs
  2. Appel des constructeurs
  3. Appel à la méthode de rappel, telle que Activity.onCreate(), adaptée à l'état du cycle de vie actuel de l'activité

En général, la méthode onCreate() a l'impact le plus élevé sur le temps de chargement, car elle effectue les tâches dont la surcharge est la plus élevée : charger et gonfler les vues, et initialiser les objets nécessaires à l'exécution de l'activité.

Démarrage tiède

Un démarrage tiède englobe un sous-ensemble des opérations qui ont lieu lors d'un démarrage à froid. Pour autant, ce sous-ensemble représente une surcharge plus importante que celle d'un démarrage à chaud. De nombreux états potentiels peuvent être considérés comme des démarrages tièdes, par exemple :

  • L'utilisateur quitte votre appli, puis la redémarre. Le processus peut continuer à s'exécuter, mais l'application doit recréer l'activité à partir de zéro à l'aide d'un appel à onCreate().

  • Le système évince votre application de la mémoire, puis l'utilisateur la relance. Le processus et l'activité doivent redémarrer, mais la tâche peut bénéficier du groupe d'états d'instances enregistrés transmis à onCreate().

Démarrage à chaud

Un démarrage à chaud de votre application nécessite moins d'efforts qu'un démarrage à froid. Lors d'un démarrage à chaud, le système met votre activité au premier plan. Si toutes les activités de votre application sont toujours en mémoire, l'application n'a pas besoin de répéter l'initialisation des objets, le gonflage de la mise en page et l'affichage.

Toutefois, si une partie de la mémoire est définitivement vidée à la suite d'événements de nettoyage, tels que onTrimMemory(), ces objets doivent être recréés en réponse au démarrage à chaud.

Le démarrage à chaud présente le même comportement à l'écran que le démarrage à froid : Le processus système affiche un écran vide jusqu'à ce que l'application ait fini d'effectuer le rendu de l'activité.

Figure 2 : Schéma représentant les différents états de démarrage et leurs processus respectifs, chaque état commençant par le premier frame affiché.

Comment identifier le démarrage de l'application dans Perfetto

Pour déboguer les problèmes de démarrage de l'application, il est utile de déterminer exactement ce qui est inclus dans la phase de démarrage de l'application. Pour identifier toute la phase de démarrage de l'application dans Perfetto, procédez comme suit :

  1. Dans Perfetto, recherchez la ligne contenant la métrique dérivée des démarrages de l'application Android. Si vous ne la voyez pas, essayez de capturer une trace à l'aide de l'application de traçage système sur l'appareil.

    Figure 3 : Le segment des métriques dérivées des démarrages de l'application Android dans Perfetto.
  2. Cliquez sur le segment associé, puis appuyez sur m pour le sélectionner. Des crochets apparaissent autour du segment et indiquent la durée du processus. La durée est également affichée dans l'onglet Current selection (Sélection actuelle).

  3. Épinglez la ligne des démarrages de l'application Android en cliquant sur l'icône en forme de punaise, qui s'affiche lorsque vous maintenez le pointeur sur la ligne.

  4. Faites défiler l'écran vers le bas jusqu'à la ligne contenant l'application en question, puis cliquez sur la première cellule pour développer la ligne.

  5. Effectuez un zoom avant sur le thread principal, généralement en haut, en appuyant sur w (appuyez respectivement sur s, a ou d pour effectuer un zoom arrière, vous déplacer vers la gauche ou vous déplacer vers la droite).

    Figure 4 : Segment des métriques dérivées des démarrages de l'application Android à proximité du thread principal de l'application.
  6. Le segment de métriques dérivées permet de voir plus facilement ce qui est inclus dans le démarrage de l'application, ce qui vous permet de poursuivre le débogage plus en détail.

Utiliser les métriques pour détecter et diagnostiquer les problèmes

Pour diagnostiquer correctement les performances liées au délai de démarrage, vous pouvez suivre les métriques qui indiquent le temps nécessaire au démarrage de votre application. Android vous propose plusieurs façons de vous signaler que votre application rencontre un problème et vous aide à le diagnostiquer. Android Vitals peut vous avertir que le problème se produit, et les outils de diagnostic peuvent vous aider à le diagnostiquer.

Avantages des métriques de démarrage

Android utilise les métriques Délai d'affichage initial (TTID) et Délai d'affichage total (TTFD) pour optimiser les démarrages d'application à froid et tiède. Android Runtime (ART) utilise les données de ces métriques pour précompiler efficacement le code afin d'optimiser les démarrages ultérieurs.

Des démarrages plus rapides entraînent une interaction plus durable des utilisateurs avec votre application, ce qui réduit le nombre d'instances de sortie anticipée, de redémarrage ou de navigation vers une autre application.

Android Vitals

Android Vitals peut vous aider à améliorer les performances de votre application en vous alertant via la Play Console lorsque les temps de démarrage de votre application sont trop longs.

Android Vitals considère que le délai de démarrage suivant de votre application est excessif :

  • le démarrage à froid prend cinq secondes ou plus ;
  • le démarrage tiède prend deux secondes ou plus ;
  • le démarrage à chaud prend 1,5 seconde ou plus.

Android Vitals utilise la métrique Délai d'affichage initial. Pour savoir comment Google Play collecte les données Android Vitals, consultez la documentation de la Play Console.

Délai d'affichage initial

Le délai d'affichage initial mesure le temps nécessaire pour qu'une application produise sa première image, y compris l'initialisation du processus à froid, la création d'activités lors d'un démarrage à froid ou tiède, et l'affichage de la première image.

Comment récupérer le délai d'affichage initial

Logcat inclut une ligne de sortie contenant une valeur appelée Displayed. Cette valeur représente le temps écoulé entre le lancement du processus et l'affichage de l'activité correspondante à l'écran. Le temps écoulé comprend la séquence d'événements suivante :

  • Lancement du processus
  • Initialisation des objets
  • Création et initialisation de l'activité
  • Gonflage de la mise en page
  • Affichage de votre application pour la première fois.

La ligne de journal indiquée ressemble à l'exemple suivant :

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

Si vous suivez la sortie logcat à partir de la ligne de commande ou dans un terminal, il est facile de trouver le temps écoulé. Pour connaître le temps écoulé dans Android Studio, désactivez les filtres dans la vue Logcat. Cette opération est nécessaire, car c'est le serveur système, et non l'appli elle-même, qui gère ce journal.

Une fois que vous avez défini les paramètres appropriés, vous pouvez rechercher le terme approprié pour afficher la durée. La figure 3 montre comment désactiver les filtres en sélectionnant "No filters" (Aucun filtre) dans la liste déroulante, puis dans la deuxième ligne de sortie en bas, un exemple de sortie logcat correspondant à la durée Displayed.

Figure 5 : Désactivation des filtres et recherche de la valeur Displayed dans logcat

La métrique Displayed dans la sortie logcat ne capture pas systématiquement le temps nécessaire avant que toutes les ressources ne soient chargées et affichées. Elle exclut les ressources qui ne sont pas référencées dans le fichier de mise en page ou que l'application crée dans le cadre de l'initialisation de l'objet. Elle exclut ces ressources, car leur chargement est un processus intégré et ne bloque pas l'affichage initial de l'application.

La ligne Displayed de la sortie logcat contient parfois un champ supplémentaire indiquant la durée totale. Par exemple :

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

Dans ce cas, la première mesure ne concerne que l'activité qui a été visualisée pour la première fois. La mesure de la durée total commence au début du processus de l'application et peut inclure une autre activité démarrée en premier, mais qui n'affiche rien à l'écran. La mesure du temps total n'est affichée que s'il existe une différence entre l'activité unique et le temps total de démarrage.

Vous pouvez également mesurer le TTID en exécutant votre application avec la commande ADB Shell Activity Manager. Exemple :

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

La métrique Displayed apparaît dans la sortie logcat comme précédemment. La fenêtre de terminal affiche les éléments suivants :

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

Les arguments -c et -a sont facultatifs et vous permettent de spécifier une <category> et une <action>.

Délai d'affichage total

La métrique "Délai d'affichage total" mesure le temps nécessaire à l'application pour produire sa première image au contenu complet, y compris le contenu chargé de manière asynchrone après la première image. Il s'agit généralement du contenu de la liste principale chargée depuis le réseau, comme indiqué par l'application.

Comment récupérer le délai d'affichage total

Vous pouvez utiliser la méthode reportFullyDrawn() pour mesurer le temps écoulé entre le lancement de l'application et l'affichage complet de toutes les ressources et les hiérarchies de vues. Cela peut être utile lorsqu'une application effectue un chargement différé. Avec le chargement différé, une application ne bloque pas l'affichage initial de la fenêtre, mais charge les ressources de manière asynchrone et met à jour la hiérarchie des vues.

En raison du chargement différé, si l'affichage initial d'une application n'inclut pas toutes les ressources, vous pouvez envisager de charger et d'afficher toutes les ressources et vues comme une métrique distincte. Par exemple, votre interface utilisateur peut être entièrement chargée avec du texte affiché, mais il se peut qu'elle n'affiche pas encore des images que l'application doit extraire du réseau.

L'article Améliorer la précision du délai de démarrage explique comment utiliser FullyDrawnReporter pour retarder les appels reportFullyDrawn jusqu'à ce que votre application devienne interactive.

Lorsque vous utilisez cette API, la valeur affichée par logcat correspond au temps écoulé entre la création de l'objet d'application et le moment où reportFullyDrawn() est appelé. Voici un exemple de résultat logcat :

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

La sortie logcat inclut parfois une durée total, comme indiqué dans la section Délai d'affichage initial.

Si vous découvrez que les délais d'affichage sont plus lents que vous le souhaitez, vous pouvez essayer d'identifier les goulots d'étranglement dans le processus de démarrage.

Identifier les goulots d'étranglement

Pour identifier les goulots d'étranglement, vous pouvez utiliser le Profileur de processeur Android Studio. Pour en savoir plus, consultez la section Inspecter l'activité du processeur avec le Profileur de processeur.

Vous pouvez également obtenir des renseignements sur les goulots d'étranglement potentiels grâce au traçage intégré dans les méthodes onCreate() de vos applications et activités. Pour en savoir plus sur le traçage intégré, consultez la documentation sur les fonctions Trace et la présentation du traçage système.

Résoudre les problèmes courants

Cette section traite de plusieurs problèmes qui affectent souvent les performances de démarrage des applications. Ces problèmes concernent principalement l'initialisation des objets d'application et d'activité, ainsi que le chargement des écrans.

Initialisation d'application à forte charge

Les performances de lancement peuvent être affectées lorsque votre code ignore l'objet Application et exécute un travail à forte charge ou une logique complexe lors de l'initialisation de cet objet. Votre application peut perdre du temps au démarrage si les sous-classes Application effectuent des initialisations qui ne sont pas nécessaires à ce stade.

Certaines initialisations peuvent être complètement inutiles, telles que l'initialisation des informations d'état pour l'activité principale lorsque l'application est démarrée en réponse à un intent. Avec un intent, l'application n'utilise qu'un sous-ensemble des données d'état précédemment initialisées.

Lors de l'initialisation de l'application, d'autres défis incluent les événements de récupération de mémoire à impact élevé ou en quantité importante, ou les opérations d'E/S sur le disque exécutées simultanément à l'initialisation, ce qui bloque encore davantage le processus d'initialisation. La récupération de mémoire est particulièrement importante pour l'environnement d'exécution Dalvik. L'environnement d'exécution Art exécute la récupération de mémoire en même temps, ce qui réduit l'impact de cette opération.

Diagnostiquer le problème

Vous pouvez utiliser le traçage de méthode ou le traçage intégré pour essayer de diagnostiquer le problème.

Traçage de méthode

L'exécution du Profileur de processeur révèle que la méthode callApplicationOnCreate() appelle votre méthode com.example.customApplication.onCreate. Si l'outil montre que l'exécution de ces méthodes prend beaucoup de temps, vous devez examiner les tâches à effectuer de plus près.

Traçage intégré

Utilisez le traçage intégré pour identifier les causes probables de ces problèmes, notamment :

  • La fonction onCreate() initiale de votre application
  • Tout objet Singleton global initialisé par votre application
  • Toute opération d'E/S sur le disque, désérialisation ou boucle serrée qui peut se produire durant le goulot d'étranglement

Les solutions au problème

Que le problème soit dû à des initialisations inutiles ou à des opérations d'E/S sur le disque, l'initialisation différée est la solution. En d'autres termes, n'initialisez que les objets qui sont nécessaires immédiatement. Au lieu de créer des objets statiques globaux, passez à un modèle Singleton où l'application initialise les objets uniquement la première fois qu'elle en a besoin.

Envisagez également d'utiliser un framework d'injection de dépendances comme Hilt, qui crée des objets et des dépendances lors de leur première injection.

Si votre application utilise des fournisseurs de contenu pour initialiser les composants de l'application au démarrage, envisagez plutôt d'utiliser la bibliothèque de démarrage d'application.

Initialisation d'activité à forte charge

La création d'activités implique souvent d'importants efforts. Il est souvent possible d'optimiser cette tâche afin d'améliorer les performances. Ces problèmes courants sont les suivants :

  • Gonflage des mises en page complexes ou de grande taille
  • Blocage de la visualisation à l'écran sur le disque ou l'E/S réseau
  • Chargement et décodage des bitmaps
  • Trame des objets VectorDrawable
  • Initialisation d'autres sous-systèmes de l'activité

Diagnostiquer le problème

Dans ce cas, le traçage de méthode et le traçage intégré peuvent également s'avérer utiles.

Traçage de méthode

Lorsque vous utilisez le Profileur de processeur, faites attention aux constructeurs de sous-classes Application et aux méthodes com.example.customApplication.onCreate() de votre application.

Si l'outil montre que l'exécution de ces méthodes prend beaucoup de temps, vous devez examiner les tâches à effectuer de plus près.

Traçage intégré

Utilisez le traçage intégré pour identifier les causes probables de ces problèmes, notamment :

  • La fonction onCreate() initiale de votre application
  • Tout objet Singleton global qu'elle initialise
  • Toute opération d'E/S sur le disque, désérialisation ou boucle serrée qui peut se produire durant le goulot d'étranglement

Les solutions au problème

Il existe de nombreux goulots d'étranglement potentiels, mais voici deux problèmes courants et leurs solutions :

  • Plus la hiérarchie des vues est grande, plus l'application met du temps à la gonfler. Pour résoudre ce problème, procédez comme suit :
    • Aplatissez la hiérarchie des vues en réduisant les mises en page redondantes ou imbriquées.
    • Ne gonflez pas les parties de l'interface utilisateur qui n'ont pas besoin d'être visibles lors du lancement. Utilisez plutôt un objet ViewStub comme espace réservé pour les sous-hiérarchies, que l'application pourra gonfler à un moment plus approprié.
  • Initialiser l'ensemble de vos ressources sur le thread principal peut également ralentir le démarrage. Pour résoudre ce problème :
    • Déplacez l'initialisation de toutes les ressources afin que l'application puisse l'effectuer de manière différée sur un thread différent.
    • Autorisez l'application à charger et afficher vos vues, puis mettez à jour les propriétés visuelles qui dépendent de bitmaps et d'autres ressources.

Écrans de démarrage personnalisés

Le démarrage peut prendre plus de temps si vous avez déjà utilisé l'une des méthodes suivantes pour implémenter un écran de démarrage personnalisé sous Android 11 (niveau d'API 30) ou version antérieure :

  • Utiliser l'attribut de thème windowDisablePreview pour désactiver l'écran vierge initial affiché par le système lors du lancement.
  • Utiliser une Activity dédiée.

À partir d'Android 12, la migration vers l'API SplashScreen est requise. Cette API accélère le démarrage et vous permet d'ajuster votre écran de démarrage comme suit :

De plus, avec la bibliothèque de compatibilité, vous pouvez rétroporter l'API SplashScreen, ce qui permet d'activer la rétrocompatibilité et de créer une apparence cohérente pour les écrans de démarrage, quelle que soit la version d'Android.

Pour en savoir plus, consultez le guide de migration des écrans de démarrage.