SurfaceView e GLSurfaceView

L'interfaccia utente del framework per app Android si basa su una gerarchia di oggetti che inizia con una visualizzazione. 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. Successivamente, tutti gli oggetti della vista visibili vengono visualizzati su una superficie configurata da WindowManager quando l'app è stata messa in primo piano. Il thread dell'interfaccia utente dell'app esegue il layout e il rendering in un buffer per frame.

SurfaceView

Un SurfaceView è un componente che puoi utilizzare per incorporare un livello composito aggiuntivo nella gerarchia delle visualizzazioni. Un SurfaceView utilizza gli stessi parametri di layout di 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, ad esempio il contesto GL o un decodificatore multimediale, devi copiare i buffer dall'origine buffer per visualizzarli sullo schermo. Puoi farlo utilizzando un SurfaceView.

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

Il rendering con SurfaceView è utile nei casi in cui devi eseguire il rendering su una superficie separata, ad esempio quando esegui 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 un SurfaceView, devi comporre buffer su una superficie offscreen, che poi viene composta sullo schermo, quindi l'uso di SurfaceView per il rendering 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, se necessario, apporta modifiche alle dimensioni o alla posizione della visualizzazione. Quindi, Hardware Composer unisce l'interfaccia utente dell'app e gli altri livelli.

La nuova superficie è il lato del produttore di una BufferQueue, il cui consumatore è un livello SurfaceFlinger. Puoi aggiornare la superficie con qualsiasi meccanismo in grado di alimentare una BufferQueue, ad esempio le funzioni Canvas fornite dalla superficie, l'attacco di un'EGLSurface e il disegno sulla superficie con GLES o la configurazione di un decodificatore multimediale per scrivere sulla superficie.

SurfaceView e il ciclo di vita dell'attività

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

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

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

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

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

Se fai clic su Indietro, visualizzi:

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

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

Se tocchi il tasto di accensione per spegnere lo schermo, viene visualizzato soloonPause() senza surfaceDestroyed(). La superficie rimane attiva e il rendering può continuare. Puoi continuare a ricevere gli eventi Choreographer se continui a richiederli. Se hai una schermata di blocco che forza un orientamento diverso, la tua attività potrebbe essere riavviata quando il dispositivo viene sbloccato. In caso contrario, 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 diventa vuoto. Il thread può essere avviato/interrotto all'avvio/all'arresto dell'attività o alla creazione/distruzione della superficie.

L'avvio/l'arresto del thread all'avvio/all'arresto dell'attività funziona bene con il ciclo di vita dell'app. Avvia il thread del renderer in onResume() e interrompilo in onStop(). Quando crei e configuri il thread, a volte la superficie esiste già, a volte no (ad esempio, è ancora attiva dopo aver attivato/disattivato lo schermo con il tasto di accensione). Devi attendere che la superficie venga creata prima di eseguire l'inizializzazione nel thread. Non puoi eseguire l'inizializzazione nel callback surfaceCreate() perché non verrà attivato di nuovo se la superficie non è stata ricreata. Esegui invece una query o memorizza nella cache lo stato della superficie e inoltrarlo al thread del renderer.

L'avvio/l'arresto del thread su creazione/distruzione della superficie funziona bene perché la superficie e il renderer sono collegati logicamente. Avvia il thread dopo la creazione della piattaforma, il che consente di evitare alcuni problemi di comunicazione tra thread. I messaggi creati/modificati sulla piattaforma vengono semplicemente inoltrati. Per assicurarti che il rendering si interrompa quando lo schermo diventa vuoto e riprenda quando non è più vuoto, chiedi a Choreographer di interrompere l'invocazione del callback di disegno del frame. onResume() riprende i callback se il thread del visualizzatore è in esecuzione. Tuttavia, se l'animazione è basata sul tempo trascorso tra i fotogrammi, potrebbe esserci un intervallo considerevole prima dell'arrivo dell'evento successivo. Questo problema può essere risolto utilizzando un messaggio di pausa/ripristino esplicito.

Entrambe le opzioni, indipendentemente dal fatto che la durata del thread sia legata all'attività o alla superficie, si concentrano sulla configurazione del thread del renderer e sul fatto che sia in esecuzione. Un problema correlato è l'estrazione dello stato dal thread quando l'attività viene interrotta (in onStop() o onSaveInstanceState()); in questi casi, è preferibile associare la durata del thread all'attività perché dopo l'unione del thread del renderer, è possibile accedere allo stato del thread visualizzato senza primitive di sincronizzazione.

GLSurfaceView

La classe GLSurfaceView fornisce classi di supporto per la gestione dei contesti EGL, della comunicazione tra thread e dell'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 configura un contesto EGL. Lo stato viene ripulito automaticamente quando l'attività viene messa in pausa. La maggior parte delle app non deve conoscere EGL per utilizzare GLES con GLSurfaceView.

Nella maggior parte dei casi, GLSurfaceView può semplificare il lavoro con GLES. In alcuni casi, può essere d'intralcio.