Gerarchie relative al rendimento e alle visualizzazioni

Il modo in cui gestisci la gerarchia degli oggetti View può influire notevolmente sul rendimento della tua app. In questa pagina viene spiegato come valutare se la gerarchia delle visualizzazioni sta rallentando la tua app e offre alcune strategie per risolvere eventuali problemi.

Questa pagina è incentrata sul miglioramento dei layout basati su View. Per informazioni su come migliorare il rendimento di Jetpack Compose, consulta Rendimento di Jetpack Compose.

Progetta e misura il rendimento

La pipeline di rendering include una fase di misurazione e layout, durante la quale il sistema posiziona in modo appropriato gli elementi pertinenti nella gerarchia della visualizzazione. La parte misura di questa fase determina le dimensioni e i confini degli oggetti View. La parte layout determina dove posizionare gli oggetti View sullo schermo.

Entrambe queste fasi della pipeline comportano un piccolo costo per ogni visualizzazione o layout elaborato. Nella maggior parte dei casi, questo costo è minimo e non influisce in modo significativo sulle prestazioni. Tuttavia, i risultati possono essere migliori quando un'app aggiunge o rimuove oggetti View, ad esempio quando un oggetto RecyclerView li ricicla o li riutilizza. Il costo può essere più elevato anche se un oggetto View deve essere ridimensionato per soddisfare i relativi vincoli. Ad esempio, se la tua app chiama SetText() un oggetto View che inserisce un a capo nel testo, potrebbe essere necessario ridimensionare View.

Se casi come questi richiedono troppo tempo, possono impedire il rendering di un frame entro i 16 ms consentiti, il che può causare la perdita di frame e rendere l'animazione discontinua.

Poiché non puoi spostare queste operazioni in un thread di lavoro, l'app deve elaborarle nel thread principale, è meglio ottimizzarle in modo che richiedano il minor tempo possibile.

Gestire layout complessi

I layout Android ti consentono di nidificare gli oggetti UI nella gerarchia di visualizzazioni. Questo nidificazione può anche comportare un costo di layout. Quando l'app elabora un oggetto per il layout, esegue la stessa operazione su tutti gli elementi secondari del layout.

Per un layout complicato, a volte il costo si verifica solo la prima volta che il sistema calcola il layout. Ad esempio, quando l'app ricicla un elemento dell'elenco complesso in un oggetto RecyclerView, il sistema deve disporre tutti gli oggetti. In un altro esempio, modifiche banali possono propagare verso l'alto la catena verso l'elemento padre fino a raggiungere un oggetto che non influisce sulle dimensioni dell'elemento padre.

Un motivo comune per cui il layout richiede molto tempo è quando le gerarchie di oggetti View sono nidificate l'una nell'altra. Ogni oggetto di layout nidificato aggiunge un costo alla fase di layout. Quanto più piana è la gerarchia, meno tempo richiede il completamento della fase di layout.

Ti consigliamo di utilizzare lo editor di layout per creare un ConstraintLayout anziché RelativeLayout o LinearLayout, in quanto in genere è più efficiente e riduce il nidificazione dei layout. Tuttavia, per i layout semplici che possono essere creati con FrameLayout, consigliamo di utilizzare FrameLayout.

Se utilizzi la classe RelativeLayout, potresti ottenere lo stesso effetto a un costo inferiore utilizzando visualizzazioni LinearLayout nidificate e non ponderate. Tuttavia, se utilizzi visualizzazioni LinearLayout ponderate e nidificate, il costo del layout è molto più elevato perché richiede più passaggi di layout, come spiegato nella sezione successiva.

Ti consigliamo inoltre di utilizzare RecyclerView anziché ListView, in quanto può riutilizzare i layout dei singoli elementi dell'elenco, il che è più efficiente e può migliorare il rendimento dello scorrimento.

Doppio imposizione fiscale

