SurfaceView e GLSurfaceView

A UI da estrutura do aplicativo Android é baseada em uma hierarquia de objetos que começam com View . Todos os elementos da UI passam por uma série de medições e um processo de layout que os ajusta 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 colocado em primeiro plano. O thread de UI do aplicativo executa o layout e a renderização em um buffer por quadro.

SurfaceView

Um SurfaceView é um componente que você pode usar para incorporar uma camada composta 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 um contexto GL ou um decodificador de mídia, você precisa copiar os buffers da origem do buffer para exibi-los na tela. Usar um SurfaceView permite que você faça isso.

Quando o componente de visualização do SurfaceView está prestes a se tornar visível, a estrutura solicita ao SurfaceControl que solicite uma nova superfície ao SurfaceFlinger. Para receber retornos de chamada quando a superfície for criada ou destruída, use a interface SurfaceHolder . Por padrão, a superfície recém-criada é colocada atrás da superfície da UI do aplicativo. Você pode substituir a ordem 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 Camera ou um contexto OpenGL ES. Ao renderizar com SurfaceView, o SurfaceFlinger compõe buffers diretamente na tela. Sem um SurfaceView, você precisa compor buffers em uma superfície fora da tela, que então é composta na tela, portanto, a renderização com SurfaceView elimina trabalho extra. Após a renderização com SurfaceView, use o thread de UI para coordenar com o ciclo de vida da atividade e fazer ajustes no tamanho ou na posição da visualização, se necessário. Em seguida, o Hardware Composer combina a IU 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 diferente do thread de UI principal.

Para uma atividade com 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, você obterá:

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

Se você girar a tela, a atividade será desmontada 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() aconteça depois de 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 Choreographer se continuar a solicitá-los. Se você tiver uma tela de bloqueio que força uma orientação diferente, sua atividade poderá ser reiniciada quando o dispositivo for desativado. Caso contrário, você poderá sair da tela em branco com a mesma superfície de antes.

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

Ter o thread iniciado/parado em Activity start/stop 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 vezes não (por exemplo, ela 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 o 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.

Ter o thread iniciado/parado na criação/destruição da superfície funciona bem porque a superfície e o renderizador estão logicamente interligados. Você inicia o thread após a criação da superfície, o que evita algumas preocupações de comunicação entre threads; e mensagens criadas/alteradas na superfície são simplesmente encaminhadas. Para garantir que a renderização pare quando a tela ficar em branco e seja retomada quando ela ficar 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, poderá 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 thread está vinculada à atividade ou à superfície, concentram-se em como o thread do renderizador está configurado e se está em execução. Uma preocupação relacionada é extrair o estado do thread quando a atividade é interrompida (em onStop() ou onSaveInstanceState() ); nesses casos, vincular a vida útil do encadeamento à atividade funciona melhor porque, após a união do encadeamento do renderizador, 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 threads e interação com o ciclo de vida da atividade. Você não precisa usar um GLSurfaceView para usar o GLES.

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

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