Analyse et optimisation du démarrage de l'appli

L'utilisateur se fait une première impression de votre appli à son démarrage. L'appli doit donc se charger rapidement et afficher les informations dont l'utilisateur a besoin pour l'utiliser. Si votre appli prend trop de temps à démarrer et que l'utilisateur s'impatiente, il risque de la quitter.

Nous vous recommandons d'utiliser la bibliothèque Macrobenchmark pour mesurer le démarrage. La bibliothèque fournit un aperçu et des traces système détaillées pour voir exactement ce qui se passe au démarrage.

Les traces système fournissent des informations utiles sur ce qu'il se passe sur votre appareil. Elles vous permettent de comprendre ce que fait votre appli au démarrage et d'identifier les aspects qui peuvent être optimisés.

Pour analyser le démarrage de votre appli, procédez comme suit :

Étapes à suivre pour analyser et optimiser le démarrage

Au démarrage, les applis doivent souvent charger des ressources spécifiques qui sont essentielles pour les utilisateurs finaux. Les ressources non essentielles peuvent se charger une fois le démarrage terminé.

Pour faire des compromis en termes de performances, tenez compte des points suivants :

  • Utilisez la bibliothèque Macrobenchmark pour mesurer le temps nécessaire à chaque opération et identifier les blocs dont l'exécution prend beaucoup de temps.

  • Vérifiez que l'opération qui nécessite de nombreuses ressources est essentielle au démarrage de l'appli. Si l'opération peut attendre que le rendu de l'appli soit complètement terminé, cela peut contribuer à réduire les contraintes liées aux ressources lors du démarrage.

  • Assurez-vous que cette opération doit s'exécuter au démarrage de l'appli. Souvent, des opérations inutiles peuvent être appelées à partir d'un ancien code ou de bibliothèques tierces.

  • Si possible, déplacez les opérations de longue durée en arrière-plan. Les processus en arrière-plan peuvent toujours affecter l'utilisation du processeur au démarrage.

Après avoir examiné attentivement l'opération, vous pouvez trouver l'équilibre à atteindre entre le temps de chargement et la nécessité de l'inclure au démarrage de l'appli. N'oubliez pas de tenir compte des risques de régression ou de modification destructive lorsque vous modifiez le workflow de votre appli.

Effectuez de nouvelles optimisations et mesures jusqu'à ce que le temps de démarrage de votre appli vous convienne. Pour en savoir plus, consultez Utiliser les métriques pour détecter et diagnostiquer les problèmes.

Mesurer et analyser le temps consacré aux opérations importantes

Une fois que vous disposez d'une trace de démarrage d'appli complète, examinez-la et mesurez le temps nécessaire pour effectuer des opérations importantes comme bindApplication ou activityStart. Nous vous recommandons d'analyser ces traces à l'aide de Perfetto ou du Profileur Android Studio.

Examinez le temps total consacré au démarrage de l'appli pour identifier les opérations qui :

  • Occupent de longues périodes et peuvent être optimisées. Chaque millième de seconde compte quand il est question de performances. Par exemple, intéressez-vous aux temps de rendu Choreographer, aux temps de gonflement de la mise en page, aux temps de chargement de la bibliothèque, aux transactions Binder ou encore aux temps de chargement des ressources. Pour commencer, examinez toutes les opérations qui prennent plus de 20 ms.
  • Bloquent le thread principal. Pour en savoir plus, consultez Parcourir un rapport Systrace.
  • Ne doivent pas nécessairement être exécutées au démarrage.
  • Peuvent attendre que le premier frame soit dessiné.

Examinez chacune de ces traces pour identifier les écarts de performances.

Identifier les opérations coûteuses sur le thread principal

