Panoramica della misurazione del rendimento dell'app

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

Problemi di prestazioni principali

Esistono molti problemi che possono contribuire a scarse prestazioni 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, la notifica o un altro punto di ingresso e la visualizzazione dei dati dell'utente sullo schermo.

Cerca di raggiungere i seguenti obiettivi di avvio nelle tue app:

  • Avvio a freddo in meno di 500 ms. Un avvio a freddo si verifica quando l'app avviata non è presente nella memoria del sistema. Questo accade quando si tratta del primo avvio dell'app dal riavvio o perché il processo dell'app viene interrotto dall'utente o dal sistema.

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

  • Latenze P95 e P99 molto vicine alla latenza mediana. Se l'avvio dell'app richiede molto tempo, l'esperienza utente risulta negativa. Le comunicazioni tra processi (IPC) e I/O non necessari durante il percorso critico dell'avvio dell'app possono riscontrare conflitti di blocco e introdurre incoerenze.

Scorrimento

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 attirarli sullo schermo alla frequenza richiesta di almeno 60 Hz. Lo sconvolgimento è più evidente durante lo scorrimento, quando invece di un flusso fluido ci sono dei singhiozzi. La demarcazione viene visualizzata quando il movimento si interrompe per uno o più frame, in quanto l'app impiega più tempo a visualizzare i contenuti rispetto alla durata di un frame sul sistema.

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

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

Transizioni non fluide

Ciò si manifesta durante interazioni quali 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 sfarfallii visivi.

Inefficienze di potenza

Fare lavoro riduce la carica della batteria e svolgere attività non necessarie ne riduce la durata.

Le allocazioni della memoria, derivanti dalla creazione di nuovi oggetti nel codice, possono essere la causa di un lavoro significativo nel sistema. Questo perché non solo le allocazioni specifiche richiedono uno sforzo da parte di Android Runtime (ART), ma anche il salvataggio di questi oggetti in un secondo momento (garbage collection) richiede anche tempo e impegno. Sia l'allocazione che la raccolta sono molto più veloci ed efficienti, soprattutto per gli oggetti temporanei. Sebbene una volta fosse possibile evitare l'allocazione degli oggetti quando possibile, ti consigliamo di scegliere la soluzione più adatta alla tua app e alla tua architettura. Risparmiare sulle allocazioni a rischio di codice non gestibile non è la best practice, date le funzionalità di ART.

Tuttavia, richiede impegno, quindi tieni presente che può contribuire ai problemi di prestazioni se stai allocando molti oggetti nel tuo ciclo interno.

Identifica i problemi

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

  1. Identifica e ispeziona i seguenti percorsi critici dell'utente:
    • Flussi di avvio comuni, ad esempio da Avvio app e dalle notifiche.
    • 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. Controlla che cosa accade durante i flussi precedenti utilizzando i seguenti strumenti di debug:
    • Perfetto: ti permette di vedere cosa sta succedendo sull'intero dispositivo con dati precisi sui tempi.
    • Profilor di memoria: consente di visualizzare le allocazioni della memoria in corso nell'heap.
    • Simpleperf: mostra un grafico a fiamma delle chiamate di funzione che utilizzano la maggior quantità di CPU in un determinato periodo di tempo. Quando identifichi qualcosa che sta richiedendo molto tempo in Systrace, ma non sai perché, Simpleperf può fornire ulteriori informazioni.

Per comprendere ed eseguire il debug di questi problemi di prestazioni, è fondamentale eseguire manualmente il debug delle singole esecuzioni del 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 automatici e sul campo:

  • Flussi di avvio
  • Segnale
    • Metriche dei campi
      • Segnali frame di Play Console: all'interno di Play Console non puoi restringere le metriche a un percorso dell'utente specifico. Registra solo i blocchi complessivi in tutta l'app.
      • Misurazione personalizzata con FrameMetricsAggregator: puoi utilizzare FrameMetricsAggregator per registrare le metriche di jank durante un determinato flusso di lavoro.
    • Test di laboratorio
      • Scorrimento con Macrobenchmark.
      • Macrobenchmark raccoglie la durata frame usando i comandi dumpsys gfxinfo che includono un singolo percorso dell'utente. Questo è un modo per capire la variazione di jank in uno specifico percorso dell'utente. Le metriche RenderTime, che evidenziano il tempo necessario per il tracciamento dei frame, sono più importanti del numero di frame indesiderati per identificare le regressioni o i miglioramenti.

