Biblioteka JankStats ułatwia śledzenie i analizowanie problemów z wydajnością aplikacji. Jank odnosi się do ramek aplikacji, których renderowanie trwa zbyt długo, a biblioteka JankStats zawiera raporty o statystykach zacięć aplikacji.
Uprawnienia
JankStats wykorzystuje istniejące funkcje platformy Androida, w tym interfejs FrameMetrics API w Androidzie 7 (poziom API 24) i nowszy lub OnPreDrawListener we wcześniejszych wersjach. Mechanizmy te pomagają aplikacjom śledzić czas potrzebny na zrealizowanie klatek. Biblioteka JanksStats oferuje 2 dodatkowe funkcje, które zwiększają jej dynamikę i są łatwiejsze w użyciu: heurystyczną heurystykę i stan interfejsu użytkownika.
Heurystyka Jank
Chociaż możesz używać FrameMetrics do śledzenia czasu trwania klatki, nie zapewnia on pomocy w określaniu rzeczywistego zacięcia. JankStats ma jednak konfigurowalne, wewnętrzne mechanizmy, które określają, kiedy występuje zacinanie, dzięki czemu raporty są bardziej przydatne od razu.
Stan interfejsu
Często jest potrzebna znajomość kontekstu problemów z wydajnością aplikacji. Jeśli na przykład masz złożoną aplikację na wiele urządzeń, która korzysta z FrameMetrics, i stwierdzisz, że często zawiera ona wyjątkowo nieładne ramki, musisz umieścić te informacje w kontekście, wiedząc, gdzie wystąpił problem, co robił użytkownik i jak go odtworzyć.
JankStats rozwiązuje ten problem, wprowadzając interfejs API state
, który umożliwia komunikowanie się z biblioteką w celu dostarczania informacji o aktywności w aplikacji. Gdy JankStats rejestruje informacje o nieprawidłowej ramce, w raportach o zacięciu dołączany jest bieżący stan aplikacji.
Wykorzystanie
Aby zacząć korzystać z JankStats, utwórz instancję i włącz bibliotekę w przypadku każdego obiektu Window
. Każdy obiekt JankStats śledzi dane tylko w obrębie Window
. Utworzenie instancji biblioteki wymaga instancji Window
wraz z detektorem OnFrameListener
. Oba narzędzia służą do wysyłania danych do klienta. Detektor jest wywoływany z parametrem FrameData
w każdej klatce i szczegółowo podaje:
- Czas rozpoczęcia ramki
- Wartości czasu trwania
- Określa, czy ramka ma być uważana za zaciętą
- Zbiór par ciągów tekstowych zawierających informacje o stanie aplikacji w ramce
Aby JankStats były bardziej przydatne, aplikacje powinny wypełniać bibliotekę odpowiednimi informacjami o stanie interfejsu na potrzeby raportowania w FrameData. Możesz to zrobić za pomocą interfejsu API PerformanceMetricsState
(nie bezpośrednio JankStats), w którym działają wszystkie interfejsy API i logika zarządzania stanem.
Inicjacja
Aby zacząć korzystać z biblioteki JankStats, najpierw dodaj zależność JankStats do pliku Gradle:
implementation "androidx.metrics:metrics-performance:1.0.0-beta01"
Następnie zainicjuj i włącz JankStats dla każdego zdarzenia Window
. Należy też wstrzymać śledzenie
JankStats, gdy aktywność przechodzi w tle. Utwórz i włącz obiekt JankStats w zastąpieniach aktywności:
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)
// ...
}
W przykładzie powyżej wstrzyknięto informacje o stanie bieżącej aktywności po utworzeniu obiektu JankStats. Wszystkie przyszłe raporty FrameData utworzone dla tego obiektu JankStats zawierają teraz także informacje o aktywności.
Metoda JankStats.createAndTrack
odwołuje się do obiektu Window
, który jest serwerem proxy hierarchii widoków w obrębie tego obiektu Window
, a także dla samego obiektu Window
. Funkcja jankFrameListener
jest wywoływana w tym samym wątku, który służy do przekazywania informacji z platformy do JankStats wewnętrznie.
Aby włączyć śledzenie i raportowanie w przypadku dowolnego obiektu JankStats, wywołaj isTrackingEnabled = true
. Chociaż jest ona domyślnie włączona,
wstrzymanie aktywności wyłącza śledzenie. W takim przypadku, zanim przejdziesz dalej,
ponownie włącz śledzenie. Aby zatrzymać śledzenie, zadzwoń pod numer isTrackingEnabled = false
.
override fun onResume() {
super.onResume()
jankStats.isTrackingEnabled = true
}
override fun onPause() {
super.onPause()
jankStats.isTrackingEnabled = false
}
Zgłaszanie
Biblioteka JankStats przekazuje w przypadku włączonych obiektów JankStats wszystkie śledzenie danych (dla każdej klatki) w elemencie OnFrameListener
. Aplikacje mogą przechowywać i agregować te dane,
aby móc je później przesłać. Więcej informacji znajdziesz na przykładach w sekcji Agregacja.
Aby aplikacja otrzymywała raporty o klatce, musisz utworzyć i podać parametr OnFrameListener
. Ten detektor jest wywoływany przy każdej ramce, aby dostarczyć aplikacjom dane o bieżącym zacięciu.
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())
}
Detektor dostarcza informacje z poszczególnych klatek o zacięciu z obiektem FrameData
. Zawiera on te informacje o żądanej ramce:
isjank
: flaga wartości logicznej wskazująca, czy w klatce wystąpiło zacięcie.frameDurationUiNanos
: Czas trwania klatki (w nanosekundach).frameStartNanos
: czas rozpoczęcia klatki (w nanosekundach).states
: stan aplikacji w klatce.
Jeśli używasz Androida w wersji 12 (poziom interfejsu API 31) lub nowszej, możesz skorzystać z tych opcji, aby udostępnić więcej danych o czasie trwania klatek:
FrameDataApi24
umożliwia wyświetlanieframeDurationCpuNanos
czasu spędzonego w częściach klatki, które nie są procesorami graficznymi.FrameDataApi31
udostępnia opcjęframeOverrunNanos
, która pokazuje czas po upływie terminu klatki, który upłynął.
Użyj StateInfo
w detektorze, aby przechowywać informacje o stanie aplikacji.
Pamiętaj, że funkcja OnFrameListener
jest wywoływana w tym samym wątku używanym wewnętrznie do przesyłania informacji o ramkach do JankStats.
W Androidzie w wersji 6 (poziom interfejsu API 23) lub starszych jest to wątek główny (UI).
W Androidzie w wersji 7 (poziom interfejsu API 24) i nowszych jest to wątek utworzony i używany przez FrameMetrics. W obu przypadkach ważne jest, by wykonać wywołanie zwrotne i szybko wrócić, by uniknąć problemów z wydajnością w danym wątku.
Obiekt FrameData wysłany w wywołaniu zwrotnym jest używany ponownie w każdej ramce, aby uniknąć konieczności przydzielania nowych obiektów na potrzeby raportowania danych. Oznacza to, że musisz skopiować te dane i zachować je w pamięci podręcznej w innym miejscu, ponieważ po przywróceniu wywołania zwrotnego obiekt powinien zostać uznany za statyczny i nieaktualny.
Zbieram
Kod aplikacji powinien agregować dane z każdej ramki, co pozwala zapisywać i przesyłać informacje według własnego uznania. Chociaż szczegóły dotyczące zapisywania i przesyłania wykraczają poza zakres wersji alfa interfejsu JankStats API, ale możesz wyświetlić działanie wstępne dotyczące agregacji danych z poszczególnych ramek w większej kolekcji za pomocą usługi JankAggregatorActivity
dostępnej w naszym repozytorium GitHub.
JankAggregatorActivity
używa klasy JankStatsAggregator
do nałożenia własnego mechanizmu raportowania na mechanizm JankStats OnFrameListener
, aby zapewnić wyższy poziom abstrakcji w przypadku raportowania tylko zbioru informacji, który obejmuje wiele ramek.
Zamiast bezpośrednio tworzyć obiekt JankStats, JankAggregatorActivity
tworzy obiekt JankStatsAggregator, który wewnętrznie tworzy własny obiekt 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)
}
Podobny mechanizm jest używany w JankAggregatorActivity
do wstrzymywania i wznawiania śledzenia. Dodanie zdarzenia pause()
jako sygnału do wygenerowania raportu z wywołaniem metody issueJankReport()
, ponieważ zmiany w cyklu życia wydają się odpowiednim momentem do rejestrowania stanu zacięcia w aplikacji:
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
}
Przykładowy kod powyżej to wszystko, czego potrzebuje aplikacja, aby włączyć JankStats i otrzymywać dane ramek.
Zarządzaj stanem
Możesz wywołać inne interfejsy API, aby dostosować JankStats. Na przykład wstrzykiwanie informacji o stanie aplikacji zwiększa przydatność danych ramek, ponieważ dostarcza kontekst dla ramek, w których dochodzi do zacinania.
Ta metoda statyczna pobiera bieżący obiekt MetricsStateHolder
z danej hierarchii widoków.
PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder
Można użyć dowolnego widoku w aktywnej hierarchii. Wewnętrznie sprawdza to, czy z hierarchią widoku jest powiązany istniejący obiekt Holder
. Informacje te są przechowywane w pamięci podręcznej w widoku u góry tej hierarchii. Jeśli taki obiekt nie istnieje, getHolderForHierarchy()
go utworzy.
Metoda statycznej getHolderForHierarchy()
pozwala uniknąć konieczności zapisywania instancji instancji w pamięci podręcznej w celu późniejszego pobrania danych i ułatwia pobieranie istniejących obiektów stanu z dowolnego miejsca w kodzie (a nawet kodu biblioteki, który w innym przypadku nie miałby dostępu do pierwotnej instancji).
Pamiętaj, że zwracana wartość to obiekt zastępczy, a nie sam obiekt stanu. Wartość obiektu stanu w elemencie jest ustawiana tylko przez JankStats. Oznacza to, że jeśli aplikacja tworzy obiekt JankStats dla okna zawierającego hierarchię widoku, obiekt stanu jest tworzony i ustawiany. W przeciwnym razie bez śledzenia informacji przez JankStats nie jest potrzebny obiekt State ani wstrzykiwany kod aplikacji czy biblioteki.
Takie podejście umożliwia uzyskanie właściciela,
który może następnie wypełnić JankStats. Kod zewnętrzny może poprosić o właściciela w każdej chwili. Wywołujący mogą buforować uproszczony obiekt Holder
i używać go w dowolnym momencie do ustawienia stanu, w zależności od wartości jego wewnętrznej właściwości state
, jak w przykładowym kodzie poniżej, gdzie stan jest ustawiany tylko wtedy, gdy właściwość wewnętrznego stanu właściciela nie ma wartości null:
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
Aby kontrolować stan UI/aplikacji, aplikacja może wstrzykiwać (lub usuwać) stan za pomocą metod putState
i removeState
. JankStats rejestruje sygnaturę czasową tych wywołań. Jeśli ramka pokrywa się z czasem rozpoczęcia i zakończenia stanu,
JankStats podaje w raportach informacje oraz dane o czasie wyświetlania ramki.
Dla każdego stanu dodaj 2 informacje: key
(kategorię stanu, np. „RecyclerView”) i value
(informację o tym, co działo się w danym momencie, np. „scrolling”).
Usuwaj stany za pomocą metody removeState()
, gdy są one już nieważne, aby mieć pewność, że nieprawidłowe lub wprowadzające w błąd informacje nie są zgłaszane razem z danymi ramek.
Wywołanie elementu putState()
za pomocą dodanego wcześniej elementu key
zastępuje dotychczasowy element value
w danym stanie nowym.
Wersja interfejsu putSingleFrameState()
State API dodaje stan, który jest rejestrowany tylko raz, w przypadku następnej raportowanej klatki. Następnie system automatycznie go usuwa, dzięki czemu kod nie zawiera przypadkiem przestarzałego stanu. Pamiętaj, że nie ma odpowiednika elementu removeState()
w pojedynczej klatce, bo JankStats automatycznie usuwa stany jednoklatkowe.
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")
}
}
}
}
Pamiętaj, że klucz używany w przypadku stanów powinien być na tyle istotny, by umożliwić późniejszą analizę. W szczególności stan z tą samą wartością key
co ten, który został dodany wcześniej, zastąpi tę wcześniejszą wartość, dlatego zalecamy używanie unikalnych nazw key
w przypadku obiektów, które mogą mieć inne instancje w aplikacji lub bibliotece. Na przykład aplikacja z 5 różnymi obiektami RecyclerView może chcieć udostępnić możliwe do zidentyfikowania klucze w przypadku każdego z nich, zamiast korzystać z funkcji RecyclerView
w każdym z nich. W takiej sytuacji trudno jest stwierdzić w wynikowych danych, do której instancji odnoszą się dane ramki.
Heurystyka Jank
Aby dostosować wewnętrzny algorytm określający, co jest uważane za zacinanie, użyj właściwości jankHeuristicMultiplier
.
Domyślnie system definiuje zacinanie jako klatki, której renderowanie trwa 2 razy dłużej niż obecnie. Nie traktuje zacięcia jako nic związanego z częstotliwością odświeżania, ponieważ informacje o czasie renderowania aplikacji nie są do końca jasne. Dlatego warto dodać bufor i zgłaszać problemy tylko wtedy, gdy powodują one zauważalne problemy z wydajnością.
Obie te wartości można zmienić za pomocą tych metod, aby lepiej dopasować się do sytuacji w aplikacji, lub podczas testowania w celu wymuszenia lub niezatrzymania zacięcia, co jest konieczne do przeprowadzenia testu.
Wykorzystanie w Jetpack Compose
Aby korzystać z JankStats w narzędziu do tworzenia wiadomości, nie musisz jeszcze przeprowadzać konfiguracji.
Aby zachować PerformanceMetricsState
po wprowadzeniu zmian w konfiguracji, zapamiętaj je w ten sposób:
/**
* 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) }
}
Aby korzystać z JankStats, dodaj do parametru stateHolder
bieżący stan w ten sposób:
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")
}
}
}
Szczegółowe informacje o korzystaniu z JankStats w aplikacji Jetpack Compose znajdziesz w naszej przykładowej aplikacji wydajności.
Prześlij opinię
Podziel się z nami swoją opinią i pomysłami, korzystając z tych zasobów:
- Śledzenie problemów
- Zgłoś problemy, żebyśmy mogli naprawić błędy.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Tworzenie profili podstawowych {:#using-profile-rules}
- Argumenty instrumentacji mikrotestów
- Argumenty instrumentacji makroporównawczej