Il est recommandé de ne pas laisser les opérations coûteuses telles que les E/S de fichiers et l'accès réseau au thread principal. Cela est tout aussi important au démarrage de l'application, car des opérations coûteuses sur le thread principal peuvent rendre l'application non réactive et retarder d'autres opérations critiques. StrictMode.ThreadPolicy permet d'identifier les cas d'opérations coûteuses qui se produisent sur le thread principal. Il est recommandé d'activer StrictMode sur les versions de débogage pour identifier les problèmes le plus tôt possible, comme illustré dans l'exemple suivant :

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ...
        if(BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                    new StrictMode.ThreadPolicy.Builder()
                            .detectAll()
                            .penaltyDeath()
                            .build()
            );
        }
        ...
    }
}

L'utilisation de StrictMode.ThreadPolicy active la règle de thread sur toutes les versions de débogage et fait planter l'application chaque fois que des cas de non-respect de cette règle sont détectés. Il est donc difficile de passer à côté de ces cas de non-respect.

TTID et TTFD

Pour connaître le temps nécessaire à l'appli pour produire son premier frame, mesurez le temps d'affichage initial (TTID). Toutefois, cette valeur ne reflète pas nécessairement combien de temps l'utilisateur doit patienter pour pouvoir commencer à interagir avec votre appli. La métrique Délai d'affichage total (TTFD) est plus utile pour mesurer et optimiser les chemins de code nécessaires afin de fournir une appli entièrement fonctionnelle.

Pour connaître les stratégies de reporting une fois que l'interface utilisateur de l'appli est entièrement dessinée, consultez Améliorer la précision du temps de démarrage.

Optimisez à la fois le TTID et le TTFD, car ils sont importants à différents égards. Un TTID court permet à l'utilisateur de voir que l'appli se lance. Il doit être court pour que l'utilisateur puisse commencer à interagir rapidement avec l'appli.

Analyser l'état général des threads

Sélectionnez le temps de démarrage de l'appli et examinez les tranches de thread en général. Le thread principal doit être responsif en permanence.

Des outils tels que le Profileur Android Studio et Perfetto fournissent un aperçu détaillé du thread principal et du temps passé à chaque étape. Pour en savoir plus sur la visualisation des traces de Perfetto, consultez la documentation sur l'interface utilisateur de Perfetto.

Identifier les principales parties de l'état de veille du thread principal

S'il existe de nombreuses périodes de mise en veille, il est probable que le thread principal de votre appli attende que le travail soit terminé. Si vous disposez d'une appli multithread, identifiez le thread attendu par votre thread principal et pensez à optimiser ces opérations. Vous pouvez également vous assurer qu'il n'y a pas de conflit de verrouillage inutile qui entraîne des retards dans votre chemin critique.

Réduire le blocage du thread principal et la veille ininterrompue

Recherchez toutes les instances du thread principal qui passent à un état bloqué. Dans Perfetto et le Profileur Studio, cela est signalé par un indicateur orange sur la chronologie d'état du thread. Identifiez les opérations, déterminez si elles sont attendues ou si elles peuvent être évitées, et optimisez-les si nécessaire.

La veille interrompue liée aux E/S peut constituer une excellente piste d'amélioration. D'autres processus qui effectuent des E/S, même s'il s'agit d'applications non liées, peuvent entrer en conflit avec les E/S de l'application principale.

Améliorer le temps de démarrage

Une fois que vous avez identifié une piste d'optimisation, examinez les solutions possibles pour réduire les temps de démarrage :

  • Chargez le contenu de manière différée et asynchrone pour accélérer le TTID.
  • Réduisez les fonctions d'appel qui effectuent des appels de liaison. S'ils sont inévitables, assurez-vous d'optimiser ces appels en mettant en cache les valeurs au lieu de répéter les appels ou de déplacer les tâches non bloquantes vers des threads en arrière-plan.
  • Pour que votre appli semble démarrer plus rapidement, vous pouvez présenter à l'utilisateur un élément qui nécessite un rendu minimal en attendant que le chargement du reste de l'écran soit terminé.
  • Créez et ajoutez un profil de démarrage à votre appli.
  • Utilisez la bibliothèque de démarrage d'appli Jetpack pour simplifier l'initialisation des composants au démarrage de l'appli.

