Accélération matérielle

À partir d'Android 3.0 (niveau d'API 11), le pipeline de rendu Android 2D prend en charge l'accélération matérielle, ce qui signifie que toutes les opérations de dessin effectuées sur le canevas d'une View utilisent le GPU. En raison de l'augmentation des ressources requises pour activer l'accélération matérielle, votre application utilisera plus de RAM.

L'accélération matérielle est activée par défaut si votre niveau d'API cible est supérieur ou égal à 14, mais vous pouvez aussi l'activer explicitement. Si votre application n'utilise que des vues standards et des Drawable, son activation globale ne devrait entraîner aucun effet négatif sur le dessin. Toutefois, comme l'accélération matérielle n'est pas compatible avec toutes les opérations de dessin en 2D, son activation peut affecter certains de vos appels de dessins ou vues personnalisés. Les problèmes se manifestent généralement par des éléments invisibles, des exceptions ou des pixels mal affichés. Pour remédier à ce problème, Android vous permet d'activer ou de désactiver l'accélération matérielle à plusieurs niveaux. Consultez Contrôler l'accélération matérielle.

Si votre application effectue un dessin personnalisé, testez-la sur des appareils matériels réels en activant l'accélération matérielle pour détecter les éventuels problèmes. La section Compatibilité avec les opérations de dessin décrit les problèmes connus liés à l'accélération matérielle et comment les contourner.

Consultez également OpenGL avec les API Framework et Renderscript.

Contrôler l'accélération matérielle

Vous pouvez contrôler l'accélération matérielle aux niveaux suivants :

  • Application
  • Activité
  • Fenêtre
  • Afficher

Au niveau de l'application

Dans votre fichier manifeste Android, ajoutez l'attribut suivant à la balise <application> afin d'activer l'accélération matérielle sur l'ensemble de votre application :

<application android:hardwareAccelerated="true" ...>

Au niveau de l'activité

Si l'accélération matérielle est activée de façon globale et que votre application n'adopte pas le comportement attendu, vous pouvez également la contrôler pour chaque activité. Pour activer ou désactiver l'accélération matérielle au niveau de l'activité, vous pouvez utiliser l'attribut android:hardwareAccelerated de l'élément <activity>. L'exemple suivant active l'accélération matérielle sur l'ensemble de l'application, mais la désactive pour une activité :

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Au niveau de la fenêtre

Si vous avez besoin d'un contrôle encore plus précis, vous pouvez activer l'accélération matérielle pour une fenêtre donnée à l'aide du code suivant :

Kotlin

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Java

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Remarque : Actuellement, vous ne pouvez pas désactiver l'accélération matérielle au niveau de la fenêtre.

Au niveau des vues

Vous pouvez désactiver l'accélération matérielle d'une vue individuelle au moment de l'exécution à l'aide du code suivant :

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Remarque : Actuellement, vous ne pouvez pas activer l'accélération matérielle au niveau des vues. En plus de désactiver l'accélération matérielle, les calques ont d'autres fonctions. Pour en savoir plus sur leur utilisation, consultez Afficher les calques.

Déterminer si une vue est accélérée matériellement

Il est parfois utile qu'une application sache si le processus d'accélération matérielle est en cours, en particulier pour les vues personnalisées, par exemple. Cela est particulièrement utile si votre application effectue beaucoup de dessins personnalisés et que toutes les opérations ne sont pas correctement prises en charge par le nouveau pipeline de rendu.

Il existe deux façons de vérifier si l'application est accélérée par le matériel :

Si vous devez effectuer cette vérification dans votre code de dessin, utilisez si possible Canvas.isHardwareAccelerated() au lieu de View.isHardwareAccelerated(). Lorsqu'une vue est associée à une fenêtre accélérée par le matériel, elle peut toujours être dessinée à l'aide d'un canevas qui n'est pas accéléré par le matériel. Cela se produit, par exemple, lors du dessin d'une vue dans un bitmap à des fins de mise en cache.

Modèles de dessin Android

Lorsque l'accélération matérielle est activée, le framework Android utilise un nouveau modèle de dessin qui s'affiche à l'écran à l'aide de listes d'affichage. Pour bien comprendre les listes d'affichage et comment elles peuvent affecter votre application, il est utile de comprendre comment Android génère des vues sans accélération matérielle. Les sections suivantes décrivent les modèles de dessin basés sur les logiciels et sur l'accélération matérielle.

Modèle de dessin basé sur logiciel

Dans le modèle de dessin logiciel, les vues sont dessinées en suivant les deux étapes suivantes :

  1. Invalider la hiérarchie
  2. Dessiner la hiérarchie

