Accelerazione hardware

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 View utilizzano la GPU. A causa della maggiore quantità di risorse necessarie per abilitare l'accelerazione hardware, la tua app utilizzerà più RAM.

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

Se la tua applicazione esegue disegni personalizzati, testa l'applicazione su dispositivi hardware reali con l'accelerazione hardware attivata per individuare eventuali problemi. La sezione Supporto per le operazioni di disegno descrive i problemi noti relativi all'accelerazione hardware e le relative soluzioni.

Vedi anche OpenGL con le API Framework e Renderscript

Controllare l'accelerazione hardware

Puoi controllare l'accelerazione hardware ai seguenti livelli:

  • Candidatura
  • 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 attività

Se l'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 consente l'accelerazione hardware per l'intera applicazione, ma la disabilita per un'attività:

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

A livello di finestra

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

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);

Nota: al momento non puoi disattivare l'accelerazione hardware a livello di finestra.

Livello di vista

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

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Nota: al momento non puoi attivare l'accelerazione hardware a livello di vista. I livelli vista hanno altre funzioni oltre a disabilitare l'accelerazione hardware. Consulta la sezione Visualizza livelli per ulteriori informazioni sui loro utilizzi.

Determinare se una visualizzazione è con accelerazione hardware

A volte è utile per un'applicazione sapere se attualmente è con accelerazione 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 ha accelerazione hardware:

Se devi eseguire questo controllo nel codice del disegno, usa Canvas.isHardwareAccelerated() anziché View.isHardwareAccelerated() quando possibile. Quando una vista è collegata a una finestra con accelerazione hardware, può comunque essere tracciata utilizzando un Canvas con accelerazione non hardware. Questo accade, ad esempio, quando si disegna una vista in una bitmap per scopi di memorizzazione nella cache.

Modelli di disegno Android

Quando l'accelerazione hardware è abilitata, il framework Android utilizza un nuovo modello di disegno che impiega elenchi di visualizzazione per eseguire il rendering della tua applicazione sullo schermo. Per comprendere appieno gli elenchi di visualizzazioni e il modo in cui potrebbero influire sulla tua applicazione, è utile capire in che modo Android estrae visualizzazioni senza accelerazione hardware. Le seguenti sezioni descrivono i modelli di disegno basati su software e con accelerazione hardware.

Modello di disegno basato su software

Nel modello di disegno software, le viste vengono tracciate seguendo questi due passaggi:

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

Ogni volta che un'applicazione deve aggiornare una parte della sua UI, richiama invalidate() (o una delle sue varianti) su qualsiasi vista che abbia modificato i contenuti. I messaggi di annullamento della convalida vengono propagati fino in fondo alla gerarchia delle visualizzazioni per calcolare le aree dello schermo che devono essere ridisegnate (la regione dirty). Il sistema Android disegna qualsiasi vista nella gerarchia che si interseca con l'area dirty. 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 l'applicazione chiama invalidate() su un pulsante e questo pulsante si trova sopra un'altra vista, il sistema Android ritraccia la vista anche se non è cambiata.
  • Il secondo problema è che il modello di disegno può nascondere i bug nella tua applicazione. Poiché il sistema Android ridisegna le visualizzazioni quando incrociano la regione "dirty", una visualizzazione di cui hai modificato i contenuti potrebbe essere ridisegnata anche se l'elemento invalidate() non è stato chiamato. In questo caso, fai affidamento sull'invalidamento di un'altra vista per ottenere il comportamento corretto. Questo comportamento può cambiare ogni volta che modifichi l'applicazione. Per questo motivo, devi sempre chiamare invalidate() nelle viste personalizzate ogni volta che modifichi i dati o lo stato che influisce sul codice di disegno della vista.

Nota: le viste Android chiamano automaticamente invalidate() quando le proprietà cambiano, ad esempio il colore di sfondo o il testo in TextView.

Modello di disegno con accelerazione hardware

Il sistema Android utilizza comunque invalidate() e draw() per richiedere aggiornamenti dello schermo e visualizzare le visualizzazioni, ma gestisce il disegno effettivo in modo diverso. Anziché eseguire immediatamente i comandi di disegno, il sistema Android li registra in 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 solo gli elenchi della Rete Display per le visualizzazioni contrassegnate come "sporche" da una chiamata invalidate(). Le viste che non sono state invalidate possono essere ridisegnate semplicemente riemettendo l'elenco di visualizzazione registrato in precedenza. Il nuovo modello di disegno contiene tre fasi:

  1. Annullare la convalida della gerarchia
  2. Registrare e aggiornare gli elenchi da visualizzare
  3. Traccia gli elenchi di visualizzazioni

