SurfaceView et GLSurfaceView

L'interface utilisateur du framework d'application Android est basée sur une hiérarchie d'objets qui commencent par un View . Tous les éléments de l'interface utilisateur passent par une série de mesures et un processus de disposition qui les adapte à une zone rectangulaire. Ensuite, tous les objets de vue visibles sont rendus sur une surface configurée par WindowManager lorsque l'application a été placée au premier plan. Le thread d'interface utilisateur de l'application effectue la mise en page et le rendu dans un tampon par image.

SurfaceView

Un SurfaceView est un composant que vous pouvez utiliser pour intégrer une couche composite supplémentaire dans votre hiérarchie de vues. Un SurfaceView prend les mêmes paramètres de présentation que les autres vues, il peut donc être manipulé comme n'importe quelle autre vue, mais le contenu du SurfaceView 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 afficher les tampons à l'écran. L'utilisation d'un 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 avantageux dans les cas où 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, de sorte que le rendu avec SurfaceView élimine le travail supplémentaire. Après le rendu avec SurfaceView, utilisez le thread d'interface utilisateur pour vous coordonner avec le cycle de vie de l'activité et apporter des ajustements à la taille ou à la position de la vue si nécessaire. Ensuite, Hardware Composer mélange l’interface utilisateur de l’application et les autres couches.

La nouvelle surface est le côté producteur d'un BufferQueue, dont le consommateur est une couche SurfaceFlinger. Vous pouvez mettre à jour la surface avec n'importe quel mécanisme capable d'alimenter une BufferQueue, comme les fonctions Canvas fournies par la surface, attacher une EGLSurface et dessiner sur la surface avec GLES, ou configurer un décodeur multimédia pour écrire la surface.

SurfaceView et le cycle de vie des activités

Lorsque vous utilisez un SurfaceView, restituez la surface à partir d’un thread autre que le thread principal de l’interface utilisateur.

Pour une activité avec 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 :

  1. onCreate()
  2. onResume()
  3. surfaceCreated()
  4. surfaceChanged()

Si vous cliquez en arrière, vous obtenez :

  1. onPause()
  2. surfaceDestroyed() (appelée juste avant que la surface ne disparaisse)

Si vous faites pivoter l'écran, l'activité est supprimée et recréée et vous obtenez le cycle complet. Vous pouvez dire 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 d'alimentation pour effacer l'écran, vous obtenez uniquement onPause() sans surfaceDestroyed() . La surface reste active et le rendu peut continuer. Vous pouvez continuer à recevoir des événements Chorégraphe si vous continuez à en faire la demande. Si vous disposez d'un écran de verrouillage qui force une orientation différente, votre activité peut être redémarrée lorsque l'appareil est réactivé. Sinon, vous pouvez ressortir d'un écran vide avec la même surface qu'avant.

La durée de vie du fil 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 soit au démarrage/arrêt de l'activité, soit lors de la création/destruction d'une surface.

Le démarrage/arrêt du thread sur le démarrage/arrêt de l'activité fonctionne bien avec le cycle de vie de l'application. Vous démarrez le thread de rendu dans onResume() et l'arrêtez dans onStop() . Lors de la création et de la configuration du thread, parfois la surface existe déjà, d'autres fois non (par exemple, elle est toujours active après avoir basculé l'écran avec le bouton d'alimentation). Il faut attendre que la surface soit créée avant de l'initialiser dans 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. Au lieu de cela, interrogez ou mettez en cache l’état de la surface et transmettez-le au thread de rendu.

Faire démarrer/arrêter le thread lors de la création/destination 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 threads ; et les messages créés/modifiés en surface sont simplement transmis. Pour garantir que le rendu s'arrête lorsque l'écran devient vide et reprend lorsqu'il redevient vide, dites à Choreographer d'arrêter d'appeler le rappel de dessin d'image. onResume() reprend les rappels si le thread de rendu est en cours d'exécution. Cependant, 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 façon dont le thread de rendu est configuré et s'il s'exécute. Une préoccupation connexe est l'extraction de l'état du thread lorsque l'activité est interrompue (dans onStop() ou onSaveInstanceState() ) ; dans de tels cas, lier la durée de vie du thread à l'activité fonctionne mieux car une fois le thread de rendu rejoint, l'état du thread rendu est accessible sans primitives de synchronisation.

GLSurfaceView

La classe GLSurfaceView fournit des classes d'assistance pour la gestion des contextes EGL, la communication interthread et l'interaction avec le cycle de vie de l'activité. Vous n'avez pas besoin d'utiliser un 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é s'interrompt. 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 certaines situations, cela peut gêner.