Panoramica della misurazione del rendimento dell'app

Questo documento ti aiuta a identificare e risolvere i principali problemi di prestazioni della tua app.

Principali problemi di rendimento

Sono molti i problemi che possono contribuire a prestazioni scadenti in un'app, ma di seguito sono riportati alcuni problemi comuni da cercare nella tua app:

Latenza di avvio

La latenza di avvio è il tempo che intercorre tra il tocco dell'icona dell'app, della notifica o di un altro punto di accesso e la visualizzazione dei dati dell'utente sullo schermo.

Punta ai seguenti obiettivi di avvio nelle tue app:

  • Avvio a freddo in meno di 500 ms. Un avvio completo si verifica quando l'app in fase di avvio non è presente nella memoria del sistema. Ciò accade quando l'app viene avviata per la prima volta dopo il riavvio o dopo l'interruzione del processo dell'app <x0A>da parte dell'utente o del sistema.

    Al contrario, un avvio caldo si verifica quando l'app è già in esecuzione in background. Un avvio a freddo richiede il massimo impegno da parte del sistema, in quanto deve caricare tutto dallo spazio di archiviazione e inizializzare l'app. Cerca di fare in modo che gli avvii a freddo durino 500 ms o meno.

  • Latenze P95 e P99 molto vicine alla latenza mediana. Quando l'app impiega molto tempo per avviarsi, l'esperienza utente è scadente. Le comunicazioni interprocesso (IPC) e le operazioni di I/O non necessarie durante il percorso critico di avvio dell'app possono causare contese di blocco e introdurre incoerenze.

Scorrimento a scatti

Jank è il termine che descrive il problema visivo che si verifica quando il sistema non è in grado di creare e fornire i frame in tempo per disegnarli sullo schermo alla cadenza richiesta di 60 Hz o superiore. Il jank è più evidente durante lo scorrimento, quando invece di un flusso animato fluido, si verificano interruzioni. Il problema di scatti si verifica quando il movimento si interrompe lungo il percorso per uno o più frame, poiché l'app impiega più tempo a eseguire il rendering dei contenuti rispetto alla durata di un frame sul sistema.

Le app devono avere come target frequenze di aggiornamento a 90 Hz. Le frequenze di rendering convenzionali sono 60 Hz, ma molti dispositivi più recenti funzionano in modalità 90 Hz durante le interazioni dell'utente, ad esempio lo scorrimento. Alcuni dispositivi supportano frequenze ancora più elevate, fino a 120 Hz.

Per vedere la frequenza di aggiornamento utilizzata da un dispositivo in un determinato momento, attiva una sovrapposizione utilizzando Opzioni sviluppatore > Mostra frequenza di aggiornamento nella sezione Debug.

Transizioni non fluide

Ciò è evidente durante le interazioni, ad esempio il passaggio da una scheda all'altra o il caricamento di una nuova attività. Questi tipi di transizioni devono essere animazioni fluide e non includere ritardi o sfarfallio visivo.

Inefficienze energetiche

L'esecuzione di attività riduce la carica della batteria e l'esecuzione di attività non necessarie riduce la durata della batteria.

Le allocazioni di memoria, che derivano dalla creazione di nuovi oggetti nel codice, possono essere la causa di un lavoro significativo nel sistema. Questo perché non solo le allocazioni richiedono uno sforzo da parte di Android Runtime (ART), ma anche la liberazione di questi oggetti in un secondo momento (garbage collection) richiede tempo e impegno. Sia l'allocazione che la raccolta sono molto più veloci ed efficienti, soprattutto per gli oggetti temporanei. Sebbene in passato fosse una best practice evitare di allocare oggetti quando possibile, ti consigliamo di fare ciò che è più sensato per la tua app e la tua architettura. Risparmiare sulle allocazioni a rischio di codice non gestibile non è la best practice, data la capacità di ART.

Tuttavia, richiede impegno, quindi tieni presente che può contribuire a problemi di prestazioni se allochi molti oggetti nel ciclo interno.

Identificare i problemi