In genere, il framework esegue la fase di layout o misurazione in un unico passaggio. Tuttavia, in alcuni casi di layout complicati, il framework potrebbe dover eseguire più volte l'iterazione su parti della gerarchia che richiedono più passaggi per la risoluzione prima di posizionare definitivamente gli elementi. La necessità di eseguire più di un'iterazione di layout e misurazione è definita doppia imposizione.

Ad esempio, quando utilizzi il contenitore RelativeLayout, che ti consente di posizionare gli oggetti View rispetto alle posizioni degli altri oggetti View, il framework esegue la seguente sequenza:

  1. Esegue un passaggio di layout e misurazione, durante il quale il framework calcola la posizione e la dimensione di ogni oggetto figlio, in base alla richiesta di ciascun oggetto figlio.
  2. Utilizza questi dati, tenendo conto dei pesi degli oggetti, per determinare la posizione corretta delle visualizzazioni correlate.
  3. Esegue un secondo passaggio di layout per finalizzare le posizioni degli oggetti.
  4. Passa alla fase successiva del processo di rendering.

Più livelli ha la gerarchia delle visualizzazioni, maggiore è il potenziale impatto negativo sul rendimento.

Come accennato in precedenza, ConstraintLayout è in genere più efficiente rispetto ad altri layout, ad eccezione di FrameLayout. È meno incline a più passaggi di layout e, in molti casi, elimina la necessità di nidificare i layout.

Anche i container diversi da RelativeLayout potrebbero aumentare la doppia tassazione. Per esempio:

  • Una visualizzazione LinearLayout può comportare una doppia verifica del layout e della misurazione se la imposti come orizzontale. Un doppio passaggio di layout e misurazione può verificarsi anche in un orientamento verticale se aggiunti measureWithLargestChild, nel qual caso il framework potrebbe dover eseguire un secondo passaggio per risolvere le dimensioni appropriate degli oggetti.
  • GridLayout consente anche il posizionamento relativo, ma normalmente evita la doppia tassazione mediante la pre-elaborazione delle relazioni di posizionamento tra le viste figlio. Tuttavia, se il layout utilizza pesi o riempimento con la classe Gravity, il vantaggio della preelaborazione viene perso e il framework potrebbe dover eseguire più passaggi se il contenitore è un RelativeLayout.

Più passaggi di layout e misurazione non rappresentano necessariamente un onere in termini di rendimento. Tuttavia, possono diventare un peso se non sono posizionati nel posto giusto. Fai attenzione alle situazioni in cui si applica una delle seguenti condizioni al tuo contenitore:

  • È un elemento radice della gerarchia delle visualizzazioni.
  • Sotto è presente una gerarchia di visualizzazione approfondita.
  • Esistono molte istanze che riempiono lo schermo, in modo simile agli elementi secondari in un oggetto ListView.

Diagnosticare i problemi relativi alla gerarchia delle visualizzazioni

Le prestazioni del layout sono un problema complesso con molti facet. I seguenti strumenti possono aiutarti a identificare i colli di bottiglia delle prestazioni. Alcuni strumenti forniscono informazioni meno definitive, ma possono fornire suggerimenti utili.

Perfetto

Perfetto è uno strumento che fornisce dati sulle prestazioni. Puoi aprire le tracce Android nell'interfaccia utente di Perfetto.

Traccia prof. render. GPU

Lo strumento sul dispositivo Rendering GPU Profile, disponibile su dispositivi con Android 6.0 (livello API 23) e versioni successive, può fornirti informazioni concrete sui colli di bottiglia delle prestazioni. Questo strumento ti consente di vedere il tempo necessario per la fase di misurazione e layout per ogni frame di rendering. Questi dati possono aiutarti a diagnosticare problemi di prestazioni di runtime e a determinare quali problemi di layout e misurazione devi risolvere.

Nella rappresentazione grafica dei dati acquisiti, il rendering della GPU del profilo utilizza il colore blu per rappresentare il tempo di layout. Per maggiori informazioni su come utilizzare questo strumento, consulta Velocità di rendering della GPU del profilo.

Lint