Con questo modello, non puoi fare affidamento su una vista che interseca la regione dirty per eseguire il metodo draw(). Per assicurarti che il sistema Android registri l'elenco di visualizzazione di una vista, devi chiamare invalidate(). Se non esegui questa operazione, una vista avrà lo stesso aspetto anche dopo essere stata modificata.

L'utilizzo degli elenchi display giova anche alle prestazioni dell'animazione perché l'impostazione di proprietà specifiche, come alpha o rotazione, non richiede la convalida della visualizzazione target (questa operazione viene eseguita automaticamente). Questa ottimizzazione si applica anche alle viste con elenchi di visualizzazione (qualsiasi vista quando la tua applicazione è con accelerazione hardware). Ad esempio, supponiamo che esista un valore LinearLayout che contiene un valore ListView superiore a un valore Button. L'elenco da visualizzare per LinearLayout ha il seguente aspetto:

  • DrawDisplayList(ListView)
  • ElencoDisegnaDisplay(Pulsante)

Supponiamo ora di voler modificare l'opacità del ListView. Dopo aver richiamato setAlpha(0.5f) su ListView, l'elenco di visualizzazione contiene ora quanto segue:

  • SalvaLivelloAlpha(0,5)
  • DrawDisplayList(ListView)
  • Ripristina
  • ElencoDisegnaDisplay(Pulsante)

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

Supporto per le operazioni di disegno

Con accelerazione hardware, la pipeline di rendering 2D supporta le operazioni di disegno Canvas più utilizzate e molte operazioni meno utilizzate. Sono supportate tutte le operazioni di disegno utilizzate per eseguire il rendering di applicazioni integrate in Android, i widget e i layout predefiniti e gli effetti visivi avanzati comuni come riflessi e texture a mosaico.

La tabella seguente descrive il livello di supporto di varie operazioni su più livelli API:

Primo livello API supportato
Canvas
disegnareBitmapMesh() (array di colori) 18
disegno() 23
disegnoPosText() 16
DrawTextOnPath() 16
disegnareVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Regione.Op.XOR) 18
clipRect(Differenza regione.Op.) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() con rotazione/prospettiva 18
Verniciatura
setAntiAlias() (per il testo) 18
setAntiAlias() (per le righe) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (per le righe) 28
setShadowlayer() (diverso dal testo) 28
setStrokeCap() (per le righe) 18
setStrokeCap() (per punti) 19
setSubpixelText() 28
Modalità Xfer
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LightEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
Ombreggiatura
ComposeShader all'interno di ComposeShader 28
Shader dello stesso tipo all'interno di ComposeShader 28
Matrice locale su ComposeShader 18

Scalabilità canvas

La pipeline di rendering 2D con accelerazione hardware è stata creata prima per supportare disegni non in scala, con alcune operazioni di disegno che riducono la qualità in modo significativo con valori di scala più elevati. Queste operazioni vengono implementate come texture disegnate su scala 1.0, trasformate dalla GPU. A partire dal livello API 28, tutte le operazioni di disegno possono scalare senza problemi.

La tabella seguente mostra quando è stata modificata l'implementazione per gestire correttamente grandi volumi:
Operazione di disegno da scalare Primo livello API supportato
DrawText() 18
disegnoPosText() 28
DrawTextOnPath() 28
Forme semplici* 17
Forme complesse* 28
(DrawPath()) 28
Livello ombra 28

Nota: le forme "semplici" sono i comandi drawRect(), drawCircle(), drawOval(), drawRoundRect() e drawArc() (con useCenter=false) inviati con un oggetto Paint che non ha un effetto PathEffect e non contiene join non predefiniti (tramite setStrokeJoin()/ setStrokeMiter()). Altre istanze di questi comandi di disegno sono riportati nella sezione "Complesso" nel grafico in alto.

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

Visualizza livelli

In tutte le versioni di Android, le visualizzazioni hanno avuto la possibilità di eseguire il rendering in buffer fuori schermo, utilizzando la cache di disegno di una vista o utilizzando Canvas.saveLayer(). I buffer, o livelli, fuori schermo hanno diversi usi. Puoi utilizzarle per ottenere prestazioni migliori quando animare viste complesse o per applicare effetti di composizione. Ad esempio, puoi implementare gli effetti di dissolvenza utilizzando Canvas.saveLayer() per eseguire temporaneamente il rendering di una visualizzazione in un livello e poi compositarla di nuovo 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 colore, modalità di fusione speciali o opacità a un livello. Una vista può utilizzare uno dei tre tipi di livelli seguenti:

  • LAYER_TYPE_NONE: la visualizzazione viene visualizzata normalmente e non è supportata da un buffer fuori schermo. Questo è il comportamento predefinito.
  • LAYER_TYPE_HARDWARE: la visualizzazione viene eseguita nell'hardware in una trama hardware se l'applicazione ha accelerazione hardware. Se l'applicazione non ha accelerazione hardware, questo tipo di livello si comporta come LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: la visualizzazione viene eseguita nel software sotto forma di bitmap.

