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:
- 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.
- 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
- Metriche di campo: Tempo di avvio di Play Console
- Test di laboratorio: avvio del test con Macrobenchmark
- 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 utilizzareFrameMetricsAggregator
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 metricheRenderTime
, che evidenziano il tempo necessario per disegnare i frame, sono più importanti del conteggio dei frame con problemi per identificare regressioni o miglioramenti.
- Metriche sul campo
Problemi di verifica dei link dell'app
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à.
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:
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:
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:
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:
Figura 5. Una traccia che mostra un frame con problemi.
Puoi esercitarti a identificarli.
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:
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.
Figura 7. Aumento del conteggio degli oggetti.
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.
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.
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.
Figura 10. Dump completo della memoria.
Figura 11. Colonna Dimensioni trattenute.
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.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Analisi e ottimizzazione dell'avvio dell'app {:#app-startup-analysis-optimization}
- Frame bloccati
- Scrivere un Macrobenchmark