JankStats-Bibliothek

Mit der JankStats-Bibliothek können Sie Leistungsprobleme in Ihren Anwendungen verfolgen und analysieren. „Jank“ bezieht sich auf Anwendungsframes, deren Rendering zu lange dauert. Die JankStats-Bibliothek bietet Berichte zu den Verzögerungsstatistiken Ihrer App.

Rechte

JankStats baut auf den vorhandenen Android-Plattformfunktionen auf, einschließlich der FrameMetrics API unter Android 7 (API-Level 24) und höher oder OnPreDrawListener auf früheren Versionen. Diese Mechanismen können Anwendungen dabei helfen zu verfolgen, wie lange Frames dauern. Die JanksStats-Bibliothek bietet zwei zusätzliche Funktionen, die sie dynamischer und benutzerfreundlicher machen: Jank-Heuristiken und UI-Status.

Jank-Heuristik

Sie können FrameMetrics für die Verfolgung der Frame-Dauer verwenden, FrameMetrics bietet jedoch keine Unterstützung beim Ermitteln der tatsächlichen Verzögerung. JankStats verfügt jedoch über konfigurierbare interne Mechanismen, um zu ermitteln, wann eine Verzögerung auftritt. Dadurch werden die Berichte sofort nützlicher.

UI-Status

Sie müssen häufig den Kontext von Leistungsproblemen in Ihrer App kennen. Wenn Sie beispielsweise eine komplexe Multiscreen-App entwickeln, die FrameMetrics verwendet und Sie feststellen, dass Ihre App oft extrem langsame Frames hat, sollten Sie diese Informationen in einen Kontext stellen, indem Sie wissen, wo das Problem aufgetreten ist, was der Nutzer getan hat und wie Sie es replizieren können.

JankStats löst dieses Problem durch die Einführung einer state API, über die Sie mit der Bibliothek kommunizieren können, um Informationen zur App-Aktivität bereitzustellen. Wenn JankStats Informationen zu einem fehlerhaften Frame protokolliert, wird der aktuelle Status der Anwendung in Verzögerungsberichten aufgeführt.

Nutzung

Um mit der Verwendung von JankStats zu beginnen, müssen Sie die Bibliothek für jedes Window instanziieren und aktivieren. Jedes JankStats-Objekt erfasst nur Daten innerhalb eines Window. Für die Instanziierung der Bibliothek sind eine Window-Instanz und ein Listener OnFrameListener erforderlich. Beide werden verwendet, um Messwerte an den Client zu senden. Der Listener wird mit FrameData für jeden Frame aufgerufen und gibt Folgendes an:

  • Beginn des Frames
  • Werte für die Dauer
  • Gibt an, ob der Frame als Verzögerung angesehen werden soll
  • Eine Reihe von String-Paaren mit Informationen zum Anwendungsstatus während des Frames.

Um JankStats nützlicher zu machen, sollten Anwendungen die Bibliothek mit relevanten UI-Statusinformationen für die Berichterstellung in FrameData füllen. Dies ist über die PerformanceMetricsState API (nicht direkt von JankStats) möglich, in der sich die gesamte Zustandsverwaltungslogik und die gesamten APIs befinden.

Initialisierung

Fügen Sie der Gradle-Datei zuerst die JankStats-Abhängigkeit hinzu, um die JankStats-Bibliothek zu verwenden:

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

Initialisieren und aktivieren Sie als Nächstes JankStats für jeden Window. Sie sollten das JankStats-Tracking auch pausieren, wenn eine Aktivität in den Hintergrund verschoben wird. Erstellen und aktivieren Sie das JankStats-Objekt in Ihren Aktivitätsüberschreibungen:

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)
        // ...
    }

Im obigen Beispiel werden Statusinformationen über die aktuelle Aktivität eingefügt, nachdem das JankStats-Objekt erstellt wurde. Alle zukünftigen FrameData-Berichte, die für dieses JankStats-Objekt erstellt werden, enthalten jetzt auch Aktivitätsinformationen.

Die Methode JankStats.createAndTrack verweist auf ein Window-Objekt, das ein Proxy für die Ansichtshierarchie innerhalb dieses Windows sowie für das Window selbst ist. jankFrameListener wird im selben Thread aufgerufen, mit dem diese Informationen intern von der Plattform an JankStats gesendet werden.

Rufen Sie isTrackingEnabled = true auf, um das Tracking und die Berichterstellung für ein JankStats-Objekt zu aktivieren. Das ist zwar standardmäßig aktiviert, wird aber durch das Pausieren einer Aktivität deaktiviert. In diesem Fall müssen Sie das Tracking wieder aktivieren, bevor Sie fortfahren. Rufen Sie isTrackingEnabled = false auf, um das Tracking zu beenden.

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

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

Berichte

