Libreria JankStats

La libreria JankStats consente di monitorare e analizzare i problemi di prestazioni nelle applicazioni. Jank si riferisce ai frame dell'applicazione che richiedono troppo tempo per il rendering e la libreria JankStats fornisce report sulle statistiche di jank della tua app.

Azioni consentite

JankStats si basa sulle funzionalità esistenti della piattaforma Android, tra cui l'API FrameMetrics su Android 7 (livello API 24) e versioni successive o OnPreDrawListener nelle versioni precedenti. Questi meccanismi possono aiutare le applicazioni a monitorare il tempo necessario per il completamento dei frame. La libreria JanksStats offre due funzionalità aggiuntive che la rendono più dinamica e più facile da usare: euristica jank e stato dell'interfaccia utente.

Euristica di Jank

Sebbene sia possibile utilizzare FrameMetrics per tenere traccia delle durate dei frame, FrameMetrics non offre alcuna assistenza per determinare il jank effettivo. JankStats, tuttavia, dispone di meccanismi interni configurabili per determinare quando si verifica il jank, rendendo i report più immediatamente utili.

Stato UI

Spesso è necessario conoscere il contesto dei problemi di prestazioni dell'app. Ad esempio, se sviluppi un'app multischermo complessa che utilizza FrameMetrics e scopri che la tua app ha spesso frame estremamente scadenti, ti consigliamo di contestualizzare queste informazioni sapendo dove si è verificato il problema, cosa stava facendo l'utente e come replicarlo.

JankStats risolve questo problema introducendo un'API state che ti consente di comunicare con la libreria per fornire informazioni sull'Attività dell'app. Quando JankStats registra le informazioni su un frame scadente, include lo stato attuale dell'applicazione nei report jank.

Utilizzo

Per iniziare a utilizzare JankStats, crea un'istanza e abilita la libreria per ogni Window. Ogni oggetto JankStats monitora i dati solo all'interno di un Window. La creazione di istanze della libreria richiede un'istanza Window insieme a un listener OnFrameListener, entrambi utilizzati per inviare metriche al client. Il listener viene chiamato con FrameData su ogni frame e mostra in dettaglio:

  • Ora di inizio frame
  • Valori della durata
  • Indica se il frame deve essere considerato jank o meno.
  • Un insieme di coppie di stringhe contenenti informazioni sullo stato dell'applicazione durante il frame

Per rendere più utile JankStats, le applicazioni devono completare la libreria con informazioni pertinenti sullo stato dell'interfaccia utente per i report in FrameData. A questo scopo, utilizza l'API PerformanceMetricsState (non direttamente JankStats), in cui si trovano tutte le API e la logica di gestione dello stato.

Inizializzazione

Per iniziare a utilizzare la libreria JankStats, aggiungi prima la dipendenza JankStats al tuo file Gradle:

implementation "androidx.metrics:metrics-performance:1.0.0-beta01"

Successivamente, inizializza e abilita JankStats per ogni Window. Devi anche mettere in pausa il monitoraggio di JankStats quando un'attività passa in background. Crea e abilita l'oggetto JankStats in Override attività:

class JankLoggingActivity : AppCompatActivity() {

    private lateinit var jankStats: JankStats


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // metrics state holder can be retrieved regardless of JankStats initialization
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // initialize JankStats for current window
        jankStats = JankStats.createAndTrack(window, jankFrameListener)

        // add activity name as state
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
        // ...
    }

L'esempio precedente inserisce informazioni sullo stato dell'attività corrente dopo aver creato l'oggetto JankStats. Tutti i futuri report FrameData creati per questo oggetto JankStats ora includono anche informazioni sulle attività.

Il metodo JankStats.createAndTrack prende un riferimento a un oggetto Window, che è un proxy per la gerarchia di visualizzazione all'interno di Window e per lo stesso Window. jankFrameListener viene chiamato sullo stesso thread utilizzato per inviare le informazioni dalla piattaforma a JankStats internamente.

Per abilitare il monitoraggio e la generazione di report su qualsiasi oggetto JankStats, chiama isTrackingEnabled = true. Sebbene sia abilitato per impostazione predefinita, la messa in pausa di un'attività disattiva il monitoraggio. In questo caso, assicurati di riattivare il tracciamento prima di procedere. Per interrompere il monitoraggio, chiama il numero isTrackingEnabled = false.

override fun onResume() {
    super.onResume()
    jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    jankStats.isTrackingEnabled = false
}

Report

La libreria JankStats segnala tutto il monitoraggio dei dati, per ogni frame, a OnFrameListener per gli oggetti JankStats abilitati. Le app possono archiviare e aggregare questi dati per poterli caricare in un secondo momento. Per ulteriori informazioni, consulta gli esempi forniti nella sezione Aggregazione.

Affinché la tua app riceva i report per frame, dovrai creare e fornire OnFrameListener. Questo listener viene chiamato su ogni frame per fornire dati di jank in corso alle app.

