Gerarchie relative al rendimento e alle visualizzazioni

Il modo in cui gestisci la gerarchia degli oggetti View può influire notevolmente sulle prestazioni della tua app. Questa pagina descrive come valutare se la gerarchia delle visualizzazioni sta rallentando la tua app e offre alcune strategie per risolvere i problemi che potrebbero sorgere.

Questa pagina è incentrata sul miglioramento dei layout basati su View. Per informazioni su come migliorare le prestazioni di Jetpack Compose, consulta Prestazioni di Jetpack Compose.

Rendimento di layout e misurazione

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

Entrambe queste fasi della pipeline comportano un piccolo costo per visualizzazione o layout elaborato. Nella maggior parte dei casi, si tratta di un costo minimo che non influisce in modo significativo sulle prestazioni. Tuttavia, può essere maggiore quando un'app aggiunge o rimuove oggetti View, ad esempio quando un oggetto RecyclerView li ricicla o li riutilizza. Il costo può anche essere superiore se un oggetto View deve valutare il ridimensionamento per soddisfare i suoi vincoli. Ad esempio, se la tua app chiama SetText() su un oggetto View che manda a capo il testo, potrebbe essere necessario ridimensionare il View.

Casi come questi che richiedono troppo tempo possono impedire il rendering di un frame entro i 16 ms consentiti, il che può causare l'abbandono dei frame e rendere l'animazione incompleta.

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

Gestisci layout complessi

I Layout di Android consentono di nidificare gli oggetti dell'interfaccia utente nella gerarchia delle viste. Questa nidificazione può imporre un costo per il layout. Quando l'app elabora un oggetto per il layout, l'app esegue lo stesso processo anche su tutti gli elementi secondari del layout.

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

Spesso il layout richiede molto tempo quando le gerarchie di oggetti View sono nidificate l'una nell'altra. Ogni oggetto di layout nidificato comporta un aumento del costo per lo stage del layout. Più la gerarchia è piatta, minore è il tempo necessario per completare la fase di layout.

Ti consigliamo di utilizzare l'editor di layout per creare una ConstraintLayout, anziché RelativeLayout o LinearLayout, poiché in genere è più efficiente e riduce la nidificazione dei layout. Tuttavia, per layout semplici ottenibili con FrameLayout, ti consigliamo di utilizzare FrameLayout.

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

Ti consigliamo inoltre di utilizzare RecyclerView anziché ListView, poiché consente di riciclare i layout di singoli elementi dell'elenco, una soluzione più efficiente che migliora le prestazioni di scorrimento.

Doppia imposizione

In genere, il framework esegue la fase del layout o della misurazione in un unico passaggio. Tuttavia, in alcuni casi di layout complessi, il framework potrebbe dover ripetere più volte l'iterazione in parti della gerarchia che richiedono più passaggi per essere risolti prima di posizionare gli elementi. Dovere eseguire più di un'iterazione di layout e misura è definito doppia imposizione.

Ad esempio, quando utilizzi il contenitore RelativeLayout, che consente di posizionare gli oggetti View rispetto alla posizione di altri oggetti View, il framework esegue la seguente sequenza:

  1. Esegue un passaggio di layout e misura, durante il quale il framework calcola la posizione e le dimensioni di ogni oggetto figlio, in base alla richiesta di ogni oggetto secondario.
  2. Utilizza questi dati, prendendo in considerazione i pesi degli oggetti, per determinare la posizione corretta delle viste correlate.
  3. Esegue una seconda trasmissione del layout per finalizzare le posizioni degli oggetti.
  4. Passa alla fase successiva del processo di rendering.

Maggiore è il numero di livelli della gerarchia delle viste, maggiore è il potenziale di peggioramento delle prestazioni.

Come accennato in precedenza, ConstraintLayout è generalmente più efficiente di altri layout tranne FrameLayout. È meno soggetto a più pass di layout e, in molti casi, elimina la necessità di nidificare i layout.

Anche i container diversi da RelativeLayout potrebbero aumentare la doppia imposizione. Ad esempio:

  • Una visualizzazione LinearLayout può comportare un doppio passaggio di layout e misurazione se la imposti in orizzontale. Un doppio passaggio per layout e misurazione potrebbe verificarsi anche in orientamento verticale se aggiungi measureWithLargestChild, nel qual caso il framework potrebbe dover eseguire una seconda passaggio per risolvere le dimensioni corrette degli oggetti.
  • GridLayout consente anche il posizionamento relativo, ma di solito evita la doppia tassazione in quanto pre-elabora le relazioni di posizionamento tra le viste secondarie. Tuttavia, se il layout utilizza ponderazioni o riempimento con la classe Gravity, il vantaggio della pre-elaborazione andrà perso e il framework potrebbe dover eseguire più passaggi se il container è un RelativeLayout.

