Vue d’ensemble de l’analyse des applications

App Analysis est un outil qui fournit aux développeurs une notification des problèmes de performances. App Analysis exécute votre code d’application sur un ensemble de recommandations en matière de performances et de bonnes pratiques.

L'analyse des applications identifie les problèmes à partir d'un ensemble de règles prédéfinies de problèmes de performances courants rencontrés par les applications. Le cas échéant, l’analyse des applications pointe vers l’outil de chronologie de Visual Studio, les informations sources et la documentation pour vous donner les moyens d’examiner.

Les règles d’analyse des applications font référence à une recommandation ou à une bonne pratique sur laquelle votre application est vérifiée.

Taille d’image décodée supérieure à la taille de rendu

Les images sont capturées à des résolutions très élevées, ce qui peut entraîner l’utilisation d’applications à l’aide d’un processeur supplémentaire lors du décodage des données d’image et de la mémoire après son chargement à partir du disque. Mais il n’y a aucun sens de décodage et d’enregistrement d’une image haute résolution en mémoire uniquement pour l’afficher plus petite que sa taille native. Au lieu de cela, créez une version de l’image à la taille exacte qu’elle sera dessinée à l’écran à l’aide des propriétés DecodePixelWidth et DecodePixelHeight.

Répercussion

L’affichage d’images à leurs tailles non natives peut avoir un impact négatif sur le temps processeur (en raison du décodage à la taille et au temps de téléchargement appropriés) et de la mémoire.

Causes et solutions

L’image n’est pas chargée de façon asynchrone

L’application utilise SetSource() au lieu de SetSourceAsync(). Vous devez toujours éviter d’utiliser SetSource et utiliser à la place SetSourceAsync lors de la définition d’un flux pour décoder les images de manière asynchrone.

L’image est appelée lorsque ImageSource n’est pas dans l’arborescence dynamique

BitmapImage est connecté à l’arborescence XAML dynamique après avoir défini le contenu avec SetSourceAsync ou UriSource. Vous devez toujours attacher une BitmapImage à l’arborescence dynamique avant de définir la source. Chaque fois qu’un élément d’image ou un pinceau est spécifié dans le balisage, ce sera automatiquement le cas. Vous trouverez ci-dessous des exemples.

exemples d’arborescences dynamiques

Exemple 1 (bon) : URI (Uniform Resource Identifier) spécifié dans le balisage.

<Image x:Name="myImage" UriSource="Assets/cool-image.png"/>

Exemple de balisage 2 : URI spécifié dans le code-behind.

<Image x:Name="myImage"/>

Exemple 2 code-behind (bon) : connexion de l'objet BitmapImage à l'arbre avant de définir son UriSource.

var bitmapImage = new BitmapImage();
myImage.Source = bitmapImage;
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);

Exemple 2 code-behind (incorrect) : définition de l’UriSource de BitmapImage avant de la connecter à l’arborescence.

var bitmapImage = new BitmapImage();
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);
myImage.Source = bitmapImage;

Le pinceau image n’est pas rectangulaire

Lorsqu'une image est utilisée pour un pinceau non rectangulaire, elle emprunte un chemin de rastérisation logiciel qui ne redimensionne pas les images. En outre, il doit stocker une copie de l’image dans la mémoire logicielle et matérielle. Par exemple, si une image est utilisée comme pinceau pour un ellipse, l’image complète potentiellement volumineuse est stockée deux fois en interne. Lorsque vous utilisez un pinceau non rectangulaire, votre application doit préredimensionner ses images à peu près à la taille à laquelle elles seront rendues.

Vous pouvez également définir une taille de décodage explicite pour créer une version de l’image à la taille exacte qu’elle sera dessinée à l’écran à l’aide des propriétés DecodePixelWidth et DecodePixelHeight.

<Image>
    <Image.Source>
    <BitmapImage UriSource="ms-appx:///Assets/highresCar.jpg" 
                 DecodePixelWidth="300" DecodePixelHeight="200"/>
    </Image.Source>
</Image>