Lo strumento Lint di Android Studio può aiutarti a capire le inefficienze nella gerarchia delle visualizzazioni. Per utilizzare questo strumento, seleziona Analizza > Ispeziona codice, come mostrato nella Figura 1.

Figura 1. Seleziona Esamina codice in Android Studio.

Le informazioni su vari elementi di layout vengono visualizzate in Android > Lint > Rendimento. Per visualizzare ulteriori dettagli, fai clic su ogni elemento per espanderlo e visualizzare ulteriori informazioni nel riquadro sul lato destro dello schermo. La Figura 2 mostra un esempio di informazioni espanse.

Figura 2. Visualizzazione di informazioni su problemi specifici identificati dallo strumento Lint.

Se fai clic su un elemento, nel riquadro a destra vengono visualizzati i problemi associati.

Per saperne di più su argomenti e problemi specifici in quest'area, consulta la documentazione su Lint.

Layout Inspector

Lo strumento Layout Inspector di Android Studio fornisce una rappresentazione visiva della gerarchia delle visualizzazioni della tua app. È un buon modo per navigare nella gerarchia della tua app, fornendo una rappresentazione visiva chiara della catena principale di una determinata visualizzazione e consente di ispezionare i layout costruiti dall'app.

Le viste presentate da Layout Inspector possono anche essere utili per identificare problemi di rendimento derivanti dalla doppia imposizione. Inoltre, può aiutarti a identificare catene profonde di layout nidificati o aree di layout con una grande quantità di elementi secondari nidificati, che possono rappresentare una fonte di costi per le prestazioni. In questi casi, le fasi di layout e misurazione possono essere costose e causare problemi di prestazioni.

Per ulteriori informazioni, consulta Eseguire il debug del layout con Layout Inspector e la convalida del layout.

Risolvere i problemi relativi alla gerarchia di visualizzazione

Il concetto fondamentale alla base della risoluzione dei problemi di prestazioni derivanti dalle gerarchie di visualizzazioni può essere difficile da applicare nella pratica. Per evitare che le gerarchie di visualizzazione impongano penalizzazioni sul rendimento, è necessario appianare la gerarchia di visualizzazione e ridurre la doppia tassazione. Questa sezione illustra le strategie per perseguire questi obiettivi.

Rimuovi i layout nidificati ridondanti

ConstraintLayout è una libreria Jetpack con un gran numero di meccanismi diversi per il posizionamento delle visualizzazioni all'interno del layout. In questo modo, non è necessario nidificare un ConstaintLayout e puoi appianare la gerarchia della visualizzazione. In genere è più semplice appianare le gerarchie utilizzando ConstraintLayout rispetto ad altri tipi di layout.

Gli sviluppatori spesso usano layout nidificati più del necessario. Ad esempio, un RelativeLayout container potrebbe contenere un singolo elemento secondario che è anche un RelativeLayout container. Questo nidificazione è ridondante e aggiunge costi non necessari alla gerarchia delle visualizzazioni. Lint può segnalare questo problema, riducendo i tempi di debug.

Applica l'unione o l'inclusione

Una causa frequente di layout nidificati ridondanti è il <include> tag. Ad esempio, puoi definire un layout riutilizzabile come segue:

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

Potresti quindi aggiungere un tag <include> per aggiungere il seguente elemento al contenitore principale:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

L'inclusione precedente nidifica inutilmente il primo layout all'interno del secondo layout.

Il tag <merge> può aiutarti a evitare questo problema. Per informazioni su questo tag, consulta Utilizzare il tag <merge>.

Adotta un layout più economico

Potresti non essere in grado di modificare lo schema di layout esistente in modo che non contenga layout ridondanti. In alcuni casi, l'unica soluzione potrebbe essere la suddivisione della gerarchia passando a un tipo di layout completamente diverso.

Ad esempio, potresti scoprire che TableLayout offre la stessa funzionalità di un layout più complesso con molte dipendenze posizionali. La libreria Jetpack ConstraintLayout offre funzionalità simili a RelativeLayout, oltre ad altre funzionalità per aiutarti a creare layout più piatti ed efficienti.