Die JankStats-Bibliothek meldet Ihr gesamtes Daten-Tracking für jeden Frame an OnFrameListener für aktivierte JankStats-Objekte. Apps können diese Daten speichern und aggregieren, um sie zu einem späteren Zeitpunkt hochzuladen. Weitere Informationen finden Sie in den Beispielen im Abschnitt Aggregation.

Sie müssen das OnFrameListener erstellen und angeben, damit Ihre App die Berichte pro Frame erhalten kann. Dieser Listener wird für jeden Frame aufgerufen, um laufende Verzögerungsdaten für Apps bereitzustellen.

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())
}

Der Listener stellt Frame-Informationen zur Verzögerung mit dem Objekt FrameData bereit. Dies enthält die folgenden Informationen zum angeforderten Frame:

  • isjank: ein boolesches Flag, das angibt, ob im Frame eine Verzögerung aufgetreten ist.
  • frameDurationUiNanos: Dauer des Frames in Nanosekunden.
  • frameStartNanos: Zeit, zu der der Frame begann (in Nanosekunden).
  • states: Status der App während des Frames.

Wenn Sie Android 12 (API-Level 31) oder höher verwenden, können Sie so mehr Daten zur Framedauer verfügbar machen:

Verwenden Sie StateInfo im Listener, um Informationen zum Anwendungsstatus zu speichern.

Beachten Sie, dass OnFrameListener im selben Thread aufgerufen wird, der intern verwendet wird, um die Informationen pro Frame an JankStats zu senden. Unter Android 6 (API-Level 23) und niedriger ist dies der Hauptthread (UI). Unter Android 7 (API-Level 24) und höher wird dieser Thread für FrameMetrics erstellt und verwendet. In beiden Fällen ist es wichtig, den Callback zu verarbeiten und schnell zurückzugeben, um Leistungsprobleme bei diesem Thread zu vermeiden.

Beachten Sie außerdem, dass das im Callback gesendete FrameData-Objekt für jeden Frame wiederverwendet wird, damit keine neuen Objekte für die Datenberichte zugewiesen werden müssen. Das bedeutet, dass Sie diese Daten kopieren und an anderer Stelle im Cache speichern müssen, da das Objekt als Status und veraltet gelten soll, sobald der Callback zurückgegeben wird.

Aggregieren

Wahrscheinlich möchten Sie, dass Ihr App-Code die Daten pro Frame aggregiert, sodass Sie die Informationen nach eigenem Ermessen speichern und hochladen können. Obwohl Details zum Speichern und Hochladen über den Umfang der JankStats API-Alphaversion hinausgehen, können Sie eine vorläufige Aktivität zum Aggregieren von Daten pro Frame in einer größeren Sammlung mit JankAggregatorActivity ansehen, das in unserem GitHub-Repository verfügbar ist.

JankAggregatorActivity verwendet die Klasse JankStatsAggregator, um ihren eigenen Berichterstellungsmechanismus auf den Mechanismus von JankStats OnFrameListener zu schichten. Dies ermöglicht eine allgemeine Abstraktion für die Berichterstellung nur für eine Sammlung von Informationen, die viele Frames umfasst.

Anstatt ein JankStats-Objekt direkt zu erstellen, erstellt JankAggregatorActivity ein JankStatsAggregator-Objekt, das intern ein eigenes JankStats-Objekt erstellt:

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 wird ein ähnlicher Mechanismus verwendet, um das Tracking zu pausieren und fortzusetzen. Dabei wird das Ereignis pause() hinzugefügt, um einen Bericht mit einem Aufruf von issueJankReport() auszugeben, da Lebenszyklusänderungen ein angemessener Zeitpunkt sind, um den Status der Verzögerung in der Anwendung zu erfassen:

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
}

Der obige Beispielcode ist alles, was eine App benötigt, um JankStats zu aktivieren und Frame-Daten zu empfangen.

Status verwalten

Es ist möglich, dass Sie andere APIs aufrufen möchten, um JankStats anzupassen. Das Einfügen von App-Statusinformationen macht beispielsweise die Framedaten hilfreicher, da Kontext für die Frames bereitgestellt wird, in denen eine Verzögerung auftritt.

Diese statische Methode ruft das aktuelle MetricsStateHolder-Objekt für eine bestimmte View-Hierarchie ab.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder

Sie können jede Ansicht in einer aktiven Hierarchie verwenden. Intern wird damit geprüft, ob der Ansichtshierarchie ein vorhandenes Holder-Objekt zugeordnet ist. Diese Informationen werden in einer Ansicht oben in dieser Hierarchie im Cache gespeichert. Wenn kein solches Objekt vorhanden ist, erstellt getHolderForHierarchy() eines.

Mit der statischen getHolderForHierarchy()-Methode können Sie vermeiden, dass die Halterinstanz zum späteren Abrufen im Cache gespeichert werden muss. Außerdem lässt sich ein vorhandenes Statusobjekt an einer beliebigen Stelle im Code (oder sogar von einem Bibliothekscode, der sonst keinen Zugriff auf die ursprüngliche Instanz hätte) abrufen.