Più pass di layout e misurazione non rappresentano necessariamente un peso in termini di prestazioni. Tuttavia, possono diventare un peso se si trovano nel posto sbagliato. Presta attenzione alle situazioni in cui al contenitore si applica una delle seguenti condizioni:

  • È un elemento principale nella gerarchia delle visualizzazioni.
  • Ha una gerarchia di visualizzazione approfondita alla base.
  • Esistono molte istanze in cui si completa la schermata, in modo simile agli elementi secondari in un oggetto ListView.

Diagnosticare i problemi relativi alla gerarchia delle visualizzazioni

Il rendimento del layout è 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 di Android nell'UI di Perfetto.

Rendering GPU

Lo strumento di rendering della GPU del profilo on-device, disponibile sui 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 quanto tempo impiega la fase di layout e misurazione per ogni frame del rendering. Questi dati possono aiutarti a diagnosticare i problemi di prestazioni di runtime e a determinare quali problemi di layout e misurazione devi risolvere.

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

Pelucchi

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

Figura 1. Seleziona Ispeziona codice in Android Studio.

Le informazioni sui vari elementi del layout vengono visualizzate in Android > Lint > Prestazioni. Per visualizzare ulteriori dettagli, fai clic su ciascun 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, vengono visualizzati i problemi associati all'elemento nel riquadro a destra.

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

Controllo layout

Lo strumento Controllo layout di Android Studio fornisce una rappresentazione visiva della gerarchia delle viste della tua app. È un buon modo per esplorare la gerarchia della tua app, fornendo una chiara rappresentazione visiva della catena principale di una determinata vista e ti consente di esaminare i layout costruiti dalla tua app.

Le viste presentate da Layout Inspector possono anche aiutare a identificare i problemi di prestazioni derivanti dalla doppia tassazione. Può anche fornire un modo per identificare catene profonde di layout nidificati o aree di layout con un numero elevato di elementi secondari nidificati, il che può essere fonte di costi in termini di prestazioni. In questi casi, le fasi di layout e misurazione possono essere costose e causare problemi di prestazioni.

Per ulteriori informazioni, consulta la sezione Eseguire il debug del layout con Controllo layout e Convalida del layout.

Risolvere i problemi relativi alla gerarchia delle visualizzazioni

Il concetto fondamentale alla base della risoluzione dei problemi di prestazioni che derivano dalle gerarchie di viste può essere difficile nella pratica. Impedire alle gerarchie di viste di imporre sanzioni relative alle prestazioni consiste nell'appiattire la gerarchia delle visualizzazioni e ridurre la doppia imposizione. Questa sezione illustra le strategie per perseguire questi obiettivi.

Rimuovi layout nidificati ridondanti

ConstraintLayout è una libreria Jetpack con un gran numero di meccanismi diversi per posizionare le viste all'interno del layout. In questo modo si riduce la necessità di nidificare un ConstaintLayout e si può contribuire ad appiattire la gerarchia delle visualizzazioni. In genere è più semplice suddividere le gerarchie utilizzando ConstraintLayout rispetto ad altri tipi di layout.

Gli sviluppatori spesso utilizzano layout più nidificati del necessario. Ad esempio, un container RelativeLayout potrebbe contenere un singolo elemento figlio che è anche un container RelativeLayout. Questa nidificazione è ridondante e aggiunge costi inutili alla gerarchia delle visualizzazioni. Lint può segnalare questo problema per te, riducendo i tempi di debug.

Adotta l'unione o l'inclusione

Una causa frequente dei layout nidificati ridondanti è il tag <include>. Ad esempio, potresti 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.

Il tag <merge> può aiutare a prevenire 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 appiattire la gerarchia passando a un tipo di layout completamente diverso.

Ad esempio, potresti scoprire che TableLayout offre le stesse funzionalità di un layout più complesso con molte dipendenze di posizionamento. La libreria Jetpack ConstraintLayout fornisce funzionalità simili a RelativeLayout, oltre ad altre funzionalità per contribuire a creare layout più piatti ed efficienti.