Chaque fois qu'une application doit mettre à jour une partie de son interface utilisateur, elle appelle invalidate() (ou l'une de ses variantes) sur toute vue dont le contenu a changé. Les messages d'invalidation sont propagés tout au long de la hiérarchie des vues pour calculer les zones de l'écran qui doivent être redessinées ("dirty region", ou "zone sale"). Le système Android dessine ensuite les vues de la hiérarchie qui présentent une intersection avec une zone sale. Malheureusement, ce modèle de dessin présente deux inconvénients :

  • Tout d'abord, il nécessite l'exécution d'une grande quantité de code à chaque passe de dessin. Par exemple, si votre application appelle invalidate() sur un bouton et qu'il se trouve au-dessus d'une autre vue, le système Android redessine la vue, même si elle n'a pas changé.
  • Le deuxième problème est que le modèle de dessin peut masquer les bugs de votre application. Étant donné que le système Android redessine les vues lorsqu'elles présentent une intersection avec une zone sale, une vue dont vous avez modifié le contenu peut être redessinée, même si invalidate() n'a pas été appelé. Lorsque cela se produit, vous vous appuyez sur une autre vue invalidée pour obtenir le comportement approprié. Ce comportement peut changer chaque fois que vous modifiez votre application. De ce fait, vous devez toujours appeler invalidate() sur vos vues personnalisées chaque fois que vous modifiez des données ou un état qui affectent le code de dessin de la vue.

Remarque : Les vues Android appellent automatiquement invalidate() lorsque leurs propriétés changent (couleur d'arrière-plan ou texte d'une TextView, par exemple).

Modèle de dessin accéléré par le matériel

Le système Android utilise toujours invalidate() et draw() pour demander des mises à jour d'écran et pour afficher des vues, mais gère le dessin différemment. Au lieu d'exécuter immédiatement les commandes de dessin, le système Android les enregistre dans des listes d'affichage, qui contiennent la sortie du code de dessin de la hiérarchie des vues. Autre optimisation : le système Android n'a besoin d'enregistrer et de mettre à jour que des listes d'affichage pour les vues marquées comme étant sales par un appel invalidate(). Les vues qui n'ont pas été invalidées peuvent être redessinées tout simplement en générant à nouveau la liste d'affichage précédemment enregistrée. Le nouveau modèle de dessin comporte trois étapes :

  1. Invalider la hiérarchie
  2. Enregistrer et mettre à jour des listes d'affichage
  3. Dessiner les listes d'affichage

Avec ce modèle, vous ne pouvez pas compter sur une vue qui présente une intersection avec une zone sale pour exécuter sa méthode draw(). Pour vous assurer que le système Android enregistre la liste d'affichage d'une vue, vous devez appeler invalidate(). Si vous oubliez de le faire, la vue reste la même, même après avoir été modifiée.

