La IU del framework de la app para Android se basa en una jerarquía de objetos que comienza con una View. Todos los elementos de la IU pasan por una serie de mediciones y un proceso de diseño que los ajusta a un área rectangular. Luego, todos los objetos de vista visibles se renderizan en una superficie que WindowManager configuró 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 elemento SurfaceView es un componente que puedes usar para incorporar una capa compuesta adicional en tu jerarquía de vistas. Una SurfaceView toma los mismos parámetros de diseño que otras vistas, por lo que se puede manipular como cualquier otra, pero el contenido de SurfaceView es transparente.
Cuando renderizas con una fuente de búfer externa, como el contexto de GL o un decodificador de medios, debes copiar los búferes de la fuente de búfer para mostrarlos en la pantalla. Usar un SurfaceView te permite hacerlo.
Cuando el componente de vista de SurfaceView está a punto de hacerse 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, la superficie recién creada se coloca detrás de la superficie de la IU de la app. Puedes anular el orden en Z predeterminado para colocar la nueva superficie en la parte superior.
La renderización con SurfaceView es beneficiosa en los casos en los que necesitas renderizar en una superficie independiente, 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 combinar los búferes en una superficie fuera de la pantalla, que luego se combina con la pantalla, por lo que la renderización con SurfaceView elimina el trabajo adicional. Después de renderizar con SurfaceView, usa el subproceso de IU para coordinar con 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 otras 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 un BufferQueue, como las funciones de Canvas proporcionadas por la superficie, adjuntar un EGLSurface y dibujar en la superficie con GLES, o configurar un decodificador de contenido multimedia para escribir 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 principal de la IU.
Para 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:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Si haces clic en Atrás, verás lo siguiente:
onPause()
surfaceDestroyed()
(se llama justo antes de que desaparezca la superficie)
Si rotas la pantalla, la actividad se desmonta y se vuelve a crear, y obtienes el ciclo completo. Para saber si se trata de un reinicio rápido, consulta isFinishing()
. Es posible iniciar o detener una actividad con tanta rapidez que surfaceCreated()
se produce después de onPause()
.
Si presionas el botón de encendido para dejar la pantalla en blanco, solo obtienes 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 impone una orientación diferente, es posible que se reinicie tu actividad cuando se desbloquee el dispositivo. De lo contrario, puedes salir de la pantalla en blanco con la misma superficie que antes.
La vida útil del subproceso se puede vincular a la superficie o a la actividad, según lo que quieras que suceda cuando la pantalla quede en blanco. El subproceso puede iniciarse o detenerse en el inicio o la detención de la actividad, o en la creación o destrucción de la superficie.
Tener el inicio o la detención del subproceso en el inicio o la detención de 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, a veces, no (por ejemplo, sigue activa después de activar 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 de surfaceCreate()
porque no se volverá a activar si no se volvió a crear la superficie. En su lugar, consulta o almacena en caché el estado de la superficie y reenvíalo al subproceso del renderizador.
Tener el inicio o la detención del subproceso en la creación o destrucción de la superficie funciona bien porque la superficie y el renderizador están entrelazados de forma lógica. Inicias el subproceso después de que se crea la plataforma, lo que evita algunos problemas de comunicación entre subprocesos, y los mensajes creados o modificados en la plataforma simplemente se reenvían. Para garantizar que la renderización se detenga cuando la pantalla se vuelva en blanco y se reanude cuando se vuelva a mostrar, dile a Choreographer que deje de invocar la devolución de llamada de dibujo de marco. onResume()
reanuda las devoluciones de llamada si el subproceso del renderizador está en ejecución. Sin embargo, si animas según el tiempo transcurrido entre fotogramas, podría haber una gran brecha antes de que llegue el siguiente evento. Puedes usar un mensaje explícito de pausa o reanudación para 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 cancela 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 auxiliares para administrar contextos de EGL, comunicación entre subprocesos y la interacción con el ciclo de vida de la actividad. No necesitas usar 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 limpia 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 interferir.