L'UI du framework d'application Android est basée sur une hiérarchie d'objets qui commence par une vue. Tous les éléments d'interface utilisateur passent par une série de mesures et un processus de mise en page qui les ajuste dans une zone rectangulaire. Ensuite, tous les objets de vue visibles sont affichés sur une surface configurée par WindowManager lorsque l'application est mise au premier plan. Le thread d'UI de l'application effectue la mise en page et l'affichage dans un tampon par frame.
SurfaceView
Une SurfaceView est un composant que vous pouvez utiliser pour intégrer une couche composite supplémentaire dans votre hiérarchie de vues. Une SurfaceView utilise les mêmes paramètres de mise en page que les autres vues. Elle peut donc être manipulée comme n'importe quelle autre vue, mais son contenu est transparent.
Lorsque vous effectuez un rendu avec une source de tampon externe, telle qu'un contexte GL ou un décodeur multimédia, vous devez copier les tampons de la source de tampon pour les afficher à l'écran. L'utilisation d'une SurfaceView vous permet de le faire.
Lorsque le composant de vue de SurfaceView est sur le point de devenir visible, le framework demande à SurfaceControl de demander une nouvelle surface à SurfaceFlinger. Pour recevoir des rappels lorsque la surface est créée ou détruite, utilisez l'interface SurfaceHolder. Par défaut, la surface nouvellement créée est placée derrière la surface de l'interface utilisateur de l'application. Vous pouvez remplacer l'ordre Z par défaut pour placer la nouvelle surface au-dessus.
Le rendu avec SurfaceView est utile lorsque vous devez effectuer un rendu sur une surface distincte, par exemple lorsque vous effectuez un rendu avec l'API Camera ou un contexte OpenGL ES. Lorsque vous effectuez un rendu avec SurfaceView, SurfaceFlinger compose directement des tampons à l'écran. Sans SurfaceView, vous devez composer des tampons sur une surface hors écran, qui est ensuite composée à l'écran. Le rendu avec SurfaceView élimine donc le travail supplémentaire. Après l'affichage avec SurfaceView, utilisez le thread d'UI pour vous coordonner avec le cycle de vie de l'activité et ajuster la taille ou la position de la vue si nécessaire. Ensuite, le compilateur matériel associe l'interface utilisateur de l'application et les autres couches.
La nouvelle surface est le côté producteur d'une BufferQueue, dont le consommateur est une couche SurfaceFlinger. Vous pouvez mettre à jour la surface avec n'importe quel mécanisme pouvant alimenter une BufferQueue, comme les fonctions Canvas fournies par la surface, l'attachement d'une EGLSurface et le dessin sur la surface avec GLES, ou la configuration d'un décodeur multimédia pour écrire la surface.
SurfaceView et cycle de vie de l'activité
Lorsque vous utilisez une SurfaceView, affichez la surface à partir d'un thread autre que le thread UI principal.
Pour une activité avec une SurfaceView, il existe deux machines à états distinctes, mais interdépendantes:
- Application
onCreate
/onResume
/onPause
- Surface créée/modifiée/détruite
Lorsque l'activité commence, vous recevez des rappels dans l'ordre suivant:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Si vous cliquez sur "Retour", vous obtenez:
onPause()
surfaceDestroyed()
(appelé juste avant la disparition de la surface)
Si vous faites pivoter l'écran, l'activité est détruite et recréée, et vous obtenez le cycle complet. Vous pouvez déterminer qu'il s'agit d'un redémarrage rapide en vérifiant isFinishing()
. Il est possible de démarrer/arrêter une activité si rapidement que surfaceCreated()
se produit après onPause()
.
Si vous appuyez sur le bouton Marche/Arrêt pour éteindre l'écran, vous n'obtenez que onPause()
sans surfaceDestroyed()
. La surface reste active et le rendu peut continuer. Vous pouvez continuer à recevoir des événements Choreographer si vous continuez à les demander. Si un écran de verrouillage force une autre orientation, votre activité peut être redémarrée lorsque l'écran de l'appareil est activé. Sinon, vous pouvez sortir de l'écran vide avec la même surface qu'auparavant.
La durée de vie du thread peut être liée à la surface ou à l'activité, en fonction de ce que vous souhaitez qu'il se passe lorsque l'écran devient vide. Le thread peut démarrer/s'arrêter au démarrage/arrêt de l'activité ou à la création/destruction de la surface.
Le démarrage/arrêt du thread au démarrage/arrêt de l'activité fonctionne bien avec le cycle de vie de l'application. Vous démarrez le thread du moteur de rendu dans onResume()
et l'arrêtez dans onStop()
.
Lorsque vous créez et configurez le thread, la surface existe parfois déjà, mais parfois non (par exemple, elle est toujours active après avoir activé l'écran avec le bouton Marche/Arrêt). Vous devez attendre que la surface soit créée avant de l'initialiser dans le thread. Vous ne pouvez pas effectuer l'initialisation dans le rappel surfaceCreate()
, car il ne se déclenchera pas à nouveau si la surface n'a pas été recréée. À la place, interrogez ou mettez en cache l'état de la surface, puis transmettez-le au thread du moteur de rendu.
Le démarrage/arrêt du thread lors de la création/destruction de la surface fonctionne bien, car la surface et le moteur de rendu sont logiquement entrelacés. Vous démarrez le thread après la création de la surface, ce qui évite certains problèmes de communication entre les threads. Les messages de création/modification de la surface sont simplement transférés. Pour vous assurer que le rendu s'arrête lorsque l'écran est vide et reprend lorsqu'il n'est plus vide, demandez à Choreographer d'arrêter d'appeler le rappel de dessin de frame. onResume()
reprend les rappels si le thread du moteur de rendu est en cours d'exécution. Toutefois, si vous animez en fonction du temps écoulé entre les images, il peut y avoir un écart important avant l'arrivée de l'événement suivant. L'utilisation d'un message de pause/reprise explicite peut résoudre ce problème.
Les deux options, que la durée de vie du thread soit liée à l'activité ou à la surface, se concentrent sur la configuration du thread du moteur de rendu et sur son exécution. Un problème connexe consiste à extraire l'état du thread lorsque l'activité est arrêtée (dans onStop()
ou onSaveInstanceState()
). Dans ce cas, lier la durée de vie du thread à l'activité fonctionne mieux, car une fois le thread du moteur de rendu joint, l'état du thread rendu peut être consulté sans primitives de synchronisation.
GLSurfaceView
La classe GLSurfaceView fournit des classes d'assistance pour gérer les contextes EGL, la communication interthread et l'interaction avec le cycle de vie de l'activité. Vous n'avez pas besoin d'utiliser une GLSurfaceView pour utiliser GLES.
Par exemple, GLSurfaceView crée un thread pour le rendu et y configure un contexte EGL. L'état est nettoyé automatiquement lorsque l'activité est mise en pause. La plupart des applications n'ont pas besoin de connaître EGL pour utiliser GLES avec GLSurfaceView.
Dans la plupart des cas, GLSurfaceView peut faciliter le travail avec GLES. Dans certains cas, il peut gêner.