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:
View.isHardwareAcceleratedrestituiscetrueseViewè collegato a una finestra con accelerazione hardware.Canvas.isHardwareAcceleratedrestituiscetrueseCanvasè accelerato 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:
- Annullare la convalida della gerarchia
- 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
invalidatesu 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 chiamareinvalidatenelle 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:
Annullare la convalida della gerarchia
Registrare e aggiornare gli elenchi display
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)RestoreDrawDisplayList(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 comeLAYER_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
Paintper applicare trattamenti visivi speciali a una visualizzazione. Ad esempio, puoi disegnare una vista in bianco e nero utilizzando unColorMatrixColorFilter.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 livellox,y,translationX,translationY: modifica la posizione del livelloscaleX,scaleY: modifica le dimensioni del livellorotation,rotationX,rotationY: modifica l'orientamento del livello nello spazio 3DpivotX,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
Painto un nuovoPathogni 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,AlphaAnimationoObjectAnimator, 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 suLAYER_TYPE_HARDWARE.