L'interface utilisateur 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 insère dans une zone rectangulaire. Ensuite, le framework affiche tous les objets de vue visibles sur une surface configurée par le WindowManager lorsque l'application a été mise au premier plan. Le thread UI de l'application effectue la mise en page et le rendu 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 le 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. C'est ce que vous permet de faire SurfaceView.
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, le framework place la surface nouvellement créée derrière la surface de l'UI 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 les tampons sur l'écran. Sans SurfaceView, vous devez composer des tampons sur une surface hors écran, qui est ensuite composée sur l'écran. Le rendu avec SurfaceView élimine donc ce travail supplémentaire. Après le rendu avec SurfaceView, utilisez le thread d'UI pour coordonner le cycle de vie de l'activité et ajuster la taille ou la position de la vue si nécessaire. Ensuite, le Hardware Composer fusionne l'UI de l'application et les autres couches.
La nouvelle surface est le côté producteur d'une BufferQueue, dont le consommateur est un calque 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, en attachant une EGLSurface et en dessinant sur la surface avec GLES, ou en configurant un décodeur multimédia pour écrire la surface.
SurfaceView et le cycle de vie de l'activité
Lorsque vous utilisez une SurfaceView, affichez la surface à partir d'un thread autre que le thread d'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é démarre, vous recevez des rappels dans cet ordre :
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. Pour savoir s'il s'agit d'un redémarrage rapide, vérifiez 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 se poursuivre. Vous pouvez continuer à recevoir des événements Choreographer si vous continuez à les demander. Si vous avez un écran de verrouillage qui force une orientation différente, votre activité peut être redémarrée lorsque l'écran de l'appareil est déverrouillé. Sinon, vous pouvez sortir de l'écran vide avec la même surface qu'avant.
La durée de vie du thread peut être liée à la surface ou à l'activité, selon ce que vous souhaitez qu'il se passe lorsque l'écran devient vide. Le thread peut démarrer/s'arrêter au début/à la fin de l'activité ou à la création/destruction de la surface.
Le fait que le thread démarre/s'arrête au début/à la fin 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()
. Lors de la création et de la configuration du thread, la surface existe parfois déjà, parfois non (par exemple, elle est toujours active après avoir allumé et éteint l'écran avec le bouton Marche/Arrêt). Vous devez attendre que la surface soit créée avant d'initialiser le thread. Vous ne pouvez pas initialiser dans le rappel surfaceCreate()
, car il ne se déclenchera plus si la surface n'a pas été recréée. Interrogez ou mettez plutôt en cache l'état de la surface, puis transmettez-le au thread du moteur de rendu.
Le fait que le thread démarre/s'arrête lors de la création/destruction de la surface fonctionne bien, car la surface et le moteur de rendu sont logiquement lié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 transférés. Pour vérifier que le rendu s'arrête lorsque l'écran devient noir et reprend lorsqu'il redevient normal, 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 frames, il peut y avoir un grand écart 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 façon dont le thread du moteur de rendu est configuré et sur son exécution. Une préoccupation connexe consiste à extraire l'état du thread lorsque l'activité est arrêtée (dans onStop()
ou onSaveInstanceState()
). Dans ce cas, il est préférable de lier la durée de vie du thread à l'activité, car une fois que le thread du moteur de rendu a été joint, l'état du thread rendu est accessible sans primitives de synchronisation.
GLSurfaceView
La classe GLSurfaceView fournit des classes d'assistance pour gérer les contextes EGL, la communication entre les threads et l'interaction avec le cycle de vie de l'activité. Vous n'avez pas besoin d'utiliser 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 l'utilisation de GLES. Dans certains cas, cela peut être gênant.