Abbiamo consigliato il seguente flusso di lavoro per identificare e risolvere i problemi di prestazioni:

  1. Identifica e ispeziona i seguenti Critical User Journey:
    • Flussi di avvio comuni, inclusi quelli da launcher e notifica.
    • Schermate in cui l'utente scorre i dati.
    • Transizioni tra le schermate.
    • Flussi di lunga durata, come la navigazione o la riproduzione di musica.
  2. Ispeziona cosa succede durante i flussi precedenti utilizzando i seguenti strumenti di debug:
    • Perfetto: ti consente di vedere cosa sta succedendo sull'intero dispositivo con dati di temporizzazione precisi.
    • Memory Profiler: ti consente di vedere quali allocazioni di memoria vengono eseguite nell'heap.
    • Simpleperf: mostra un grafico a fiamme delle chiamate funzione che utilizzano la maggior parte della CPU durante un determinato periodo di tempo. Quando identifichi qualcosa che richiede molto tempo in Systrace, ma non sai perché, Simpleperf può fornire informazioni aggiuntive.

Per comprendere ed eseguire il debug di questi problemi di prestazioni, è fondamentale eseguire manualmente il debug delle singole esecuzioni di test. Non puoi sostituire i passaggi precedenti analizzando i dati aggregati. Tuttavia, per capire cosa vedono effettivamente gli utenti e identificare quando potrebbero verificarsi regressioni, è importante configurare la raccolta delle metriche nei test automatizzati e sul campo:

  • Flussi di avvio
  • Jank
    • Metriche sul campo
      • Android vitals in Play Console: in Play Console non puoi restringere le metriche a un percorso utente specifico. Riporta solo il jank complessivo nell'app.
      • Misurazione personalizzata con FrameMetricsAggregator: puoi utilizzare FrameMetricsAggregator per registrare le metriche di jank durante un particolare flusso di lavoro.
    • Test di laboratorio
      • Scorrimento con Macrobenchmark.
      • Macrobenchmark raccoglie i tempi dei frame utilizzando i comandi dumpsys gfxinfo che racchiudono un singolo percorso utente. Questo è un modo per comprendere la variazione di jank in un percorso utente specifico. Le metriche RenderTime, che evidenziano il tempo necessario per disegnare i frame, sono più importanti del conteggio dei frame con problemi per identificare regressioni o miglioramenti.

I link alle app sono link diretti basati sull'URL del tuo sito web di cui è stata verificata la proprietà. Di seguito sono riportati i motivi per cui le verifiche dei link per app potrebbero non riuscire.

  • Ambiti del filtro per intent: aggiungi autoVerify solo ai filtri per intent per gli URL a cui la tua app può rispondere.
  • Passaggi di protocollo non verificati: i reindirizzamenti lato server e dei sottodomini non verificati sono considerati rischi per la sicurezza e non superano la verifica. che causano l'interruzione di tutti i link autoVerify. Ad esempio, il reindirizzamento dei link da HTTP a HTTPS, come da example.com a www.example.com, senza verificare i link HTTPS può causare un errore di verifica. Assicurati di verificare i link per app aggiungendo filtri intent.
  • Link non verificabili: l'aggiunta di link non verificabili a scopo di test può impedire al sistema di verificare i link all'app per la tua app.
  • Server inaffidabili: assicurati che i server possano connettersi alle app client.

Configurare l'app per l'analisi del rendimento

È essenziale eseguire la configurazione corretta per ottenere benchmark accurati, ripetibili e utilizzabili da un'app. Esegui il test su un sistema il più vicino possibile alla produzione, eliminando le fonti di rumore. Le sezioni seguenti mostrano una serie di passaggi specifici per APK e sistema che puoi eseguire per preparare una configurazione di test, alcuni dei quali sono specifici per il caso d'uso.

Tracepoint

Le app possono instrumentare il proprio codice con eventi di traccia personalizzati.

Durante l'acquisizione delle tracce, la tracciatura comporta un piccolo sovraccarico di circa 5 μs per sezione, quindi non inserirla in ogni metodo. Il tracciamento di blocchi di lavoro più grandi di >0,1 ms può fornire informazioni significative sui colli di bottiglia.

Considerazioni relative agli APK

Le varianti di debug possono essere utili per la risoluzione dei problemi e la simbolizzazione degli esempi di stack, ma hanno un impatto significativo sulle prestazioni. I dispositivi con Android 10 (livello API 29) e versioni successive possono utilizzare profileable android:shell="true" nel manifest per attivare la profilazione nelle build di rilascio.

Utilizza la configurazione di riduzione del codice di livello di produzione. A seconda delle risorse utilizzate dall'app, questo può avere un impatto significativo sulle prestazioni. Alcune configurazioni ProGuard rimuovono i punti di traccia, quindi valuta la possibilità di rimuovere queste regole per la configurazione su cui stai eseguendo i test.

