Accelerazione hardware (visualizzazioni)

Concetti e implementazione di Jetpack Compose

A partire da Android 3.0 (livello API 11), la pipeline di rendering 2D di Android supporta l'accelerazione hardware, il che significa che tutte le operazioni di disegno eseguite sul canvas di un View utilizzano la GPU. A causa delle maggiori risorse necessarie per attivare l'accelerazione hardware, la tua app consumerà più RAM.

L'accelerazione hardware è abilitata per impostazione predefinita se il livello API target è >=14, ma può anche essere abilitata esplicitamente. Se la tua applicazione utilizza solo viste standard e Drawable, l'attivazione a livello globale non dovrebbe causare effetti di disegno negativi. Tuttavia, poiché l'accelerazione hardware non è supportata per tutte le operazioni di disegno 2D, l'attivazione potrebbe influire su alcune delle tue visualizzazioni personalizzate o chiamate di disegno. I problemi di solito si manifestano come elementi invisibili, eccezioni o pixel visualizzati in modo errato. Per risolvere questo problema, Android ti offre la possibilità di attivare o disattivare l'accelerazione hardware a più livelli. Vedi Controllare l'accelerazione hardware.

Se la tua applicazione esegue disegni personalizzati, testala su dispositivi hardware reali con l'accelerazione hardware attivata per rilevare eventuali problemi. La sezione Supporto delle operazioni di disegno descrive i problemi noti relativi all'accelerazione hardware e come risolverli.

Consulta anche OpenGL con le API Framework e Renderscript.

Controllare l'accelerazione hardware

Puoi controllare l'accelerazione hardware ai seguenti livelli:

  • Applicazione
  • Attività
  • Finestra
  • Visualizza

Livello di applicazione

Nel file manifest Android, aggiungi il seguente attributo al tag <application> per attivare l'accelerazione hardware per l'intera applicazione:

<application android:hardwareAccelerated="true" ...>

Livello di attività

Se la tua applicazione non si comporta correttamente con l'accelerazione hardware attivata a livello globale, puoi controllarla anche per le singole attività. Per attivare o disattivare l'accelerazione hardware a livello di attività, puoi utilizzare l'attributo android:hardwareAccelerated per l'elemento <activity>. L'esempio seguente abilita l'accelerazione hardware per l'intera applicazione, ma la disabilita per un'attività:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Livello della finestra

Se hai bisogno di un controllo ancora più granulare, puoi attivare l'accelerazione hardware per una determinata finestra con il seguente codice:

Kotlin

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Java

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Livello di visualizzazione

Puoi disattivare l'accelerazione hardware per una singola visualizzazione in fase di runtime con il seguente codice:

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Determinare se una visualizzazione è accelerata dall'hardware

A volte è utile che un'applicazione sappia se è attualmente accelerata dall'hardware, soprattutto per elementi come le visualizzazioni personalizzate. Ciò è particolarmente utile se la tua applicazione esegue molti disegni personalizzati e non tutte le operazioni sono supportate correttamente dalla nuova pipeline di rendering.

Esistono due modi diversi per verificare se l'applicazione è accelerata dall'hardware:

Se devi eseguire questo controllo nel codice di disegno, utilizza Canvas.isHardwareAccelerated anziché View.isHardwareAccelerated, se possibile. Quando una visualizzazione è collegata a una finestra con accelerazione hardware, può comunque essere disegnata utilizzando una tela non con accelerazione hardware. Ciò accade, ad esempio, quando si disegna una visualizzazione in una bitmap a scopo di memorizzazione nella cache.

Modelli di disegno Android

Quando l'accelerazione hardware è attivata, il framework Android utilizza un nuovo modello di disegno che utilizza le liste di visualizzazione per eseguire il rendering dell'applicazione sullo schermo. Per comprendere appieno gli elenchi di visualizzazione e il modo in cui potrebbero influire sulla tua applicazione, è utile capire anche come Android disegna le visualizzazioni senza accelerazione hardware. Le sezioni seguenti descrivono i modelli di disegno basati su software e accelerati dall'hardware.

Modello di disegno basato su software

Nel modello di disegno del software, le viste vengono disegnate con i due passaggi seguenti:

  1. Annullare la convalida della gerarchia
  2. Disegnare la gerarchia