L'utilisation de listes d'affichage améliore également les performances des animations, car la définition de propriétés spécifiques, comme la version alpha ou la rotation, ne nécessite pas d'invalider la vue ciblée (cette opération s'effectue automatiquement). Cette optimisation s'applique également aux vues avec des listes d'affichage (toute vue lorsque l'application est accélérée par le matériel). Par exemple, supposons qu'une LinearLayout contient une ListView au-dessus d'un Button. La liste d'affichage de la LinearLayout se présente comme suit :

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

Supposons maintenant que vous souhaitiez modifier l'opacité de la ListView. Après avoir appelé setAlpha(0.5f) sur la ListView, la liste d'affichage contient désormais ce qui suit :

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

Le code de dessin complexe de la ListView n'a pas été exécuté. Au lieu de cela, le système n'a mis à jour que la liste d'affichage de la LinearLayout simplifiée. Dans une application pour laquelle l'accélération matérielle n'est pas activée, le code de dessin de la liste et de son parent est à nouveau exécuté.

Compatibilité avec les opérations de dessin

Lorsque le pipeline de rendu 2D est accéléré par le matériel, il accepte les opérations de dessin Canvas les plus courantes, ainsi que de nombreuses opérations moins utilisées. Toutes les opérations de dessin utilisées pour le rendu d'applications qui sont fournies avec Android, les mises en page et les widgets par défaut, ainsi que les effets visuels courants tels que les reflets et les textures en mosaïque, sont acceptés.

Le tableau suivant décrit le niveau de compatibilité des différentes opérations entre les niveaux d'API :

Premier niveau d'API compatible
Canevas
drawBitmapMesh() (éventail de couleurs) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18 ans
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() avec rotation/perspective 18 ans
Peindre
setAntiAlias() (pour du texte) 18 ans
setAntiAlias() (pour des lignes) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (pour des lignes) 28
setShadowLayer() (autre que du texte) 28
setStrokeCap() (pour des lignes) 18
setStrokeCap() (pour des points) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LIGHTEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
Nuanceur
ComposeShader dans ComposeShader 28
Nuanceurs du même type dans ComposeShader 28
Matrice locale sur ComposeShader 18

Mise à l'échelle du canevas

Le pipeline de rendu 2D accéléré par le matériel a été conçu en premier pour pouvoir dessiner sans mise à l'échelle. Certaines opérations de dessin dégradent considérablement la qualité lorsque la mise à l'échelle se fait à des valeurs plus importantes. Ces opérations sont implémentées en tant que textures dessinées à l'échelle 1.0, transformées par le GPU. À partir du niveau d'API 28, toutes les opérations de dessin peuvent être mises à l'échelle sans problème.

Le tableau suivant indique à quel moment l'implémentation a été modifiée pour gérer correctement les opérations à grande échelle :
Opération de dessin devant être mises à l'échelle Premier niveau d'API compatible
drawText() 18 ans
drawPosText() 28
drawTextOnPath() 28
Formes simples* 17
Formes complexes* 28
drawPath() 28
Couche d'ombre 28

Remarque : Les formes "simples" sont les commandes drawRect(), drawCircle(), drawOval(), drawRoundRect() et drawArc() (avec useCenter=false) envoyées avec une peinture qui n'a pas de PathEffect et qui ne contient pas de jointures autres que celles par défaut (via setStrokeJoin()/setStrokeMiter()). Les autres instances de ces commandes de dessin sont classées dans la catégorie "complexe", dans le graphique ci-dessus.

Si votre application est affectée par l'une de ces fonctionnalités ou limites, vous pouvez désactiver l'accélération matérielle pour la partie concernée de l'application en appelant setLayerType(View.LAYER_TYPE_SOFTWARE, null). Vous pouvez ainsi profiter de l'accélération matérielle partout ailleurs. Pour en savoir plus sur l'activation et la désactivation de l'accélération matérielle à différents niveaux de votre application, consultez Contrôler l'accélération matérielle.

Afficher les calques

Dans toutes les versions d'Android, les vues pouvaient s'afficher dans des tampons hors écran, soit à l'aide du cache de dessin d'une vue, soit via Canvas.saveLayer(). Les tampons ou les couches hors écran ont plusieurs utilisations. Vous pouvez les utiliser pour améliorer les performances lors de l'animation de vues complexes ou pour appliquer des effets de composition. Par exemple, vous pouvez mettre en œuvre des effets de fondu en utilisant Canvas.saveLayer() pour afficher temporairement une vue dans une couche, puis la recomposer à l'écran avec un facteur d'opacité.

À partir d'Android 3.0 (niveau d'API 11), vous pouvez mieux contrôler quand et comment utiliser les couches avec la méthode View.setLayerType(). Cette API utilise deux paramètres : le type de calque que vous souhaitez utiliser et un objet Paint facultatif qui décrit la façon dont la couche doit être composée. Vous pouvez utiliser le paramètre Paint pour appliquer des filtres de couleur, des modes de fusion spéciaux ou de l'opacité à un calque. Une vue peut utiliser l'un des trois types de calques suivants :

  • LAYER_TYPE_NONE : la vue s'affiche normalement et n'est pas protégée par un tampon hors écran. Il s'agit du comportement par défaut.
  • LAYER_TYPE_HARDWARE : la vue est affichée dans un appareil sous forme de texture matérielle si l'application est accélérée par le matériel. Si l'application n'est pas accélérée par le matériel, ce type de couche se comporte de la même manière que LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE : la vue est affichée dans un logiciel sous forme de bitmap.