Analyser les performances de l'UI

Le démarrage de l'appli comprend un écran de démarrage et le temps de chargement de votre page d'accueil. Pour optimiser le démarrage de l'appli, inspectez les traces pour comprendre le temps nécessaire au rendu de l'UI.

Limiter les tâches d'initialisation

Le chargement de certains frames peut être plus long que d'autres. Ces opérations sont considérées comme coûteuses pour l'appli.

Pour optimiser l'initialisation, procédez comme suit :

  • Choisissez en priorité les passes de mise en page lentes pour les améliorer.
  • Examinez chaque avertissement de Perfetto et alerte de Systrace en ajoutant des événements de trace personnalisés afin de réduire les retards et les rendus coûteux.

Mesurer les données de frame

Il existe plusieurs façons de mesurer les données de frame. Les cinq principales méthodes de collecte sont les suivantes :

  • Collecte locale à l'aide de dumpsys gfxinfo : tous les frames observés dans les données de dumpsys ne sont pas responsables de la lenteur d'affichage de votre appli ni n'ont nécessairement d'incidence sur les utilisateurs finaux. Cependant, il s'agit d'une bonne mesure à examiner à différents cycles de publication afin de comprendre la tendance générale en termes de performances. Pour savoir comment utiliser gfxinfo et framestats afin d'intégrer les mesures des performances de l'UI à vos pratiques de test, consultez Principes fondamentaux des tests des applications Android.
  • Collecte des champs à l'aide de JankStats : collectez les délais d'affichage des frames à partir de parties spécifiques de votre appli avec la bibliothèque JankStats, puis enregistrez et analysez les données.
  • Tests réalisés avec Macrobenchmark (Perfetto en arrière-plan)
  • Perfetto FrameTimeline: Sur Android 12 (niveau d'API 31), vous pouvez collecter des métriques de chronologie de frames à partir d'une trace Perfetto à l'origine de la perte de frames. Cela peut constituer la première étape pour déterminer pourquoi une perte de frames est survenue.
  • Profileur Android Studio pour la détection des à-coups

Vérifier le temps de chargement de l'activité principale

L'activité principale de votre appli peut contenir une grande quantité d'informations qui sont chargées à partir de plusieurs sources. Vérifiez la mise en page Activity de la page d'accueil et examinez spécifiquement la méthode Choreographer.onDraw de cette activité.

  • Utilisez reportFullyDrawn pour signaler au système que votre appli est maintenant complètement affichée à des fins d'optimisation.
  • Mesurez l'activité et les lancements d'appli à l'aide de StartupTimingMetric avec la bibliothèque Macrobenchmark.
  • Observez les pertes de frames.
  • Identifiez les mises en page dont le rendu ou la mesure prend beaucoup de temps.
  • Identifiez les éléments dont le chargement prend beaucoup de temps.
  • Identifiez les mises en page inutiles qui sont gonflées au démarrage.

Voici quelques solutions possibles pour optimiser le temps de chargement de l'activité principale :

  • Simplifiez au maximum votre mise en page initiale. Pour en savoir plus, consultez Optimiser les hiérarchies de mise en page.
  • Ajoutez des points de trace personnalisés pour fournir plus d'informations sur les pertes de frames et les mises en page complexes.
  • Réduisez le nombre et la taille des ressources bitmap chargées au démarrage.
  • Utilisez ViewStub lorsque les mises en page ne sont pas immédiatement définies sur VISIBLE. Un ViewStub est une vue invisible de taille nulle qui peut être utilisée pour gonfler, de manière différée, des ressources de mise en page lors de l'exécution. Pour en savoir plus, consultez ViewStub.

    Si vous utilisez Jetpack Compose, vous pouvez obtenir un comportement semblable à ViewStub en utilisant l'état pour différer le chargement de certains composants :

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    Chargez les composants dans le bloc conditionnel en modifiant shouldLoad :

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Cela déclenche une recomposition qui inclut le code dans le bloc conditionnel du premier extrait.