Compilation

Compila la tua app sul dispositivo in uno stato noto, in genere speed per semplicità o speed-profile per una corrispondenza più precisa con il rendimento di produzione (anche se ciò richiede il riscaldamento dell'applicazione e lo scarico dei profili o la compilazione dei profili di base dell'app).

Sia speed che speed-profile riducono la quantità di codice in esecuzione interpretato da dex e, di conseguenza, la quantità di compilazione just-in-time (JIT) in background, che può causare interferenze significative. Solo speed-profile riduce l'impatto del caricamento delle classi di runtime da dex.

Il seguente comando compila l'applicazione utilizzando la modalità speed:

adb shell cmd package compile -m speed -f com.example.packagename

La modalità di compilazione speed compila completamente i metodi dell'app. La modalità speed-profile compila i metodi e le classi dell'app in base a un profilo dei percorsi di codice utilizzati raccolto durante l'utilizzo dell'app. Può essere difficile raccogliere i profili in modo coerente e corretto, quindi se decidi di utilizzarli, verifica che raccolgano ciò che ti aspetti. I profili si trovano nel seguente percorso:

/data/misc/profiles/ref/[package-name]/primary.prof

Considerazioni sul sistema

Per misurazioni di basso livello e ad alta fedeltà, calibra i tuoi dispositivi. Esegui confronti A/B sullo stesso dispositivo e sulla stessa versione del sistema operativo. Possono verificarsi variazioni significative nel rendimento, anche per lo stesso tipo di dispositivo.

Sui dispositivi con accesso root, valuta la possibilità di utilizzare uno script lockClocks per Microbenchmark. Tra le altre cose, questi script eseguono le seguenti operazioni:

  • Posiziona le CPU a una frequenza fissa.
  • Disattiva i core piccoli e configura la GPU.
  • Disattiva la limitazione termica.

Non è consigliabile utilizzare uno script lockClocks per i test incentrati sull'esperienza utente, come l'avvio dell'app, il test DoU e il test di jank, ma può essere essenziale per ridurre il rumore nei test Microbenchmark.

Se possibile, valuta la possibilità di utilizzare un framework di test come Macrobenchmark, che può ridurre il rumore nelle misurazioni e prevenire l'imprecisione della misurazione.

Avvio dell'app lento: attività di trampolino non necessaria

Un'attività di trampolino può estendere inutilmente il tempo di avvio dell'app ed è importante sapere se la tua app lo fa. Come mostrato nell'esempio seguente di traccia, un activityStart è immediatamente seguito da un altro activityStart senza che vengano disegnati frame dalla prima attività.

alt_text Figura 1. Una traccia che mostra l'attività sul tappeto elastico.

Ciò può verificarsi sia in un punto di ingresso delle notifiche sia in un punto di ingresso di avvio regolare dell'app e spesso puoi risolvere il problema eseguendo il refactoring. Ad esempio, se utilizzi questa attività per eseguire la configurazione prima dell'esecuzione di un'altra attività, estrai questo codice in un componente o una libreria riutilizzabile.

Allocazioni non necessarie che attivano frequenti GC

In un Systrace, potresti notare che le operazioni di Garbage Collection (GC) vengono eseguite più frequentemente del previsto.

Nell'esempio seguente, ogni 10 secondi durante un'operazione a lunga esecuzione è un indicatore che l'app potrebbe allocare inutilmente ma in modo coerente nel tempo:

alt_text Figura 2. Una traccia che mostra lo spazio tra gli eventi di Google Cloud.

Potresti anche notare che uno stack di chiamate specifico esegue la maggior parte delle allocazioni quando utilizzi Profiler di memoria. Non è necessario eliminare tutte le allocazioni in modo aggressivo, in quanto ciò può rendere più difficile la manutenzione del codice. Inizia invece a lavorare sui punti critici delle allocazioni.

Frame instabili

La pipeline grafica è relativamente complessa e può esserci qualche sfumatura nel determinare se un utente potrebbe alla fine vedere un frame eliminato. In alcuni casi, la piattaforma può "recuperare" un frame utilizzando il buffering. Tuttavia, puoi ignorare la maggior parte di queste sfumature per identificare i frame problematici dal punto di vista della tua app.

Quando i frame vengono disegnati con poco lavoro richiesto dall'app, i Choreographer.doFrame() punti di traccia si verificano con una cadenza di 16,7 ms su un dispositivo a 60 FPS:

