La UI del framework dell'app per Android si basa su una gerarchia di oggetti che inizia con una 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, il framework esegue il rendering di tutti gli oggetti visibili su una superficie configurata da WindowManager quando l'app è stata portata in primo piano. Il thread UI dell'app esegue il layout e il rendering in un buffer per frame.
SurfaceView
Una 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, ad esempio un contesto GL o un decoder multimediale, devi copiare i buffer dall'origine buffer per visualizzarli sullo schermo. L'utilizzo di una 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 a SurfaceFlinger. Per ricevere callback quando la superficie viene creata o eliminata, utilizza l'interfaccia SurfaceHolder. Per impostazione predefinita, il framework posiziona la superficie appena creata dietro la superficie dell'interfaccia utente dell'app. Puoi ignorare l'ordinamento Z predefinito per posizionare la nuova superficie in alto.
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 una SurfaceView, devi comporre i buffer in una superficie off-screen, che viene poi composta sullo schermo, quindi il rendering con SurfaceView elimina il lavoro aggiuntivo. Dopo il rendering con SurfaceView, utilizza il thread UI per coordinarti con il ciclo di vita dell'attività e apportare modifiche alle dimensioni o alla posizione della visualizzazione, se necessario. Poi, Hardware Composer combina la UI dell'app e gli altri livelli.
La nuova superficie è il lato 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, collegando una EGLSurface e disegnando sulla superficie con GLES o configurando un decoder multimediale per scrivere sulla superficie.
SurfaceView e il ciclo di vita dell'attività
Quando utilizzi una SurfaceView, esegui il rendering della superficie da un thread diverso da quello dell'interfaccia utente principale.
Per un'attività con una 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:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Se fai clic su Indietro, visualizzi:
onPause()
surfaceDestroyed()
(chiamata poco prima che la superficie scompaia)
Se ruoti lo schermo, l'attività viene interrotta 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()
si verifica dopo onPause()
.
Se tocchi il tasto di accensione per oscurare lo schermo, visualizzi solo
onPause()
senza surfaceDestroyed()
. La superficie
rimane attiva e il rendering può continuare. Puoi continuare a ricevere
eventi di 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 riattivato. In caso contrario, puoi uscire dalla modalità di spegnimento dello schermo
con la stessa superficie di prima.
La durata del thread può essere associata alla superficie o all'attività, a seconda di cosa vuoi che accada quando lo schermo diventa vuoto. Il thread può iniziare/terminare all'avvio/all'arresto dell'attività o alla creazione/eliminazione della superficie.
L'avvio/l'interruzione del thread all'avvio/all'interruzione dell'attività funziona bene con il ciclo di vita dell'app. Avvii il thread del renderer in onResume()
e lo interrompi
in onStop()
. Quando crei e configuri il thread,
a volte la superficie esiste già, altre volte no (ad esempio,
è ancora attiva dopo aver attivato/disattivato lo schermo con il tasto di accensione). Devi
attendere la creazione della superficie prima di inizializzarla nel thread. Non puoi
inizializzare 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 inoltralo al thread del renderer.
L'avvio/l'arresto del thread alla creazione/eliminazione della superficie funziona bene perché
la superficie e il renderer sono logicamente interconnessi. Avvii il thread
dopo la creazione della superficie, il che evita alcuni problemi di comunicazione tra thread; e i messaggi di creazione/modifica della superficie vengono inoltrati. Per verificare che
il rendering si interrompa quando lo schermo diventa vuoto e riprenda quando si riattiva, comunica
a Choreographer di interrompere la chiamata di callback di disegno del frame. onResume()
riprende i callback se il thread del renderer è in esecuzione. Tuttavia, se
l'animazione si basa sul tempo trascorso tra i frame, potrebbe esserci un intervallo di tempo elevato prima
dell'arrivo dell'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 viene configurato il thread del renderer e se è in esecuzione. Un problema correlato è l'estrazione dello stato
dal thread quando l'attività viene interrotta (in onStop()
o
onSaveInstanceState()
); in questi casi, il collegamento della durata del
thread all'attività funziona meglio perché
dopo l'unione del thread del renderer, è 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 interthread 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 configura un contesto EGL. Lo stato viene pulito 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 l'utilizzo di GLES. In alcune situazioni, può essere d'intralcio.