SurfaceView y GLSurfaceView

La IU del framework de la app para Android se basa en una jerarquía de objetos que comienza con un View. Todos los elementos de la IU pasan por una serie de mediciones y un proceso de diseño que los ajusta en un área rectangular. Luego, el framework renderiza todos los objetos de vista visibles en una superficie que configuró WindowManager cuando la app pasó al primer plano. El subproceso de IU de la app realiza el diseño y la renderización en un búfer por fotograma.

SurfaceView

Un SurfaceView es un componente que puedes usar para incorporar una capa compuesta adicional dentro de tu jerarquía de vistas. Un SurfaceView toma los mismos parámetros de diseño que otras vistas, por lo que se puede manipular como cualquier otra vista, pero el contenido de SurfaceView es transparente.

Cuando renderizas con una fuente de búfer externa, como un contexto de GL o un decodificador de medios, debes copiar los búferes de la fuente de búfer para mostrarlos en la pantalla. El uso de un SurfaceView te permite hacerlo.

Cuando el componente de vista de SurfaceView está a punto de volverse visible, el framework le pide a SurfaceControl que solicite una nueva superficie a SurfaceFlinger. Para recibir devoluciones de llamada cuando se crea o destruye la superficie, usa la interfaz SurfaceHolder. De forma predeterminada, el framework coloca la superficie recién creada detrás de la superficie de la IU de la app. Puedes anular el orden Z predeterminado para colocar la nueva superficie en la parte superior.

El renderizado con SurfaceView es beneficioso en los casos en los que necesitas renderizar en una superficie separada, como cuando renderizas con la API de Camera o un contexto de OpenGL ES. Cuando renderizas con SurfaceView, SurfaceFlinger compone directamente los búferes en la pantalla. Sin un SurfaceView, debes componer búferes en una superficie fuera de la pantalla, que luego se compone en la pantalla, por lo que el procesamiento con SurfaceView elimina el trabajo adicional. Después de renderizar con SurfaceView, usa el subproceso de IU para coordinar el ciclo de vida de la actividad y realizar ajustes en el tamaño o la posición de la vista si es necesario. Luego, el Hardware Composer combina la IU de la app y las demás capas.

La nueva superficie es el lado del productor de un BufferQueue, cuyo consumidor es una capa de SurfaceFlinger. Puedes actualizar la superficie con cualquier mecanismo que pueda alimentar una BufferQueue, como las funciones de Canvas proporcionadas por la superficie, adjuntar una EGLSurface y dibujar en la superficie con GLES, o configurar un decodificador de medios para escribir en la superficie.

SurfaceView y el ciclo de vida de la actividad

Cuando uses un SurfaceView, renderiza la superficie desde un subproceso que no sea el subproceso de IU principal.

En el caso de una actividad con un SurfaceView, hay dos máquinas de estados separadas, pero interdependientes:

  • App onCreate/onResume/onPause
  • Se creó, cambió o destruyó una superficie

Cuando se inicia la actividad, recibes devoluciones de llamada en este orden:

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

Si haces clic en Atrás, verás lo siguiente:

  1. onPause()
  2. surfaceDestroyed() (se llama justo antes de que desaparezca la superficie)

Si rotas la pantalla, la actividad se destruye y se vuelve a crear, y obtienes el ciclo completo. Para saber si es un reinicio rápido, verifica isFinishing(). Es posible iniciar o detener una actividad tan rápido que surfaceCreated() ocurra después de onPause().

Si presionas el botón de encendido para apagar la pantalla, solo obtendrás onPause() sin surfaceDestroyed(). La superficie permanece activa y la renderización puede continuar. Puedes seguir recibiendo eventos de Choreographer si sigues solicitándolos. Si tienes una pantalla de bloqueo que fuerza una orientación diferente, es posible que se reinicie tu actividad cuando se quite la pantalla en blanco del dispositivo. De lo contrario, puedes salir de la pantalla en blanco con la misma superficie que antes.

La vida útil del subproceso puede estar vinculada a la superficie o a la actividad, según lo que quieras que suceda cuando la pantalla se ponga en blanco. El subproceso puede iniciarse o detenerse cuando se inicia o detiene la actividad, o bien cuando se crea o destruye la superficie.

Hacer que el subproceso se inicie o detenga cuando se inicia o detiene la actividad funciona bien con el ciclo de vida de la app. Inicias el subproceso del renderizador en onResume() y lo detienes en onStop(). Cuando se crea y configura el subproceso, a veces la superficie ya existe y otras veces no (por ejemplo, sigue activa después de alternar la pantalla con el botón de encendido). Debes esperar a que se cree la superficie antes de inicializarla en el subproceso. No puedes inicializar en la devolución de llamada surfaceCreate() porque no se activará de nuevo si no se vuelve a crear la superficie. En su lugar, consulta o almacena en caché el estado de la superficie y reenvíalo al subproceso del renderizador.

El inicio y la detención del subproceso en la creación y destrucción de la superficie funcionan bien porque la superficie y el renderizador están entrelazados de forma lógica. Inicias el subproceso después de que se crea la superficie, lo que evita algunos problemas de comunicación entre subprocesos, y se reenvían los mensajes de superficie creada o cambiada. Para verificar que la renderización se detiene cuando la pantalla se pone en blanco y se reanuda cuando deja de estarlo, dile a Choreographer que deje de invocar la devolución de llamada de dibujo de fotogramas. onResume() reanuda las devoluciones de llamada si el subproceso del renderizador está en ejecución. Sin embargo, si animas en función del tiempo transcurrido entre fotogramas, podría haber una gran brecha antes de que llegue el siguiente evento. Usar un mensaje explícito de pausa o reanudación puede resolver este problema.

Ambas opciones, ya sea que la vida útil del subproceso esté vinculada a la actividad o a la superficie, se enfocan en cómo se configura el subproceso del renderizador y si se está ejecutando. Una preocupación relacionada es extraer el estado del subproceso cuando se detiene la actividad (en onStop() o onSaveInstanceState()). En esos casos, vincular la vida útil del subproceso a la actividad funciona mejor porque, después de que se une el subproceso del renderizador, se puede acceder al estado del subproceso renderizado sin primitivas de sincronización.

GLSurfaceView

La clase GLSurfaceView proporciona clases de ayuda para administrar contextos de EGL, la comunicación entre subprocesos y la interacción con el ciclo de vida de la actividad. No es necesario que uses un GLSurfaceView para usar GLES.

Por ejemplo, GLSurfaceView crea un subproceso para la renderización y configura un contexto de EGL allí. El estado se borra automáticamente cuando se pausa la actividad. La mayoría de las apps no necesitan saber nada sobre EGL para usar GLES con GLSurfaceView.

En la mayoría de los casos, GLSurfaceView puede facilitar el trabajo con GLES. En algunas situaciones, puede ser un obstáculo.