alt_text Figura 3. Una traccia che mostra i frame veloci frequenti.

Se riduci lo zoom e scorri la traccia, a volte vedi che i frame impiegano un po' più di tempo per essere completati, ma va bene lo stesso perché non impiegano più del tempo assegnato di 16,7 ms:

alt_text Figura 4. Una traccia che mostra frame veloci frequenti con raffiche periodiche di lavoro.

Quando si verifica un'interruzione di questa cadenza regolare, si tratta di un frame instabile, come mostrato nella figura 5:

alt_text Figura 5. Una traccia che mostra un frame con problemi.

Puoi esercitarti a identificarli.

alt_text Figura 6. Una traccia che mostra più frame con problemi.

In alcuni casi, devi aumentare lo zoom su un punto di tracciamento per visualizzare maggiori informazioni su quali visualizzazioni vengono gonfiate o su cosa sta facendo RecyclerView. In altri casi, potresti dover eseguire ulteriori ispezioni.

Per ulteriori informazioni sull'identificazione dei frame janky e sul debug delle relative cause, consulta Rendering lento.

Errori comuni di RecyclerView

L'invalidazione di tutti i dati di supporto di RecyclerView può comportare tempi di rendering dei frame lunghi e jank. Per ridurre al minimo il numero di viste da aggiornare, invalida solo i dati che cambiano.

Consulta Presentare dati dinamici per scoprire come evitare costose chiamate notifyDatasetChanged() che causano l'aggiornamento dei contenuti anziché la loro sostituzione completa.

Se non supporti correttamente ogni RecyclerView nidificato, la RecyclerView interno può essere ricreato completamente ogni volta. Ogni RecyclerView interno nidificato deve avere un RecycledViewPool impostato per garantire che le visualizzazioni possano essere riciclate tra ogni RecyclerView interno.

Il precaricamento di dati insufficienti o non tempestivo può rendere difficile raggiungere la fine di un elenco scorrevole quando un utente deve attendere altri dati dal server. Anche se tecnicamente non si tratta di jank, in quanto non vengono mancati i termini di scadenza dei frame, puoi migliorare significativamente l'esperienza utente modificando i tempi e la quantità di precaricamento in modo che l'utente non debba attendere i dati.

Eseguire il debug dell'app

Di seguito sono riportati diversi metodi per eseguire il debug del rendimento della tua app. Guarda il seguente video per una panoramica della tracciatura del sistema e dell'utilizzo del profiler di Android Studio.

Eseguire il debug dell'avvio dell'app con Systrace

Consulta Tempi di avvio dell'app per una panoramica della procedura di avvio dell'app e guarda il seguente video per una panoramica della tracciatura del sistema.

Puoi disambiguare i tipi di startup nelle seguenti fasi:

  • Avvio a freddo: inizia creando un nuovo processo senza stato salvato.
  • Avvio a caldo: ricrea l'attività riutilizzando il processo oppure ricrea il processo con lo stato salvato.
  • Avvio a caldo: riavvia l'attività e inizia dall'inflazione.

Ti consigliamo di acquisire le Systrace con l'app System Tracing sul dispositivo. Per Android 10 e versioni successive, utilizza Perfetto. Per Android 9 e versioni precedenti, utilizza Systrace. Ti consigliamo inoltre di visualizzare i file di traccia con il visualizzatore di tracce Perfetto basato sul web. Per ulteriori informazioni, consulta la panoramica della tracciabilità del sistema.

Ecco alcuni aspetti da controllare:

  • Monitor contention: la competizione per le risorse protette dal monitor può introdurre un ritardo significativo nell'avvio dell'app.
  • Transazioni del binder sincrone: cerca transazioni non necessarie nel percorso critico della tua app. Se una transazione necessaria è costosa, valuta la possibilità di collaborare con il team della piattaforma associata per apportare miglioramenti.

  • GC simultaneo: è comune e ha un impatto relativamente basso, ma se lo riscontri spesso, valuta la possibilità di esaminarlo con il profiler di memoria di Android Studio.

  • I/O: controlla l'I/O eseguito durante l'avvio e cerca eventuali blocchi prolungati.

  • Attività significativa su altri thread: questi possono interferire con il thread della UI, quindi fai attenzione al lavoro in background durante l'avvio.