Beachten Sie, dass der Rückgabewert ein Halterobjekt und nicht das Statusobjekt selbst ist. Der Wert des Statusobjekts im Halter wird nur von JankStats festgelegt. Wenn also eine Anwendung ein JankStats-Objekt für das Fenster erstellt, das diese Ansichtshierarchie enthält, wird das Statusobjekt erstellt und festgelegt. Andernfalls besteht kein Bedarf für das Zustandsobjekt und der App- oder Bibliothekscode muss den Zustand nicht einschleusen, ohne dass JankStats die Informationen verfolgt.

Mit diesem Ansatz kann ein Halter abgerufen werden, den JankStats dann befüllen kann. Externer Code kann den Inhaber jederzeit abfragen. Aufrufer können das einfache Holder-Objekt im Cache speichern und jederzeit verwenden, um den Status abhängig vom Wert seiner internen state-Eigenschaft festzulegen, wie im Beispielcode unten zu sehen. Dabei wird der Status nur festgelegt, wenn die interne Statuseigenschaft des Inhabers nicht null ist:

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

Zur Steuerung des UI-/App-Status kann eine App mit den Methoden putState und removeState einen Zustand einschleusen oder entfernen. JankStats protokolliert den Zeitstempel für diese Aufrufe. Wenn sich ein Frame mit der Start- und Endzeit des Status überschneidet, meldet JanStats diese Statusinformationen zusammen mit den Zeitdaten für den Frame.

Fügen Sie für jeden Bundesstaat zwei Informationen hinzu: key (eine Bundeslandkategorie, z. B. „RecyclerView“) und value (Informationen über das, was zu diesem Zeitpunkt passiert ist, z. B. „Scrollen“).

Sie können Status mit der Methode removeState() entfernen, wenn dieser Status nicht mehr gültig ist, um sicherzustellen, dass keine falschen oder irreführenden Informationen mit Frame-Daten gemeldet werden.

Beim Aufrufen von putState() mit einem zuvor hinzugefügten key wird der vorhandene value dieses Status durch den neuen ersetzt.

Die putSingleFrameState()-Version der State API fügt auf dem nächsten gemeldeten Frame einen Status hinzu, der nur einmal protokolliert wird. Danach wird er vom System automatisch entfernt, damit Ihr Code nicht versehentlich einen veralteten Status aufweist. Es gibt kein SingleFrame-Äquivalent zu removeState(), da JankStats Status mit einem einzelnen Frame automatisch entfernt.

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")
            }
        }
    }
}

Der für Zustände verwendete Schlüssel sollte bedeutend genug sein, um eine spätere Analyse zu ermöglichen. Da ein Zustand mit der gleichen key wie ein zuvor hinzugefügter Bundesstaat den früheren Wert ersetzt, sollten Sie versuchen, für Objekte, die in Ihrer Anwendung oder Bibliothek unterschiedliche Instanzen haben, eindeutige key-Namen zu verwenden. Beispiel: Eine App mit fünf verschiedenen RecyclerViews möchte für jede einen identifizierbaren Schlüssel bereitstellen, anstatt einfach RecyclerView für jede Ansicht zu verwenden. Dann ist es nicht möglich, aus den resultierenden Daten leicht zu erkennen, auf welche Instanz sich die Frame-Daten beziehen.

Jank-Heuristik

Verwenden Sie das Attribut jankHeuristicMultiplier, um den internen Algorithmus zur Bestimmung, was als Verzögerung gilt, anzupassen.

Standardmäßig definiert das System eine Verzögerung als einen Frame, der doppelt so lange zum Rendern benötigt wie mit der aktuellen Aktualisierungsrate. Eine Verzögerung wird über die Aktualisierungsrate hinaus nicht berücksichtigt, da die Informationen zur Renderingzeit der Anwendung nicht eindeutig sind. Daher ist es besser, einen Puffer hinzuzufügen und Probleme nur zu melden, wenn sie deutliche Leistungsprobleme verursachen.

Beide Werte können mit diesen Methoden an die Situation der App angepasst oder beim Testen geändert werden, um eine Verzögerung zu erzwingen oder nicht, wie für den Test erforderlich.

Verwendung in Jetpack Compose

Derzeit ist nur sehr wenig Einrichtung erforderlich, um JankStats in Compose zu verwenden. Um die PerformanceMetricsState bei Konfigurationsänderungen beizubehalten, müssen Sie es so speichern:

/**
 * 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) }
}

Um JankStats zu verwenden, fügen Sie den aktuellen Status zu stateHolder hinzu, wie hier gezeigt:

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")
        }
    }
}

Ausführliche Informationen zur Verwendung von JankStats in Ihrer Jetpack Compose-Anwendung finden Sie in unserer Beispielanwendung für die Leistung.

Feedback geben

Teilen Sie uns Ihr Feedback und Ihre Ideen über diese Ressourcen mit:

Problemverfolgung
Melden Sie Probleme, damit wir sie beheben können.