A interface da estrutura de apps Android é baseada em uma hierarquia de objetos que começa com uma View. Todos os elementos da interface passam por uma série de medições e um processo de layout que os encaixa em uma área retangular. Em seguida, o framework renderiza todos os objetos de visualização visíveis em uma superfície configurada pelo WindowManager quando o app foi colocado em primeiro plano. A linha de execução da interface do usuário do app faz o layout e a renderização em um buffer por frame.
SurfaceView
Uma SurfaceView é um componente que pode ser usado para incorporar uma camada composta adicional na hierarquia de visualização. Uma SurfaceView usa os mesmos parâmetros de layout que outras visualizações. Portanto, ela pode ser manipulada como qualquer outra visualização, mas o conteúdo da SurfaceView é transparente.
Ao renderizar com uma origem de buffer externa, como um contexto GL ou um decodificador de mídia, é necessário copiar buffers da origem para mostrar os buffers na tela. Usar uma SurfaceView permite fazer isso.
Quando o componente de visualização do SurfaceView está prestes a ficar visível, o framework pede ao SurfaceControl para solicitar uma nova superfície do SurfaceFlinger. Para receber callbacks quando a superfície é criada ou destruída, use a interface SurfaceHolder. Por padrão, o framework coloca a superfície recém-criada atrás da superfície da interface do app. Você pode substituir a ordenação Z padrão para colocar a nova superfície na parte de cima.
A renderização com SurfaceView é útil em 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 o SurfaceView, o SurfaceFlinger compõe diretamente os buffers na tela. Sem um SurfaceView, é necessário compor buffers em uma superfície fora da tela, que é composta na tela. Assim, a renderização com SurfaceView elimina trabalho extra. Depois de renderizar com SurfaceView, use a linha de execução da UI para coordenar com o ciclo de vida da atividade e faça ajustes no tamanho ou na posição da visualização, se necessário. Em seguida, o Hardware Composer combina a interface do app e as outras camadas.
A nova superfície é o lado do produtor de uma BufferQueue, cujo consumidor é uma camada do SurfaceFlinger. É possível atualizar a superfície com qualquer mecanismo que possa alimentar uma BufferQueue, como funções Canvas fornecidas pela superfície, anexando uma 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 uma linha de execução diferente da linha de execução principal da interface.
Para uma atividade com um SurfaceView, há duas máquinas de estado separadas, mas interdependentes:
- App
onCreate
/onResume
/onPause
- Superfície criada/alterada/destruída
Quando a atividade é iniciada, você recebe callbacks nesta ordem:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Se você clicar em "Voltar", vai receber:
onPause()
surfaceDestroyed()
(chamado logo antes de a superfície desaparecer)
Se você girar a tela, a atividade será destruída e recriada, e você
receberá o ciclo completo. Para saber se é uma reinicialização rápida, verifique
isFinishing()
. É possível iniciar/parar uma atividade tão rapidamente
que surfaceCreated()
acontece depois de onPause()
.
Se você tocar no botão liga/desliga para deixar a tela em branco, vai receber apenas
onPause()
sem surfaceDestroyed()
. A superfície permanece ativa, e a renderização pode continuar. Você pode continuar recebendo eventos do
Choreographer se continuar solicitando. Se você tiver uma tela de bloqueio
que força 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 duração da linha de execução pode ser vinculada à superfície ou à atividade, dependendo do que você quer que aconteça quando a tela ficar em branco. A execução pode ser iniciada/interrompida no início/fim da atividade ou na criação/destruição da superfície.
Ter o início/fim da linha de execução no início/fim da atividade funciona bem com o ciclo de vida do app. Você inicia a linha de execução do renderizador em onResume()
e a interrompe em onStop()
. Ao criar e configurar a linha de execução, às vezes a superfície já existe, outras vezes não. Por exemplo, ela ainda está ativa depois de alternar a tela com o botão liga/desliga. É necessário
aguardar a criação da superfície antes de inicializar na linha de execução. Não é possível
inicializar no callback surfaceCreate()
porque ele não será acionado
novamente se a superfície não for recriada. Em vez disso, consulte ou armazene em cache o estado
da superfície e encaminhe para a linha de execução do renderizador.
Iniciar/parar a linha de execução na criação/destruição da superfície funciona bem porque
a superfície e o renderizador estão logicamente interligados. Você inicia a linha de execução depois que a superfície é criada, o que evita alguns problemas de comunicação entre linhas de execução, e as mensagens de criação/mudança de superfície são encaminhadas. Para verificar se a renderização para quando a tela fica em branco e é retomada quando ela volta ao normal, diga ao Choreographer para parar de invocar o callback de desenho de frame. onResume()
retoma os callbacks se a linha de execução do renderizador estiver em execução. No entanto, se você
animar com base no tempo decorrido entre frames, poderá haver uma grande lacuna antes
da chegada do próximo evento. Usar uma mensagem explícita de pausa/retomada pode resolver esse
problema.
Ambas as opções, seja a vida útil da linha de execução vinculada à atividade
ou à superfície, se concentram em como a linha de execução do renderizador é
configurada e se está em execução. Uma preocupação relacionada é extrair o estado da linha de execução quando a atividade é encerrada (em onStop()
ou onSaveInstanceState()
). Nesses casos, vincular o ciclo de vida da linha de execução à atividade funciona melhor porque, depois que a linha de execução do renderizador é unida, o estado da linha de execução renderizada pode ser acessado sem primitivos de sincronização.
GLSurfaceView
A classe GLSurfaceView oferece classes auxiliares para gerenciar contextos EGL, comunicação entre linhas de execução e interação com o ciclo de vida da atividade. Não é necessário usar uma GLSurfaceView para usar o GLES.
Por exemplo, o GLSurfaceView cria uma linha de execução para renderização e configura um contexto EGL nela. O estado é limpo automaticamente quando a atividade é pausada. A maioria dos apps não precisa saber nada sobre EGL para usar GLES com GLSurfaceView.
Na maioria dos casos, o GLSurfaceView facilita o trabalho com o GLES. Em algumas situações, ele pode atrapalhar.