Les unités de DecodePixelWidth et DecodePixelHeight sont par défaut des pixels physiques. La propriété DecodePixelType peut être utilisée pour modifier ce comportement : définir DecodePixelType sur logique entraîne la taille de décodage à automatiquement prendre en compte le paramètre d’échelle actuel du système, comme pour d'autres contenus XAML. Il serait donc généralement approprié de définir DecodePixelType sur Logique si, par exemple, vous souhaitez que DecodePixelWidth et DecodePixelHeight correspondent aux propriétés Hauteur et Largeur du contrôle Image où l'image sera affichée. Avec le comportement par défaut de l’utilisation de pixels physiques, vous devez tenir compte du facteur d’échelle actuel du système vous-même, et vous devez écouter les notifications de modification d’échelle au cas où l’utilisateur modifie ses préférences d’affichage.

Dans certains cas où une taille de décodage appropriée ne peut pas être déterminée à l'avance, vous devez vous en remettre au décodage automatique de la bonne taille par XAML, qui s'efforcera au mieux de décoder l'image à la taille appropriée si une largeur/hauteur de pixel de décodage explicite n’est pas spécifiée.

Vous devez définir une taille de décodage explicite si vous connaissez la taille du contenu de l’image à l’avance. Vous devez également définir conjointement DecodePixelType sur logique si la taille de décodage fournie est relative à d’autres tailles d’éléments XAML. Par exemple, si vous définissez explicitement la taille de contenu avec Image.Width et Image.Height, vous pouvez définir DecodePixelType sur DecodePixelType.Logical pour utiliser les mêmes dimensions de pixel logique qu’un contrôle Image, puis utiliser explicitement BitmapImage.DecodePixelWidth et/ou BitmapImage.DecodePixelHeight pour contrôler la taille de l’image pour réaliser des économies de mémoire potentiellement importantes.

Notez que Image.Stretch doit être pris en compte lors de la détermination de la taille du contenu décodé.

Les images utilisées à l’intérieur de BitmapIcons reviennent au décodage en taille naturelle

Définissez une taille de décodage explicite pour créer une version de l’image à la taille exacte qu’elle sera dessinée à l’écran à l’aide des propriétés DecodePixelWidth et DecodePixelHeight.

Les images qui apparaissent très grandes à l’écran retournent au décodage à leur taille naturelle.

Les images qui apparaissent extrêmement grandes à l'écran reviennent à leur taille naturelle après décodage. Définissez une taille de décodage explicite pour créer une version de l’image à la taille exacte qu’elle sera dessinée à l’écran à l’aide des propriétés DecodePixelWidth et DecodePixelHeight.

Image masquée

L'image est masquée en réglant l'opacité à 0 ou la visibilité à "Collapsed" sur l'élément d'image hôte, le pinceau ou tout élément parent. Les images non visibles à l'écran en raison d'un recadrage ou d'une transparence peuvent être redécodées à leur taille naturelle.

L'image utilise une propriété NineGrid

Lorsqu’une image est utilisée pour une NineGrid, l’image utilise un chemin de rastérisation logicielle, qui ne met pas à l’échelle les images du tout. En outre, il doit stocker une copie de l’image dans la mémoire logicielle et matérielle. Lorsque vous utilisez NineGrid, votre application doit adapter ses images à peu près à la taille à laquelle elles seront rendues.

Les images qui utilisent la propriété NineGrid reviennent au décodage en taille naturelle. Envisagez d’ajouter l’effet ninegrid à l’image d’origine.

DecodePixelWidth ou DecodePixelHeight sont définis sur une taille supérieure à la taille à laquelle l'image s'affiche à l'écran.

Si DecodePixelWidth/Height sont explicitement définis plus grand que l’image affichée à l’écran, l’application utilise inutilement une mémoire supplémentaire, jusqu’à 4 octets par pixel, ce qui devient rapidement coûteux pour les grandes images. L’image sera également mise à l’échelle à l’aide d’une mise à l’échelle bilinéaire, ce qui pourrait la rendre floue pour les facteurs de grande échelle.

L’image est décodée dans le cadre de la production d’une image glisser-déposer

Définissez une taille de décodage explicite pour créer une version de l’image à la taille exacte qu’elle sera dessinée à l’écran à l’aide des propriétés DecodePixelWidth et DecodePixelHeight.

Éléments réduits au moment du chargement

Un modèle courant dans les applications consiste à masquer les éléments de l’interface utilisateur initialement et à les afficher ultérieurement. Dans la plupart des cas, ces éléments doivent être différés à l’aide de x :Load ou x :DeferLoadStrategy pour éviter de payer le coût de création de l’élément au moment du chargement.