I link dell'app sono link diretti basati sull'URL del tuo sito web che è stato verificato per appartenere al tuo sito web. Di seguito sono riportati i motivi per cui le verifiche di App Link non andranno a buon fine.

  • Ambiti di filtro per intent: aggiungi solo autoVerify ai filtri per intent per gli URL a cui la tua app può rispondere.
  • Switch di protocollo non verificati: i reindirizzamenti lato server e sottodomini non verificati sono considerati rischi per la sicurezza e la verifica non riuscita. Causano il mancato funzionamento di tutti i link autoVerify. Ad esempio, reindirizzare i link da HTTP a HTTPS, come example.com a www.example.com, senza verificare i link HTTPS, la verifica potrebbe non riuscire. Assicurati di verificare i link alle app aggiungendo filtri per intent.
  • Link non verificabili: l'aggiunta di link non verificabili a scopo di test può far sì che il sistema non verifichi i link dell'app per la tua app.
  • Server non affidabili: assicurati che i tuoi server possano connettersi alle app client.

Configurare l'app per l'analisi del rendimento

È essenziale effettuare una corretta configurazione per ottenere da un'app benchmark precisi, ripetibili e strategici. Esegui i test su un sistema il più vicino possibile alla produzione, eliminando al contempo le fonti di rumore. Le seguenti sezioni mostrano una serie di passaggi specifici per APK e sistema che puoi seguire per preparare una configurazione di test, alcuni dei quali sono specifici per i casi d'uso.

Punti di traccia

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

Durante l'acquisizione delle tracce, il tracciamento comporta un piccolo sovraccarico di circa 5 μs per sezione, quindi non racchiuderlo in tutti i metodi. Il tracciamento di blocchi di lavoro più grandi, con una frequenza maggiore di 0,1 ms, può fornire insight significativi sui colli di bottiglia.

Considerazioni sugli APK

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

Utilizza la configurazione di riduzione del codice di livello enterprise. A seconda delle risorse utilizzate dalla tua app, questo può avere un impatto significativo sulle prestazioni. Alcune configurazioni ProGuard rimuovono i punti di traccia, pertanto ti consigliamo di rimuovere queste regole per la configurazione su cui stai eseguendo i test.

Compilation

Compila l'app sul dispositivo a uno stato noto, in genere speed o speed-profile. L'attività just-in-time (JIT) in background può avere un overhead di prestazioni significativo e lo raggiungi spesso se reinstalli l'APK tra un'esecuzione di test e l'altra. Per farlo, ecco un comando:

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

La modalità di compilazione speed compila completamente l'app. La modalità speed-profile compila l'app in base a un profilo dei percorsi di codice utilizzati che vengono raccolti durante l'utilizzo dell'app. Raccogliere profili in modo coerente e corretto può essere difficile, quindi, se decidi di usarli, accertati che stiano raccogliendo ciò che ti aspetti. I profili si trovano nella seguente posizione:

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

Macrobenchmark consente di specificare direttamente la modalità di compilazione.

Considerazioni sul sistema

Per misurazioni di basso livello e ad alta precisione, calibra i dispositivi. Esegui confronti A/B sullo stesso dispositivo e sulla stessa versione del sistema operativo. Possono esserci variazioni significative in termini di rendimento, anche sullo stesso tipo di dispositivo.

Sui dispositivi rooted, valuta la possibilità di utilizzare uno script lockClocks per Microbenchmarks. Tra le altre cose, questi script svolgono le seguenti operazioni:

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

Sconsigliamo di utilizzare uno script lockClocks per test incentrati sull'esperienza utente come il lancio dell'app, i test della DoU e il jank test, ma può essere essenziale per ridurre il rumore nei test di Microbenchmark.

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

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

Un'attività di trampolino può prolungare inutilmente il tempo di avvio dell'app ed è importante sapere se questa attività è in corso. Come mostrato nella traccia di esempio seguente, un elemento activityStart è immediatamente seguito da un altro activityStart senza che la prima attività catturi alcun frame.

testo_alt Figura 1. Una traccia che mostra l'attività del trampolino.