Ogni volta che un'app deve aggiornare una parte della sua UI, richiama invalidate() (o una delle sue varianti) su qualsiasi visualizzazione il cui contenuto è cambiato. I messaggi di invalidazione vengono propagati fino alla gerarchia della visualizzazione per calcolare le regioni dello schermo che devono essere ridisegnate (la regione sporca). Il sistema Android disegna quindi qualsiasi visualizzazione nella gerarchia che interseca la regione modificata. Purtroppo, questo modello di disegno presenta due svantaggi:

  • Innanzitutto, questo modello richiede l'esecuzione di molto codice a ogni passaggio di disegno. Ad esempio, se la tua applicazione chiama invalidate su un pulsante e questo pulsante si trova sopra un'altra visualizzazione, il sistema Android ridisegna la visualizzazione anche se non è cambiata.

  • Il secondo problema è che il modello di disegno può nascondere bug nell'applicazione. Poiché il sistema Android ridisegna le visualizzazioni quando intersecano la regione sporca, una visualizzazione il cui contenuto è stato modificato potrebbe essere ridisegnata anche se non è stato chiamato invalidate. In questo caso, fai affidamento sull'invalidazione di un'altra visualizzazione per ottenere il comportamento corretto. Questo comportamento può cambiare ogni volta che modifichi l'applicazione. Per questo motivo, devi sempre chiamare invalidate nelle tue visualizzazioni personalizzate ogni volta che modifichi i dati o lo stato che influisce sul codice di disegno della visualizzazione.

Modello di disegno con accelerazione hardware

Il sistema Android utilizza ancora invalidate e draw per richiedere aggiornamenti dello schermo e per eseguire il rendering delle visualizzazioni, ma gestisce il disegno effettivo in modo diverso. Anziché eseguire immediatamente i comandi di disegno, il sistema Android li registra all'interno di elenchi di visualizzazione, che contengono l'output del codice di disegno della gerarchia delle visualizzazioni. Un'altra ottimizzazione è che il sistema Android deve registrare e aggiornare gli elenchi di visualizzazione solo per le visualizzazioni contrassegnate come modificate da una chiamata invalidate. Le visualizzazioni non invalidate possono essere ridisegnate riemettendo l'elenco visualizzazioni registrato in precedenza. Il nuovo modello di disegno prevede tre fasi:

  1. Annullare la convalida della gerarchia

  2. Registrare e aggiornare gli elenchi display

  3. Disegna gli elenchi visualizzati

Con questo modello, non puoi fare affidamento su una visualizzazione che interseca la regione modificata per l'esecuzione del metodo draw. Per assicurarti che il sistema Android registri un elenco di visualizzazione di una visualizzazione, devi chiamare invalidate. Se non lo fai, una visualizzazione avrà lo stesso aspetto anche dopo la modifica.

L'utilizzo di elenchi di visualizzazione migliora anche le prestazioni delle animazioni perché l'impostazione di proprietà specifiche, come alpha o rotazione, non richiede l'invalidazione della visualizzazione di destinazione (viene eseguita automaticamente). Questa ottimizzazione si applica anche alle visualizzazioni con elenchi di visualizzazione (qualsiasi visualizzazione quando l'applicazione è accelerata dall'hardware). Ad esempio, supponiamo che esista un LinearLayout che contiene un ListView sopra un Button. L'elenco di visualizzazione per LinearLayout ha questo aspetto:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

Supponiamo ora di voler modificare l'opacità di ListView. Dopo aver richiamato setAlpha(0.5f) su ListView, l'elenco dei display ora contiene questo:

  • SaveLayerAlpha(0.5)

  • DrawDisplayList(ListView)

  • Restore

  • DrawDisplayList(Button)

Il codice di disegno complesso di ListView non è stato eseguito. Il sistema ha invece aggiornato solo l'elenco di visualizzazione del molto più semplice LinearLayout. In un'applicazione senza accelerazione hardware abilitata, il codice di disegno sia dell'elenco che del relativo elemento principale viene eseguito di nuovo.

Supporto per le operazioni di disegno

Quando viene accelerata dall'hardware, la pipeline di rendering 2D supporta le operazioni di disegno Canvas più comunemente utilizzate, nonché molte operazioni meno utilizzate. Sono supportate tutte le operazioni di disegno utilizzate per il rendering delle applicazioni fornite con Android, dei widget e dei layout predefiniti e degli effetti visivi avanzati comuni, come riflessi e texture affiancate.

La tabella seguente descrive il livello di supporto di varie operazioni nei diversi livelli API:

Primo livello API supportato
Canvas
drawBitmapMesh() (array di colori) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() con rotazione/prospettiva 18
Pittura
setAntiAlias() (per il testo) 18
setAntiAlias() (per le linee) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (per le linee) 28
setShadowLayer() (diverso dal testo) 28
setStrokeCap() (per le linee) 18
setStrokeCap() (per i punti) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LIGHTEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
Shader
ComposeShader all'interno di ComposeShader 28
Shader dello stesso tipo all'interno di ComposeShader 28
Matrice locale su ComposeShader 18

Scalabilità del canvas

La pipeline di rendering 2D con accelerazione hardware è stata creata per supportare il disegno non scalato, con alcune operazioni di disegno che peggiorano notevolmente la qualità a valori di scala più elevati. Queste operazioni vengono implementate come texture disegnate in scala 1.0, trasformate dalla GPU. A partire dal livello API 28, tutte le operazioni di disegno possono essere scalate senza problemi.

La tabella seguente mostra quando l'implementazione è stata modificata per gestire correttamente le grandi scale:

Operazione di disegno da scalare Primo livello API supportato
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Forme semplici 17
Forme complesse 28
drawPath() 28
Livello di ombreggiatura 28

Se la tua applicazione è interessata da una di queste funzionalità mancanti o limitazioni, puoi disattivare l'accelerazione hardware solo per la parte interessata della tua applicazione chiamando setLayerType(View.LAYER_TYPE_SOFTWARE, null). In questo modo, puoi comunque usufruire dell'accelerazione hardware altrove. Per ulteriori informazioni su come attivare e disattivare l'accelerazione hardware a diversi livelli nell'applicazione, consulta Controllare l'accelerazione hardware.

Visualizzare i livelli

In tutte le versioni di Android, le visualizzazioni hanno la possibilità di eseguire il rendering in buffer off-screen, utilizzando la cache di disegno di una visualizzazione o utilizzando Canvas.saveLayer. I buffer o i livelli fuori schermo hanno diversi utilizzi. Puoi utilizzarli per ottenere prestazioni migliori quando animi viste complesse o per applicare effetti di composizione. Ad esempio, puoi implementare effetti di dissolvenza utilizzando Canvas.saveLayer per eseguire temporaneamente il rendering di una visualizzazione in un livello e poi ricomporla sullo schermo con un fattore di opacità.

A partire da Android 3.0 (livello API 11), hai un maggiore controllo su come e quando utilizzare i livelli con il metodo View.setLayerType. Questa API accetta due parametri: il tipo di livello che vuoi utilizzare e un oggetto Paint facoltativo che descrive come deve essere composto il livello. Puoi utilizzare il parametro Paint per applicare filtri di colore, modalità di fusione speciali o opacità a un livello. Una visualizzazione può utilizzare uno dei tre tipi di livello:

  • LAYER_TYPE_NONE: la visualizzazione viene visualizzata normalmente e non è supportata da un buffer off-screen. Questo è il comportamento predefinito.

  • LAYER_TYPE_HARDWARE: la visualizzazione viene eseguita nell'hardware in una texture hardware se l'applicazione è con accelerazione hardware. Se l'applicazione non è accelerata dall'hardware, questo tipo di livello si comporta come LAYER_TYPE_SOFTWARE.

  • LAYER_TYPE_SOFTWARE: la visualizzazione viene visualizzata nel software in una bitmap.