private val jankFrameListener = JankStats.OnFrameListener { frameData ->
    // A real app could do something more interesting, like writing the info to local storage and later on report it.
    Log.v("JankStatsSample", frameData.toString())
}

Il listener fornisce informazioni per frame su jank con l'oggetto FrameData. Questo contiene le seguenti informazioni sul frame richiesto:

  • isjank: un flag booleano che indica se jank si è verificato nel frame.
  • frameDurationUiNanos: durata del frame (in nanosecondi).
  • frameStartNanos: ora di inizio del frame (in nanosecondi).
  • states: stato della tua app durante il frame.

Se usi Android 12 (livello API 31) o versioni successive, puoi usare la seguente procedura per esporre più dati sulla durata dei frame:

Utilizza StateInfo nell'ascoltatore per archiviare le informazioni sullo stato dell'applicazione.

Tieni presente che OnFrameListener viene chiamato sullo stesso thread utilizzato internamente per inviare le informazioni per frame a JankStats. Su Android 6 (livello API 23) e versioni precedenti, si tratta del thread principale (UI). Su Android 7 (livello API 24) e versioni successive, è il thread creato e utilizzato da FrameMetrics. In entrambi i casi, è importante gestire il callback e tornare rapidamente per evitare problemi di prestazioni nel thread.

Inoltre, tieni presente che l'oggetto FrameData inviato nel callback viene riutilizzato su ogni frame per evitare di allocare nuovi oggetti per i report sui dati. Ciò significa che devi copiare e memorizzare nella cache i dati altrove, poiché l'oggetto deve essere considerato obsoleto e obsoleto non appena viene restituito il callback.

Aggregazione

È probabile che tu voglia che il codice dell'app aggreghi i dati per frame, così puoi salvare e caricare le informazioni a tua discrezione. Anche se i dettagli relativi al salvataggio e al caricamento non rientrano nell'ambito della release alpha dell'API JankStats, puoi visualizzare un'attività preliminare per aggregare i dati per frame in una raccolta più ampia utilizzando JankAggregatorActivity, disponibile nel nostro repository GitHub.

JankAggregatorActivity utilizza la classe JankStatsAggregator per sovrapporre il proprio meccanismo di generazione di report al meccanismo OnFrameListener JankStats, in modo da fornire un'astrazione di livello superiore per la generazione di report solo su una raccolta di informazioni che comprende molti frame.

Anziché creare direttamente un oggetto JankStats, JankAggregatorActivity crea un oggetto JankStatsAggregator, che crea internamente il proprio oggetto JankStats:

class JankAggregatorActivity : AppCompatActivity() {

    private lateinit var jankStatsAggregator: JankStatsAggregator


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // Metrics state holder can be retrieved regardless of JankStats initialization.
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // Initialize JankStats with an aggregator for the current window.
        jankStatsAggregator = JankStatsAggregator(window, jankReportListener)

        // Add the Activity name as state.
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
    }

In JankAggregatorActivity viene utilizzato un meccanismo simile per mettere in pausa e riprendere il monitoraggio, con l'aggiunta dell'evento pause() come indicatore per inviare un report con una chiamata a issueJankReport(), poiché le modifiche del ciclo di vita sembrano un momento appropriato per acquisire lo stato di jank nell'applicazione:

override fun onResume() {
    super.onResume()
    jankStatsAggregator.jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    // Before disabling tracking, issue the report with (optionally) specified reason.
    jankStatsAggregator.issueJankReport("Activity paused")
    jankStatsAggregator.jankStats.isTrackingEnabled = false
}

Il codice di esempio sopra riportato è tutto ciò di cui un'app ha bisogno per abilitare JankStats e ricevere dati dei frame.

Gestisci lo stato

È possibile che tu voglia chiamare altre API per personalizzare JankStats. Ad esempio, l'inserimento di informazioni sullo stato dell'app rende più utili i dati dei frame fornendo il contesto per i frame in cui si verifica il jank.

Questo metodo statico recupera l'oggetto MetricsStateHolder corrente per una determinata gerarchia di tipo View.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder

È possibile usare qualsiasi vista in una gerarchia attiva. Internamente, questo controllo verifica se esiste un oggetto Holder associato alla gerarchia delle viste. Queste informazioni vengono memorizzate nella cache in una visualizzazione in cima alla gerarchia. Se questo oggetto non esiste, getHolderForHierarchy() ne crea uno.

Il metodo getHolderForHierarchy() statico consente di evitare di dover memorizzare nella cache l'istanza del holder per un recupero successivo e semplifica il recupero di un oggetto di stato esistente da qualsiasi punto del codice (o anche del codice della libreria, che altrimenti non avrebbe accesso all'istanza originale).

Tieni presente che il valore restituito è un oggetto holder, non l'oggetto stato in sé. Il valore dell'oggetto stato all'interno del titolare viene impostato solo da JankStats. In altre parole, se un'applicazione crea un oggetto JankStats per la finestra contenente la gerarchia delle viste, l'oggetto di stato viene creato e impostato. Altrimenti, senza che JankStats monitori le informazioni, non è necessario l'oggetto stato e non è necessario che il codice dell'app o della libreria inserisca lo stato.