Cela inclut les cas où un convertisseur booléen à visibilité est utilisé pour masquer les éléments jusqu’à une date ultérieure.

Répercussion

Les éléments repliés sont chargés en même temps que d’autres éléments et contribuent à une augmentation du temps de chargement.

La cause

Cette règle a été déclenchée, car un élément a été replié au moment du chargement. La réduction d’un élément ou la définition de son opacité sur 0 n’empêche pas la création de l’élément. Cette règle peut être due à une application qui utilise un convertisseur de booléen en visibilité qui par défaut est sur false.

Solution

En utilisant l'attribut x:Load ou x:DeferLoadStrategy, vous pouvez retarder le chargement d’un élément d’interface utilisateur et le charger au besoin. Il s’agit d’un bon moyen de retarder le traitement de l’interface utilisateur qui n’est pas visible dans la première image. Vous pouvez choisir de charger l’élément si nécessaire, ou dans le cadre d’un ensemble de logiques retardées. Pour déclencher le chargement, appelez findName sur l’élément que vous souhaitez charger. x :Load étend les fonctionnalités de x :DeferLoadStrategy permettant aux éléments d’être déchargés et pour que l’état de chargement soit contrôlé via x :Bind.

Dans certains cas, l’utilisation de findName pour afficher une partie de l’interface utilisateur peut ne pas être la réponse. Cela est vrai si vous prévoyez de réaliser un élément significatif de l’interface utilisateur sur le clic d’un bouton avec une latence très faible. Dans ce cas, vous pourriez vouloir accepter une latence d’interface utilisateur plus rapide au prix d'une mémoire supplémentaire. Si c’est le cas, vous devez utiliser x:DeferLoadStrategy et définir la propriété Visibility sur Collapsed pour l’élément que vous souhaitez réaliser. Une fois la page chargée et que le thread d’interface utilisateur est gratuit, vous pouvez appeler findName si nécessaire pour charger les éléments. Les éléments ne seront pas visibles par l’utilisateur tant que vous n’avez pas défini la visibilité de l’élément sur Visible.

ListView n’est pas virtualisé

La virtualisation de l’interface utilisateur est l’amélioration la plus importante que vous pouvez apporter pour améliorer les performances de collecte. Cela signifie que les éléments d’interface utilisateur représentant les éléments sont créés à la demande. Pour un contrôle d’éléments lié à une collection de 1 000 éléments, il s’agirait d’un gaspillage de ressources pour créer l’interface utilisateur pour tous les éléments en même temps, car ils ne peuvent pas tous être affichés en même temps. ListView et GridView (et d’autres contrôles dérivés d’ItemsControl standard) effectuent la virtualisation de l’interface utilisateur pour vous. Lorsque les éléments sont proches d’être affichés (à quelques pages), l’infrastructure génère l’interface utilisateur des éléments et les met en cache. Lorsqu’il est peu probable que les éléments soient affichés à nouveau, le cadre libère la mémoire.

La virtualisation de l’interface utilisateur n’est qu’un des facteurs clés pour améliorer les performances de collecte. La réduction de la complexité des éléments de collecte et de la virtualisation des données est deux autres aspects importants pour améliorer les performances de collecte. Pour plus d’informations sur l’amélioration des performances de collecte dans ListViews et GridViews, consultez les articles sur optimisation de l’interface utilisateur ListView et GridView et la virtualisation des données ListView et Gridview.

Répercussion

Un ItemsControl non virtualisé augmente le temps de chargement et l’utilisation des ressources en chargeant plus de ses éléments enfants que nécessaire.

La cause

Le concept d’une fenêtre d’affichage est essentiel à la virtualisation de l’interface utilisateur, car l’infrastructure doit créer les éléments susceptibles d’être affichés. En général, la fenêtre d’affichage d’un ItemsControl est l’étendue du contrôle logique. Par exemple, la fenêtre d’affichage d’un ListView est la largeur et la hauteur de l’élément ListView. Certains panneaux, comme ScrollViewer et une grille avec des lignes ou colonnes à taille automatique, offrent aux éléments enfants un espace illimité. Lorsqu’un ItemsControl virtualisé est placé dans un panneau comme celui-ci, il faut suffisamment de place pour afficher tous ses éléments, ce qui élimine la virtualisation.

Solution