Questo può accadere sia in un punto di ingresso di notifica che in un normale punto di ingresso di avvio dell'app e spesso puoi risolvere il problema con il refactoring. Ad esempio, se utilizzi questa attività per eseguire la configurazione prima dell'esecuzione di un'altra attività, estrai questo codice in una libreria o un componente riutilizzabile.

Allocazioni non necessarie che attivano frequenti GC

Potresti notare che le garbage collection (GC) avvengono più spesso di quanto ti aspetti in Systrace.

Nell'esempio seguente, ogni 10 secondi durante un'operazione a lunga esecuzione indica che l'app potrebbe essere allocata inutilmente ma costantemente nel tempo:

testo_alt Figura 2. Una traccia che mostra lo spazio tra gli eventi di Garbage Collection.

Quando utilizzi Memory Profiler, potresti anche notare che uno stack di chiamate specifico sta occupando la maggior parte delle allocazioni. Non devi eliminare tutte le allocazioni in modo aggressivo, poiché questo può rendere il codice più difficile da gestire. Inizia invece lavorando sugli hotspot delle allocazioni.

Fotogrammi deboli

La pipeline grafica è relativamente complessa e può presentare alcune sfumature nel determinare se un utente alla fine potrebbe visualizzare un frame ignorato. In alcuni casi, la piattaforma è in grado di "salvare" un frame usando il buffering. Tuttavia, puoi ignorare la maggior parte di questa sfumatura per identificare i frame problematici dal punto di vista dell'app.

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

testo_alt Figura 3. Traccia che mostra frame veloci e frequenti.

Se diminuisci lo zoom e ti sposti nella traccia, a volte puoi notare che il completamento dei fotogrammi richiede un po' più di tempo, ma non è un problema, perché non richiedono più del tempo previsto di 16,7 ms:

testo_alt Figura 4. Una traccia che mostra frame veloci frequenti con burst di lavoro periodici.

Quando noti un'interruzione di questa cadenza regolare, si tratta di un frame scadente, come mostrato nella Figura 5:

testo_alt Figura 5. Una traccia che mostra un frame scadente.

Puoi esercitarti a identificarli.

testo_alt Figura 6. Una traccia che mostra frame più scadenti.

In alcuni casi, è necessario eseguire lo zoom su un punto di traccia per ottenere ulteriori informazioni sulle viste che vengono gonfiate o su ciò che sta facendo RecyclerView. In altri casi, potrebbe essere necessario analizzare ulteriormente.

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

Errori comuni di RecyclerView

La convalida di tutti i dati di supporto di RecyclerView inutilmente può comportare lunghi tempi di rendering dei frame e jank. Per ridurre al minimo il numero di visualizzazioni da aggiornare, annulla solo i dati che cambiano.

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

Se non supporti correttamente ogni elemento RecyclerView nidificato, è possibile che l'elemento RecyclerView interno venga ricreato completamente ogni volta. Ogni elemento RecyclerView interno nidificato deve avere un set di RecycledViewPool per garantire che le viste possano essere riciclate tra ogni elemento RecyclerView interno.

Il mancato precaricamento di dati o il mancato precaricamento di dati in modo tempestivo può rendere fastidioso l'arrivo in fondo di un elenco a scorrimento quando un utente deve attendere di ottenere più dati dal server. Anche se non si tratta tecnicamente di un fastidio, dato che non mancano le scadenze dei frame, puoi migliorare notevolmente l'UX modificando la tempistica e la quantità del precaricamento in modo che l'utente non debba attendere i dati.

Esegui il debug dell'app

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

Debug dell'avvio dell'app con Systrace

Vedi Tempi di avvio dell'app per una panoramica del processo di avvio dell'app e guarda il seguente video per una panoramica del tracciamento del sistema.

Puoi distinguere i tipi di startup nelle fasi seguenti:

  • Avvio a freddo: inizia con la creazione di un nuovo processo senza stato salvato.
  • Avvio a caldo: ricrea l'attività mentre riutilizza il processo o ricrea il processo con lo stato salvato.
  • Avvio a caldo: riavvia l'attività e inizia al momento dell'inflazione.

Consigliamo di acquisire le istanze con l'app di tracciamento del sistema sul dispositivo. Per Android 10 e versioni successive, usa Perfetto. Per Android 9 e versioni precedenti, usa Systrace. Ti consigliamo inoltre di visualizzare i file di traccia con il visualizzatore traccia Perfetto basato sul web. Per ulteriori informazioni, consulta la Panoramica del tracciamento del sistema.