Il tipo di livello da utilizzare dipende dall'obiettivo:

  • Prestazioni: utilizza un tipo di livello hardware per eseguire il rendering di una visualizzazione all'interno di una trama hardware. Quando una vista viene visualizzata in un livello, il codice di disegno non deve essere eseguito finché la vista non chiama invalidate(). Alcune animazioni, come quelle alfa, possono quindi essere applicate direttamente al livello, una soluzione molto efficiente per la GPU.
  • Effetti visivi: utilizza un tipo di livello hardware o software e Paint per applicare speciali trattamenti visivi a una vista. Ad esempio, puoi disegnare una vista in bianco e nero utilizzando un elemento ColorMatrixColorFilter.
  • Compatibilità: utilizza un tipo di livello software per forzare la visualizzazione di una vista nel software. Se una vista con accelerazione hardware (ad esempio, se l'intera applicazione è con 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 è con accelerazione hardware. Non sempre è possibile eseguire un'animazione a 60 frame al secondo quando si animano viste complesse che generano molte operazioni di disegno. Questo problema può essere risolto utilizzando livelli di hardware per eseguire il rendering della visualizzazione su una trama hardware. La texture hardware può quindi essere utilizzata per animare la visualizzazione, eliminando la necessità che la vista si ridisegniamo costantemente durante l'animazione. La vista non viene ridisegnata a meno che non ne modifichi le proprietà, l'operazione chiama invalidate() o la chiamata manuale a invalidate(). Se esegui un'animazione nell'applicazione e non ottieni i risultati desiderati, valuta la possibilità di attivare livelli hardware nelle visualizzazioni animate.

Quando una vista è supportata da un livello hardware, alcune delle sue proprietà vengono gestite in base al modo in cui il livello è composto sullo schermo. L'impostazione di queste proprietà sarà efficiente perché non richiedono l'annullamento della validità e la rielaborazione della vista. Il seguente elenco di proprietà influisce sul modo in cui è composto il livello. Se chiami il setter per una di queste proprietà, l'annullamento della convalida è ottimale e non verrà ridisegnato la vista scelta come target:

  • 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: cambia l'origine delle trasformazioni del livello

Queste proprietà sono i nomi utilizzati per animare una vista 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 memoria video, ti consigliamo vivamente di attivarli solo per la durata dell'animazione e di disattivarli al termine dell'animazione. A tale scopo, puoi utilizzare i listener di animazioni:

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 ulteriori informazioni sull'animazione delle proprietà, consulta la sezione Animazione della proprietà.

Suggerimenti utili

Il passaggio alla grafica 2D con accelerazione hardware può aumentare immediatamente le prestazioni, ma dovresti comunque progettare la tua applicazione per utilizzare la GPU in modo efficace seguendo questi consigli:

Ridurre il numero di visualizzazioni della tua applicazione
Più visualizzazioni ha il sistema, più lento sarà il sistema. Questo vale anche per la pipeline di rendering software. La riduzione delle visualizzazioni è uno dei modi più semplici per ottimizzare l'interfaccia utente.
Evitare il sovraccarico
Non disegnare troppi livelli uno sopra l'altro. Rimuovi tutte le viste completamente oscurate da altre viste opache che sovrastano. Se hai bisogno di 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 è non disegnare più di 2,5 volte il numero di pixel sullo schermo per frame (i pixel trasparenti in una bitmap contano!).
Non creare oggetti di rendering nei metodi di disegno
Un errore comune è creare una nuova Paint o una nuova Path ogni volta che viene richiamato un metodo di rendering. In questo modo il garbage collector viene eseguito con maggiore frequenza e bypassa le cache e le ottimizzazioni nella pipeline hardware.
Non modificare le forme troppo spesso
Ad esempio, forme, percorsi e cerchi complessi 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 i contenuti di una bitmap, questa viene caricata di nuovo come texture GPU la prossima volta che la disegna.
Usa la versione alpha con cautela
Quando rendi una visualizzazione traslucida utilizzando setAlpha(), AlphaAnimation o ObjectAnimator, viene visualizzata in un buffer fuori schermo che raddoppia il tasso di riempimento richiesto. Quando applichi alpha a viste di grandi dimensioni, valuta la possibilità di impostare il tipo di livello della vista su LAYER_TYPE_HARDWARE.