Restaurez la virtualisation en définissant une largeur et une hauteur sur ItemsControl que vous utilisez.

Thread d’interface utilisateur bloqué ou inactif pendant le chargement

Le blocage du thread d'interface utilisateur fait référence aux appels synchrones à des fonctions effectuées hors du thread qui bloquent le thread d'interface utilisateur.

Pour obtenir la liste complète des meilleures pratiques pour améliorer les performances de démarrage de votre application, consultez Meilleures pratiques pour les performances de démarrage de votre application et Maintenir le thread d’interface utilisateur réactif.

Répercussion

Un thread d’interface utilisateur bloqué ou inactif pendant le temps de chargement empêche la disposition et d’autres opérations d’interface utilisateur, ce qui augmente le temps de démarrage.

La cause

Le code de plateforme pour l’interface utilisateur et le code de votre application pour l’interface utilisateur sont tous exécutés sur le même thread d’interface utilisateur. Une seule instruction peut s’exécuter sur ce thread à la fois. Par conséquent, si le code de votre application prend trop de temps pour traiter un événement, l’infrastructure ne peut pas exécuter la disposition ou déclencher de nouveaux événements représentant l’interaction utilisateur. La réactivité de votre application est liée à la disponibilité du thread d’interface utilisateur pour traiter le travail.

Solution

Votre application peut être interactive même s’il existe des parties de l’application qui ne sont pas entièrement fonctionnelles. Par exemple, si votre application affiche des données qui prennent un certain temps à récupérer, vous pouvez faire en sorte que ce code s’exécute indépendamment du code de démarrage de l’application en récupérant les données de manière asynchrone. Lorsque les données sont disponibles, renseignez l’interface utilisateur de l’application avec les données. Pour aider à maintenir la réactivité de votre application, la plateforme fournit des versions asynchrones de plusieurs de ses API. Une API asynchrone garantit que votre thread d’exécution actif ne bloque jamais pendant une durée importante. Lorsque vous appelez une API à partir du thread d’interface utilisateur, utilisez la version asynchrone si elle est disponible.

{Binding} est utilisé au lieu de {x :Bind}

Cette règle est déclenchée lorsque votre application utilise une instruction {Binding}. Pour améliorer les performances des applications, les applications doivent envisager d’utiliser {x :Bind}.

Répercussion

{Binding} s’exécute en plus de temps et plus de mémoire que {x :Bind}.

La cause

L’application utilise {Binding} au lieu de {x :Bind}. {Binding} apporte avec elle un ensemble de travail non trivial et une surcharge processeur. La création d’un {Binding} provoque une série d’allocations et la mise à jour d’une cible de liaison peut entraîner la réflexion et la boxe.

Solution

Utilisez l’extension de balisage {x:Bind}, qui compile les liaisons lors de la génération. Les liaisons {x :Bind} (souvent appelées liaisons compilées) présentent de bonnes performances, fournissent une validation au moment de la compilation de vos expressions de liaison et prennent en charge le débogage en vous permettant de définir des points d’arrêt dans les fichiers de code générés en tant que classe partielle pour votre page.

Notez que x:Bind n'est pas toujours adapté, par exemple dans les scénarios à liaison tardive. Pour obtenir la liste complète des cas non couverts par {x :Bind}, consultez la documentation {x :Bind}.

x :Name est utilisé au lieu de x :Key

ResourceDictionaries est généralement utilisé pour stocker vos ressources à un niveau quelque peu global, c’est-à-dire les ressources que votre application souhaite référencer à plusieurs emplacements ; par exemple, les styles, les pinceaux, les modèles, et ainsi de suite. En général, nous avons optimisé ResourceDictionaries pour ne pas instancier les ressources, sauf si elles sont demandées. Mais il y a peu d’endroits où vous devez être un peu prudent.

Répercussion

Toute ressource avec x :Name est instanciée dès la création de ResourceDictionary. Cela se produit parce que x :Name indique à la plateforme que votre application a besoin d’un accès de champ à cette ressource, de sorte que la plateforme doit créer quelque chose pour créer une référence à.

La cause

Votre application définit x :Name sur une ressource.

Solution

Utilisez x :Key au lieu de x :Name lorsque vous ne référencez pas les ressources à partir du code-behind.

Le contrôle Collections utilise un panneau non virtualisant.