Le type de calque que vous utilisez dépend de votre objectif :

  • Performances : utilisez une couche matérielle pour afficher une vue dans une texture matérielle. Une fois qu'une vue est affichée dans un calque, il n'est pas nécessaire d'exécuter son code de dessin tant qu'elle n'a pas appelé invalidate(). Certaines animations, telles que les animations alpha, peuvent ensuite être appliquées directement à la couche, ce qui est très efficace pour le GPU.
  • Effets visuels : utilisez un calque matériel ou logiciel, et une Paint pour appliquer des traitements visuels spéciaux à une vue. Par exemple, vous pouvez dessiner une vue en noir et blanc à l'aide d'un ColorMatrixColorFilter.
  • Compatibilité : utilisez une couche logicielle pour forcer l'affichage d'une vue dans un logiciel. Si une vue accélérée par le matériel (par exemple, si l'ensemble de votre application est accélérée par le matériel) rencontre des problèmes d'affichage, cette solution vous permet de contourner facilement les limites du pipeline de rendu matériel.

Afficher les calques et les animations

Les couches matérielles peuvent générer des animations plus rapides et plus fluides lorsque votre application est accélérée par le matériel. Il n'est pas toujours possible d'exécuter une animation à 60 images par seconde lors de l'animation de vues complexes générant de nombreuses opérations de dessin. Vous pouvez résoudre ce problème en utilisant des couches matérielles pour afficher la vue dans une texture matérielle. La texture matérielle peut ensuite être utilisée pour animer la vue, ce qui évite de la redessiner en permanence lorsqu'elle est animée. La vue n'est pas redessinée, sauf si vous modifiez ses propriétés, qui appelle invalidate(), ou si vous appelez invalidate() manuellement. Si vous exécutez une animation dans votre application et que vous n'obtenez pas les résultats escomptés, envisagez d'activer les couches matérielles sur vos vues animées.

Lorsqu'une vue repose sur une couche matérielle, certaines de ses propriétés sont gérées par la façon dont la couche est composée à l'écran. Définir ces propriétés est efficace, car elles n'exigent pas que la vue soit invalidée et redessinée. La liste de propriétés suivante affecte la façon dont la couche est composée. L'appel du setter pour l'une de ces propriétés se traduit par une invalidation optimale sans redessiner la vue ciblée :

  • alpha : modifie l'opacité du calque.
  • x, y, translationX, translationY : modifie la position du calque.
  • scaleX, scaleY : modifie la taille du calque.
  • rotation, rotationX, rotationY : modifie l'orientation du calque dans l'espace 3D.
  • pivotX, pivotY : modifie l'origine des transformations du calque.

Ces propriétés sont les noms utilisés lors de l'animation d'une vue avec un ObjectAnimator. Si vous souhaitez accéder à ces propriétés, appelez le setter ou le getter approprié. Par exemple, pour modifier la propriété alpha, appelez setAlpha(). L'extrait de code suivant montre la manière la plus efficace de faire pivoter une vue en 3D autour de l'axe Y :

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

Étant donné que les couches matérielles consomment de la mémoire vidéo, nous vous recommandons de les activer uniquement pendant la durée de l'animation, puis de les désactiver une fois l'animation terminée. Pour ce faire, utilisez les écouteurs d'animation :

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

Pour en savoir plus sur l'animation des propriétés, consultez Animation de propriétés.

Conseils et astuces

En passant à des graphismes 2D accélérés par le matériel, vous pouvez instantanément améliorer les performances. Toutefois, vous devez tout de même concevoir votre application de telle sorte qu'elle utilise efficacement le GPU en appliquant les recommandations suivantes :

Réduire le nombre de vues dans votre application
Plus le système doit dessiner de vues, plus il est lent. Cela s'applique également au pipeline de rendu logiciel. Réduire les vues est l'un des moyens les plus simples d'optimiser votre interface utilisateur.
Éviter les superpositions
Ne superposez pas trop de calques. Supprimez toutes les vues obscurcies par d'autres vues opaques. Si vous devez dessiner plusieurs couches fusionnées les unes aux autres, envisagez de les fusionner en une seule couche. En règle générale, avec le matériel actuel, il est recommandé de ne pas dessiner plus de 2,5 fois le nombre de pixels qui s'affichent à l'écran par image (les pixels transparents d'un bitmap comptent).
Ne pas créer d'objets à afficher dans les méthodes de dessin
Une erreur courante consiste à créer un nouvel objet Paint ou Path chaque fois qu'une méthode d'affichage est appelée. Cela force le récupérateur de mémoire à s'exécuter plus souvent, et contourne également les caches et les optimisations dans le pipeline matériel.
Ne pas modifier trop souvent les formes
Par exemple, les formes, les trajets et les cercles complexes sont affichés à l'aide de masques de texture. Chaque fois que vous créez ou modifiez un chemin d'accès, le pipeline matériel crée un masque qui peut s'avérer coûteux.
Ne pas modifier trop souvent les bitmaps
Chaque fois que vous modifiez le contenu d'un bitmap, celui-ci est réimporté en tant que texture GPU quand vous le dessinez la fois suivante.
Utiliser la version alpha avec précaution
Lorsque vous définissez une vue translucide à l'aide de setAlpha(), AlphaAnimation ou ObjectAnimator, elle est affichée dans un tampon hors écran qui double le taux de remplissage requis. Lorsque vous appliquez la version alpha à des vues très volumineuses, envisagez de définir le type de calque de la vue sur LAYER_TYPE_HARDWARE.