SurfaceView e GLSurfaceView

A IU da estrutura do aplicativo Android é baseada em uma hierarquia de objetos que começam com um View . Todos os elementos da interface do usuário passam por uma série de medidas e um processo de layout que os encaixa em uma área retangular. Em seguida, todos os objetos de visualização visíveis são renderizados em uma superfície que foi configurada pelo WindowManager quando o aplicativo foi trazido para o primeiro plano. O thread de interface do usuário do aplicativo executa o layout e a renderização em um buffer por quadro.

Visualização de superfície

Um SurfaceView é um componente que você pode usar para incorporar uma camada de composição adicional em sua hierarquia de visualização. Um SurfaceView usa os mesmos parâmetros de layout que outras visualizações, portanto, pode ser manipulado como qualquer outra visualização, mas o conteúdo do SurfaceView é transparente.

Ao renderizar com uma fonte de buffer externa, como contexto GL ou um decodificador de mídia, você precisa copiar os buffers da fonte de buffer para exibir os buffers na tela. Usar um SurfaceView permite que você faça isso.

Quando o componente de exibição do SurfaceView está prestes a se tornar visível, a estrutura solicita ao SurfaceControl que solicite uma nova superfície do SurfaceFlinger. Para receber retornos de chamada quando a superfície é criada ou destruída, use a interface SurfaceHolder . Por padrão, a superfície recém-criada é colocada atrás da superfície da interface do usuário do aplicativo. Você pode substituir a ordenação Z padrão para colocar a nova superfície no topo.

A renderização com SurfaceView é benéfica nos casos em que você precisa renderizar em uma superfície separada, como quando você renderiza com a API da câmera ou um contexto OpenGL ES. Ao renderizar com o SurfaceView, o SurfaceFlinger compõe diretamente os buffers na tela. Sem um SurfaceView, você precisa compor buffers para uma superfície fora da tela, que então é composta para a tela, de modo que a renderização com o SurfaceView elimina o trabalho extra. Após renderizar com SurfaceView, use o thread de interface do usuário para coordenar com o ciclo de vida da atividade e fazer ajustes no tamanho ou na posição da exibição, se necessário. Em seguida, o Hardware Composer combina a interface do usuário do aplicativo e as outras camadas.

A nova superfície é o lado produtor de um BufferQueue, cujo consumidor é uma camada SurfaceFlinger. Você pode atualizar a superfície com qualquer mecanismo que possa alimentar um BufferQueue, como funções Canvas fornecidas pela superfície, anexando um EGLSurface e desenhando na superfície com GLES ou configurando um decodificador de mídia para gravar a superfície.

SurfaceView e o ciclo de vida da atividade

Ao usar um SurfaceView, renderize a superfície de um thread que não seja o thread da interface do usuário principal.

Para uma atividade com um SurfaceView, existem duas máquinas de estado separadas, mas interdependentes:

  • Aplicativo onCreate / onResume / onPause
  • Superfície criada/alterada/destruída

Quando a atividade começa, você recebe retornos de chamada nesta ordem:

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

Se você clicar de volta, obterá:

  1. onPause()
  2. surfaceDestroyed() (chamado logo antes da superfície desaparecer)

Se você girar a tela, a atividade será desfeita e recriada e você terá o ciclo completo. Você pode dizer que é uma reinicialização rápida verificando isFinishing() . É possível iniciar/parar uma atividade tão rapidamente que surfaceCreated() acontece após onPause() .

Se você tocar no botão liga / desliga para apagar a tela, obterá apenas onPause() sem surfaceDestroyed() . A superfície permanece ativa e a renderização pode continuar. Você pode continuar recebendo eventos do Coreógrafo se continuar a solicitá-los. Se você tiver uma tela de bloqueio que force uma orientação diferente, sua atividade poderá ser reiniciada quando o dispositivo for desbloqueado. Caso contrário, você pode sair da tela em branco com a mesma superfície de antes.

A vida útil do thread pode ser vinculada à superfície ou à atividade, dependendo do que você deseja que aconteça quando a tela ficar em branco. O encadeamento pode iniciar/parar no início/parada da atividade ou na criação/destruição da superfície.

Ter o início/parada do encadeamento no início/parada da atividade funciona bem com o ciclo de vida do aplicativo. Você inicia o thread do renderizador em onResume() e o interrompe em onStop() . Ao criar e configurar o thread, às vezes a superfície já existe, outras não (por exemplo, ainda está ativa após alternar a tela com o botão liga / desliga). Você tem que esperar que a superfície seja criada antes de inicializar no thread. Você não pode inicializar no retorno de chamada surfaceCreate() porque ele não será acionado novamente se a superfície não tiver sido recriada. Em vez disso, consulte ou armazene em cache o estado da superfície e encaminhe-o para o thread do renderizador.

Fazer o thread iniciar/parar na superfície criar/destruir funciona bem porque a superfície e o renderizador estão logicamente interligados. Você inicia o encadeamento após a criação da superfície, o que evita alguns problemas de comunicação entre encadeamentos; e as mensagens criadas/alteradas na superfície são simplesmente encaminhadas. Para garantir que a renderização pare quando a tela ficar em branco e recomece quando não estiver em branco, diga ao Choreographer para parar de invocar o retorno de chamada de desenho de quadro. onResume() retoma os retornos de chamada se o thread do renderizador estiver em execução. No entanto, se você animar com base no tempo decorrido entre os quadros, pode haver um grande intervalo antes da chegada do próximo evento; usar uma mensagem explícita de pausa/retomada pode resolver esse problema.

Ambas as opções, se a vida útil do encadeamento está vinculada à Atividade ou à superfície, focam em como o encadeamento do renderizador está configurado e se está em execução. Uma preocupação relacionada é extrair o estado do encadeamento quando a atividade é encerrada (em onStop() ou onSaveInstanceState() ); nesses casos, vincular o tempo de vida do encadeamento à atividade funciona melhor porque depois que o encadeamento do renderizador for unido, o estado do encadeamento renderizado pode ser acessado sem primitivas de sincronização.

GLSurfaceView

A classe GLSurfaceView fornece classes auxiliares para gerenciar contextos EGL, comunicação entre encadeamentos e interação com o ciclo de vida da atividade. Você não precisa usar um GLSurfaceView para usar GLES.

Por exemplo, GLSurfaceView cria um encadeamento para renderização e configura um contexto EGL lá. O estado é limpo automaticamente quando a atividade é pausada. A maioria dos aplicativos não precisa saber nada sobre EGL para usar GLES com GLSurfaceView.

Na maioria dos casos, o GLSurfaceView pode facilitar o trabalho com o GLES. Em algumas situações, pode atrapalhar.