Si vous fournissez un modèle de panneau d’éléments personnalisés (voir ItemsPanel), veillez à utiliser un panneau de virtualisation tel que ItemsWrapGrid ou ItemsStackPanel. Si vous utilisez VariableSizedWrapGrid, WrapGrid ou StackPanel, vous n’obtiendrez pas de virtualisation. En outre, les événements ListView suivants sont déclenchés uniquement lors de l’utilisation d’un ItemsWrapGrid ou d’un ItemsStackPanel : ChoosingGroupHeaderContainer, ChoosingItemContainer et ContainerContentChanging.

La virtualisation de l’interface utilisateur est l’amélioration la plus importante que vous pouvez apporter pour améliorer les performances de collecte. Cela signifie que les éléments d’interface utilisateur représentant les éléments sont créés à la demande. Pour un contrôle d’éléments lié à une collection de 1 000 éléments, il s’agirait d’un gaspillage de ressources pour créer l’interface utilisateur pour tous les éléments en même temps, car ils ne peuvent pas tous être affichés en même temps. ListView et GridView (et d’autres contrôles dérivés d’ItemsControl standard) effectuent la virtualisation de l’interface utilisateur pour vous. Lorsque les éléments sont proches d’être affichés (à quelques pages), l’infrastructure génère l’interface utilisateur des éléments et les met en cache. Lorsqu’il est peu probable que les éléments soient affichés à nouveau, le cadre libère la mémoire.

La virtualisation de l’interface utilisateur n’est qu’un des facteurs clés pour améliorer les performances de collecte. La réduction de la complexité des éléments de collecte et de la virtualisation des données est deux autres aspects importants pour améliorer les performances de collecte. Pour plus d’informations sur l’amélioration des performances de collecte dans ListViews et GridViews, consultez les articles sur optimisation de l’interface utilisateur ListView et GridView et la virtualisation des données ListView et Gridview.

Répercussion

Un ItemsControl non virtualisé augmente le temps de chargement et l’utilisation des ressources en chargeant plus de ses éléments enfants que nécessaire.

La cause

Vous utilisez un panneau qui ne prend pas en charge la virtualisation.

Solution

Utilisez un panneau de virtualisation tel que ItemsWrapGrid ou ItemsStackPanel.

Accessibilité : éléments UIA sans nom

En XAML, vous pouvez fournir un nom en définissant AutomationProperties.Name. De nombreux homologues d’automatisation fournissent un nom par défaut à UIA si AutomationProperties.Name n’est pas défini.

Répercussion

Si un utilisateur atteint un élément sans nom, il n’a souvent aucun moyen de savoir à quoi l’élément est lié.

La cause

Le nom UIA de l’élément est null ou vide. Cette règle vérifie ce que l’UIA voit, et non la valeur de la AutomationProperties.Name.

Solution

Définissez la propriété AutomationProperties.Name dans le code XAML du contrôle sur une chaîne localisée appropriée.

Parfois, la bonne solution d'application n'est pas de fournir un nom, c'est de supprimer l'élément UIA de tout sauf les arbres bruts. Vous pouvez le faire en XAML en définissant AutomationProperties.AccessibilityView = "Raw".

Accessibilité : les éléments UIA avec le même Controltype ne doivent pas avoir le même nom

Deux éléments UIA avec le même parent UIA ne doivent pas avoir le même Nom et ControlType. Il est possible d’avoir deux contrôles portant le même nom s’ils ont des ControlTypes différents.

Cette règle ne vérifie pas les noms en double avec différents parents. Toutefois, dans la plupart des cas, vous ne devez pas dupliquer les noms et les types de contrôle dans l'ensemble d'une fenêtre, même avec des parents différents. Les cas où les noms en double dans une fenêtre sont acceptables sont deux listes avec des éléments identiques. Dans ce cas, les éléments de liste doivent avoir des Noms et des types de contrôle identiques.

Répercussion

Si un utilisateur atteint un élément portant le même Nom et ControlType qu’un autre élément avec le même parent UIA, il se peut que l’utilisateur ne puisse pas distinguer la différence entre les éléments.

La cause

Les éléments UIA avec le même parent UIA ont le même Nom et ControlType.

Solution

Définissez un nom en XAML à l’aide de AutomationProperties.Name. Dans les listes où cela se produit généralement, utilisez la liaison pour lier la valeur du AutomationProperties.Name à une source de données.