Ti consigliamo di chiamare reportFullyDrawn al termine dell'avvio dal punto di vista dell'app per migliorare i report sulle metriche di avvio dell'app. Per saperne di più sull'utilizzo di reportFullyDrawn, consulta la sezione Tempo di visualizzazione completa. Puoi estrarre gli orari di inizio definiti da RFD tramite il processore di tracce Perfetto e viene generato un evento di traccia visibile all'utente.

Utilizzare System Tracing sul dispositivo

Puoi utilizzare l'app a livello di sistema chiamata System Tracing per acquisire una traccia di sistema su un dispositivo. Questa app ti consente di registrare le tracce dal dispositivo senza doverlo collegare o connetterlo a adb.

Utilizzare Memory Profiler di Android Studio

Puoi utilizzare Android Studio Memory Profiler per ispezionare la pressione della memoria che potrebbe essere causata da perdite di memoria o da pattern di utilizzo errati. Fornisce una visualizzazione in tempo reale delle allocazioni degli oggetti.

Puoi risolvere i problemi di memoria nella tua app seguendo le informazioni sull'utilizzo di Memory Profiler per monitorare perché e con quale frequenza si verificano le operazioni di Garbage Collection.

Per profilare la memoria dell'app, segui questi passaggi:

  1. Rilevare problemi di memoria.

    Registra una sessione di profilazione della memoria del percorso dell'utente su cui vuoi concentrarti. Cerca un numero crescente di oggetti, come mostrato nella Figura 7, che alla fine porta a GC, come mostrato nella Figura 8.

    alt_text Figura 7. Aumento del conteggio degli oggetti.

    alt_text Figura 8. Raccolte di rifiuti.

    Dopo aver identificato il percorso dell'utente che aggiunge pressione sulla memoria, analizza le cause principali della pressione sulla memoria.

  2. Diagnosticare i punti critici della pressione della memoria.

    Seleziona un intervallo nella sequenza temporale per visualizzare sia le Allocazioni sia le Dimensioni superficiali, come mostrato nella figura 9.

    alt_text Figura 9. Valori per Allocations e Shallow Size.

    Esistono diversi modi per ordinare questi dati. Di seguito sono riportati alcuni esempi di come ogni visualizzazione può aiutarti ad analizzare i problemi.

    • Organizza per classe: utile quando vuoi trovare classi che generano oggetti che altrimenti vengono memorizzati nella cache o riutilizzati da un pool di memoria.

      Ad esempio, se vedi un'app che crea 2000 oggetti della classe chiamata "Vertex" ogni secondo, il conteggio Allocazioni aumenta di 2000 ogni secondo e lo vedi quando ordini per classe. Se vuoi riutilizzare questi oggetti per evitare di generare dati inutili, implementa un pool di memoria.

    • Organizza per callstack: utile quando vuoi trovare un percorso critico in cui viene allocata la memoria, ad esempio all'interno di un ciclo o di una funzione specifica che esegue molte operazioni di allocazione.

    • Dimensioni superficiali: tiene traccia solo della memoria dell'oggetto stesso. È utile per monitorare classi semplici composte principalmente solo da valori primitivi.

    • Dimensioni mantenute: mostra la memoria totale dovuta all'oggetto e ai riferimenti a cui fa riferimento solo l'oggetto. È utile per monitorare la pressione della memoria dovuta a oggetti complessi. Per ottenere questo valore, esegui un dump completo della memoria, come mostrato nella Figura 10, e Dimensioni mantenute viene aggiunto come colonna, come mostrato nella Figura 11.

      alt_text Figura 10. Dump completo della memoria.

      Colonna Dimensioni trattenute.
      Figura 11. Colonna Dimensioni trattenute.
  3. Misura l'impatto di un'ottimizzazione.

    Le GC sono più evidenti ed è più facile misurare l'impatto delle ottimizzazioni della memoria. Quando un'ottimizzazione riduce la pressione della memoria, vengono visualizzati meno GC.

    Per misurare l'impatto dell'ottimizzazione, nella sequenza temporale del profiler, misura il tempo tra le operazioni di Garbage Collection. Puoi quindi notare che il tempo tra una GC e l'altra è maggiore.

    Gli impatti finali dei miglioramenti della memoria sono i seguenti:

    • I riavvii per esaurimento della memoria sono probabilmente ridotti se l'app non subisce costantemente una saturazione della memoria.
    • Un numero inferiore di GC migliora le metriche di jank, in particolare nel P99. Questo perché i GC causano contesa della CPU, il che può comportare il rinvio delle attività di rendering durante l'esecuzione di GC.