Questo approccio consente di recuperare un titolare che JankStats può compilare. Il codice esterno può richiedere il titolare in qualsiasi momento. I chiamanti possono memorizzare nella cache l'oggetto Holder leggero e utilizzarlo in qualsiasi momento per impostare lo stato, a seconda del valore della relativa proprietà state interna, come nel codice di esempio riportato di seguito, dove lo stato viene impostato solo quando la proprietà dello stato interno del titolare è diversa da null:

val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)

Per controllare lo stato della UI o dell'app, un'app può inserire (o rimuovere) uno stato con i metodi putState e removeState. JankStats registra il timestamp per queste chiamate. Se un frame si sovrappone all'ora di inizio e di fine dello stato, JankStats segnala le informazioni insieme ai dati temporali per il frame.

Per qualsiasi stato, aggiungi due informazioni: key (una categoria di stato, come "RecyclerView") e value (informazioni su cosa stava accadendo in quel momento, come lo "scorrimento").

Rimuovi gli stati che utilizzano il metodo removeState() quando quello stato non è più valido, per garantire che nei dati del frame non vengano segnalate informazioni errate o fuorvianti.

La chiamata a putState() con un key aggiunto in precedenza sostituisce il valore value esistente di quello stato con quello nuovo.

La versione putSingleFrameState() dell'API di stato aggiunge uno stato che viene registrato una sola volta al frame successivo segnalato. Il sistema lo rimuove automaticamente in seguito, assicurando che il tuo codice non presenti per sbaglio uno stato obsoleto. Tieni presente che non esiste un equivalente singleFrame di removeState(), poiché JankStats rimuove automaticamente gli stati dei frame singoli.

private val scrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        // check if JankStats is initialized and skip adding state if not
        val metricsState = metricsStateHolder?.state ?: return

        when (newState) {
            RecyclerView.SCROLL_STATE_DRAGGING -> {
                metricsState.putState("RecyclerView", "Dragging")
            }
            RecyclerView.SCROLL_STATE_SETTLING -> {
                metricsState.putState("RecyclerView", "Settling")
            }
            else -> {
                metricsState.removeState("RecyclerView")
            }
        }
    }
}

Tieni presente che la chiave utilizzata per gli stati dovrebbe essere abbastanza significativa da consentire un'analisi successiva. In particolare, poiché uno stato con lo stesso key di uno aggiunto in precedenza sostituirà il valore precedente, dovresti provare a utilizzare nomi key univoci per gli oggetti che potrebbero avere istanze diverse nell'app o nella libreria. Ad esempio, un'app con cinque diversi RecyclerView potrebbe voler fornire chiavi identificabili per ognuna invece di utilizzare semplicemente RecyclerView per ognuna e non essere in grado di capire facilmente nei dati risultanti a quale istanza fanno riferimento i dati del frame.

Euristica di Jank

Per regolare l'algoritmo interno al fine di determinare cosa è considerato jank, utilizza la proprietà jankHeuristicMultiplier.

Per impostazione predefinita, il sistema definisce jank come un frame che impiega il doppio del tempo per essere visualizzato rispetto alla frequenza di aggiornamento attuale. Non considera jank come elementi al di sopra della frequenza di aggiornamento perché le informazioni sul tempo di rendering dell'app non sono del tutto chiare. Pertanto, è preferibile aggiungere un buffer e segnalare i problemi solo quando causano problemi di prestazioni evidenti.

Entrambi questi valori possono essere modificati tramite questi metodi per adattarli alla situazione dell'app più da vicino o, in fase di test, per forzare il verificarsi o meno di jank, se necessario per il test.

Utilizzo in Jetpack Compose

Al momento è richiesta una configurazione minima per utilizzare JankStats in Compose. Per conservare PerformanceMetricsState durante le modifiche alla configurazione, ricorda quanto segue:

/**
 * Retrieve MetricsStateHolder from compose and remember until the current view changes.
 */
@Composable
fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {
    val view = LocalView.current
    return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) }
}

Per usare JankStats, aggiungi lo stato attuale a stateHolder come mostrato qui:

val metricsStateHolder = rememberMetricsStateHolder()

// Reporting scrolling state from compose should be done from side effect to prevent recomposition.
LaunchedEffect(metricsStateHolder, listState) {
    snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->
        if (isScrolling) {
            metricsStateHolder.state?.putState("LazyList", "Scrolling")
        } else {
            metricsStateHolder.state?.removeState("LazyList")
        }
    }
}

Per informazioni dettagliate sull'utilizzo di JankStats nell'applicazione Jetpack Compose, consulta la nostra app di esempio per le prestazioni.

Fornisci feedback

Condividi il tuo feedback e le tue idee con noi attraverso queste risorse:

Issue Tracker
Segnala i problemi per consentirci di correggerli.