Il tipo di livello che utilizzi dipende dal tuo obiettivo:

  • Rendimento: utilizza un tipo di livello hardware per eseguire il rendering di una visualizzazione in una texture hardware. Una volta eseguito il rendering di una visualizzazione in un livello, il relativo codice di disegno non deve essere eseguito finché la visualizzazione non chiama invalidate. Alcune animazioni, come le animazioni alfa, possono essere applicate direttamente al livello, il che è molto efficiente per la GPU.

  • Effetti visivi: utilizza un tipo di livello hardware o software e un Paint per applicare trattamenti visivi speciali a una visualizzazione. Ad esempio, puoi disegnare una vista in bianco e nero utilizzando un ColorMatrixColorFilter.

  • Compatibilità: utilizza un tipo di livello software per forzare il rendering di una visualizzazione nel software. Se una visualizzazione con accelerazione hardware (ad esempio, se l'intera applicazione ha accelerazione hardware) presenta problemi di rendering, questo è un modo semplice per aggirare le limitazioni della pipeline di rendering hardware.

Visualizzare livelli e animazioni

I livelli hardware possono offrire animazioni più veloci e fluide quando l'applicazione è accelerata dall'hardware. L'esecuzione di un'animazione a 60 fotogrammi al secondo non è sempre possibile quando si animano viste complesse che eseguono molte operazioni di disegno. Questo problema può essere risolto utilizzando i livelli hardware per eseguire il rendering della visualizzazione in una texture hardware. La texture hardware può quindi essere utilizzata per animare la visualizzazione, eliminando la necessità che la visualizzazione si ridisegni costantemente quando viene animata. La visualizzazione non viene ridisegnata a meno che tu non modifichi le proprietà della visualizzazione, che chiama invalidate, o se chiami invalidate manualmente. Se esegui un'animazione nella tua applicazione e non ottieni i risultati che desideri, valuta la possibilità di attivare i livelli hardware nelle visualizzazioni animate.

Quando una visualizzazione è supportata da un livello hardware, alcune delle sue proprietà vengono gestite dal modo in cui il livello viene composto sullo schermo. L'impostazione di queste proprietà sarà efficiente perché non richiede l'invalidazione e il ridisegno della visualizzazione. Di seguito è riportato un elenco di proprietà che influiscono sul modo in cui il livello viene composto. La chiamata al setter per una qualsiasi di queste proprietà comporta un'invalidazione ottimale e nessun ridisegno della visualizzazione di destinazione:

  • alpha: modifica l'opacità del livello

  • x, y, translationX, translationY: modifica la posizione del livello

  • scaleX, scaleY: modifica le dimensioni del livello

  • rotation, rotationX, rotationY: modifica l'orientamento del livello nello spazio 3D

  • pivotX, pivotY: modifica l'origine delle trasformazioni del livello

Queste proprietà sono i nomi utilizzati per animare una visualizzazione con un ObjectAnimator. Se vuoi accedere a queste proprietà, chiama il setter o il getter appropriato. Ad esempio, per modificare la proprietà alpha, chiama setAlpha. Il seguente snippet di codice mostra il modo più efficiente per ruotare una visualizzazione in 3D attorno all'asse Y:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

Poiché i livelli hardware consumano la memoria video, ti consigliamo vivamente di attivarli solo per la durata dell'animazione e di disattivarli al termine dell'animazione. Puoi farlo utilizzando i listener di animazione:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

Per saperne di più sull'animazione delle proprietà, consulta Animazione delle proprietà.

Suggerimenti utili

Il passaggio alla grafica 2D con accelerazione hardware può aumentare immediatamente le prestazioni, ma devi comunque progettare l'applicazione in modo che utilizzi la GPU in modo efficace seguendo questi consigli:

Ridurre il numero di visualizzazioni nell'applicazione
Più visualizzazioni deve disegnare il sistema, più lento sarà. Questo vale anche per la pipeline di rendering software. Ridurre le visualizzazioni è uno dei modi più semplici per ottimizzare la tua UI.
Evitare lo scoperto di conto
Non disegnare troppi livelli uno sopra l'altro. Rimuovi tutte le visualizzazioni completamente oscurate da altre visualizzazioni opache sopra. Se devi disegnare più livelli miscelati uno sopra l'altro, valuta la possibilità di unirli in un unico livello. Una buona regola generale con l'hardware attuale è di non disegnare più di 2,5 volte il numero di pixel sullo schermo per fotogramma (vengono conteggiati anche i pixel trasparenti in una bitmap).
Non creare oggetti di rendering nei metodi di disegno
Un errore comune è creare un nuovo Paint o un nuovo Path ogni volta che viene richiamato un metodo di rendering. In questo modo, il garbage collector viene eseguito più spesso e vengono ignorate anche le cache e le ottimizzazioni nella pipeline hardware.
Non modificare le forme troppo spesso
Forme, tracciati e cerchi complessi, ad esempio, vengono visualizzati utilizzando maschere di texture. Ogni volta che crei o modifichi un percorso, la pipeline hardware crea una nuova maschera, che può essere costosa.
Non modificare troppo spesso le bitmap
Ogni volta che modifichi il contenuto di una bitmap, questa viene caricata di nuovo come texture GPU la volta successiva che la disegni.
Utilizzare la versione alpha con attenzione
Quando rendi una visualizzazione traslucida utilizzando setAlpha, AlphaAnimation o ObjectAnimator, viene eseguito il rendering in un buffer off-screen che raddoppia la velocità di riempimento richiesta. Quando applichi il canale alfa a visualizzazioni molto grandi, valuta la possibilità di impostare il tipo di livello della visualizzazione su LAYER_TYPE_HARDWARE.