SurfaceView e GLSurfaceView

L'interfaccia utente del framework dell'app Android si basa su una gerarchia di oggetti che iniziano con View . Tutti gli elementi dell'interfaccia utente vengono sottoposti a una serie di misurazioni e a un processo di layout che li inserisce in un'area rettangolare. Quindi, viene eseguito il rendering di tutti gli oggetti vista visibili su una superficie configurata dal WindowManager quando l'app è stata portata in primo piano. Il thread dell'interfaccia utente dell'app esegue il layout e il rendering su un buffer per fotogramma.

Visualizzazione superficie

Un SurfaceView è un componente che puoi utilizzare per incorporare un livello composito aggiuntivo all'interno della gerarchia di visualizzazione. Un SurfaceView accetta gli stessi parametri di layout delle altre visualizzazioni, quindi può essere manipolato come qualsiasi altra visualizzazione, ma i contenuti di SurfaceView sono trasparenti.

Quando esegui il rendering con un'origine buffer esterna, come un contesto GL o un decodificatore multimediale, devi copiare i buffer dall'origine buffer per visualizzare i buffer sullo schermo. L'uso di SurfaceView ti consente di farlo.

Quando il componente di visualizzazione di SurfaceView sta per diventare visibile, il framework chiede a SurfaceControl di richiedere una nuova superficie da SurfaceFlinger. Per ricevere callback quando la superficie viene creata o distrutta, utilizzare l'interfaccia SurfaceHolder . Per impostazione predefinita, la superficie appena creata viene posizionata dietro la superficie dell'interfaccia utente dell'app. È possibile sovrascrivere l'ordinamento Z predefinito per mettere la nuova superficie in primo piano.

Il rendering con SurfaceView è utile nei casi in cui è necessario eseguire il rendering su una superficie separata, ad esempio quando si esegue il rendering con l'API Camera o un contesto OpenGL ES. Quando esegui il rendering con SurfaceView, SurfaceFlinger compone direttamente i buffer sullo schermo. Senza SurfaceView, è necessario comporre i buffer su una superficie fuori schermo, che viene poi composta sullo schermo, quindi il rendering con SurfaceView elimina il lavoro aggiuntivo. Dopo il rendering con SurfaceView, utilizza il thread dell'interfaccia utente per coordinarti con il ciclo di vita dell'attività e apportare modifiche alle dimensioni o alla posizione della vista, se necessario. Quindi, Hardware Composer unisce l'interfaccia utente dell'app e gli altri livelli.

La nuova superficie è il lato produttore di BufferQueue, il cui consumatore è un livello SurfaceFlinger. È possibile aggiornare la superficie con qualsiasi meccanismo in grado di alimentare un BufferQueue, ad esempio le funzioni Canvas fornite dalla superficie, collegando un EGLSurface e disegnando sulla superficie con GLES o configurando un decodificatore multimediale per scrivere la superficie.

SurfaceView e il ciclo di vita dell'attività

Quando usi SurfaceView, esegui il rendering della superficie da un thread diverso dal thread principale dell'interfaccia utente.

Per un'attività con SurfaceView, esistono due macchine a stati separate ma interdipendenti:

  • App onCreate / onResume / onPause
  • Superficie creata/modificata/distrutta

Quando l'attività inizia, ricevi richiamate in questo ordine:

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

Se fai clic indietro, ottieni:

  1. onPause()
  2. surfaceDestroyed() (chiamato appena prima che la superficie scompaia)

Se ruoti lo schermo, l'attività viene smontata e ricreata e ottieni il ciclo completo. Puoi capire che si tratta di un riavvio rapido controllando isFinishing() . È possibile avviare/interrompere un'attività così rapidamente che surfaceCreated() avviene dopo onPause() .

Se tocchi il pulsante di accensione per oscurare lo schermo, ottieni solo onPause() senza surfaceDestroyed() . La superficie rimane attiva e il rendering può continuare. Puoi continuare a ricevere eventi Choreographer se continui a richiederli. Se hai una schermata di blocco che impone un orientamento diverso, la tua attività potrebbe essere riavviata quando il dispositivo viene sbloccato. Altrimenti, puoi uscire dallo schermo vuoto con la stessa superficie di prima.

La durata del thread può essere legata alla superficie o all'attività, a seconda di cosa vuoi che accada quando lo schermo si spegne. Il thread può iniziare/interrompere all'avvio/interruzione dell'attività o alla creazione/eliminazione della superficie.

L'avvio/arresto del thread sull'avvio/arresto dell'attività funziona bene con il ciclo di vita dell'app. Inizi il thread del renderer in onResume() e lo interrompi in onStop() . Durante la creazione e la configurazione del thread, a volte la superficie esiste già, altre volte no (ad esempio, è ancora attiva dopo aver attivato lo schermo con il pulsante di accensione). Devi attendere la creazione della superficie prima di inizializzarla nel thread. Non è possibile inizializzare nel callback surfaceCreate() perché non si attiverà nuovamente se la superficie non è stata ricreata. Invece, esegui una query o memorizza nella cache lo stato della superficie e inoltralo al thread del renderer.

Avere l'avvio/arresto del thread sulla creazione/distruzione della superficie funziona bene perché la superficie e il renderer sono logicamente intrecciati. Il thread viene avviato dopo la creazione della superficie, il che evita alcuni problemi di comunicazione tra thread; e i messaggi creati/modificati sulla superficie vengono semplicemente inoltrati. Per garantire che il rendering si interrompa quando lo schermo diventa vuoto e riprenda quando viene ripristinato lo spazio vuoto, indicare a Choreographer di interrompere il richiamo del callback di disegno del fotogramma. onResume() riprende le richiamate se il thread del renderer è in esecuzione. Tuttavia, se esegui l'animazione in base al tempo trascorso tra i fotogrammi, potrebbe verificarsi un ampio intervallo prima che arrivi l'evento successivo; l'utilizzo di un messaggio di pausa/ripresa esplicito può risolvere questo problema.

Entrambe le opzioni, indipendentemente dal fatto che la durata del thread sia legata all'attività o alla superficie, si concentrano su come è configurato il thread del renderer e se è in esecuzione. Una preoccupazione correlata è l'estrazione dello stato dal thread quando l'attività viene interrotta (in onStop() o onSaveInstanceState() ); in questi casi, legare la durata del thread all'attività funziona meglio perché dopo che il thread del renderer è stato unito, è possibile accedere allo stato del thread sottoposto a rendering senza primitive di sincronizzazione.

GLSurfaceView

La classe GLSurfaceView fornisce classi helper per la gestione dei contesti EGL, la comunicazione tra thread e l'interazione con il ciclo di vita dell'attività. Non è necessario utilizzare GLSurfaceView per utilizzare GLES.

Ad esempio, GLSurfaceView crea un thread per il rendering e lì configura un contesto EGL. Lo stato viene ripulito automaticamente quando l'attività viene sospesa. La maggior parte delle app non ha bisogno di sapere nulla di EGL per utilizzare GLES con GLSurfaceView.

Nella maggior parte dei casi, GLSurfaceView può semplificare il lavoro con GLES. In alcune situazioni, può intralciarti.