Alcuni aspetti da controllare sono:

  • Monitora i conflitti: la concorrenza per le risorse protette dal monitoraggio può introdurre un ritardo significativo nell'avvio dell'app.
  • Transazioni binder sincrone: cerca le transazioni non necessarie nel percorso critico della tua app. Se una transazione necessaria è costosa, valuta la possibilità di collaborare con il team della piattaforma associato.

  • Garbage Collection simultanea: si tratta di un problema comune e di impatto relativamente basso, ma se lo riscontri spesso, valuta la possibilità di esaminarlo con il profiler della memoria di Android Studio.

  • I/O: verifica la presenza di I/O durante l'avvio e cerca stalli lunghi.

  • Attività significative su altri thread: possono interferire con il thread dell'interfaccia utente, quindi presta 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 relative all'avvio dell'app. Consulta la sezione Tempo per la visualizzazione completa per ulteriori informazioni sull'uso di reportFullyDrawn. Puoi estrarre i tempi di inizio definiti da RFD tramite il processore di traccia Perfetto per generare un evento di traccia visibile all'utente.

Utilizzo del tracciamento del sistema sul dispositivo

Puoi utilizzare l'app a livello di sistema denominata Tracciamento del sistema per acquisire una traccia di sistema su un dispositivo. Questa app consente di registrare le tracce del dispositivo senza doverla collegare o collegare a adb.

Usare Android Studio Memory Profiler

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 non corretti. Offre una visualizzazione in tempo reale delle allocazioni degli oggetti.

Puoi risolvere i problemi di memoria nella tua app seguendo le informazioni tratte dall'utilizzo di Profiler della memoria per monitorare perché e con quale frequenza si verificano i GC.

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

  1. Rileva i 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 ai GC, come mostrato nella Figura 8.

    testo_alt Figura 7. Aumento del conteggio degli oggetti.

    testo_alt Figura 8. Raccolta rifiuti.

    Dopo aver identificato il percorso dell'utente che aumenta la pressione sulla memoria, analizza le cause principali di questa pressione.

  2. Eseguire la diagnosi degli hotspot di pressione della memoria.

    Seleziona un intervallo nella sequenza temporale per visualizzare sia le allocazioni sia la dimensione superficiale, come mostrato nella Figura 9.

    testo_alt Figura 9. Valori per Allocazioni e Dimensioni superficiali.

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

    • Ordina 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 di classe denominata "Vertex" ogni secondo, il conteggio delle allocations viene aumentato di 2000 ogni secondo e lo vedi quando esegui l'ordinamento per classe. Se vuoi riutilizzare questi oggetti per evitare di generare garbage, implementa un pool di memoria.

    • Ordina per callback: utile quando vuoi individuare un percorso ad accesso frequente in cui viene allocata la memoria, ad esempio all'interno di un loop o di una funzione specifica che svolge molte operazioni di allocazione.

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

    • Dimensioni conservate: mostra la memoria totale dovuta all'oggetto e ai riferimenti a cui l'oggetto fa riferimento esclusivamente. È utile per tenere traccia della pressione della memoria dovuta a oggetti complessi. Per ottenere questo valore, esegui un dump completo della memoria, come mostrato nella Figura 10, in modo che il valore Dimensioni mantenuta venga aggiunto come colonna, come mostrato nella Figura 11.

      testo_alt Figura 10. Dump della memoria completa.

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

    Le GC sono più evidenti e facili da misurare l'impatto delle ottimizzazioni della memoria. Quando un'ottimizzazione riduce la pressione della memoria, vedrai meno GC.

    Per misurare l'impatto dell'ottimizzazione, nella sequenza temporale del profiler, misura il tempo tra i GC. A questo punto, puoi osservare il tempo che trascorre tra un GC e l'altro.

    Gli effetti finali dei miglioramenti della memoria sono i seguenti:

    • Gli arresti per esaurimento della memoria vengono probabilmente ridotti se l'app non raggiunge costantemente pressione in memoria.
    • Avere meno GC migliora le metriche di jank, soprattutto nei P99. Questo perché i GC causano una contesa della CPU, che può portare al differimento delle attività di rendering durante l'esecuzione dei GC.