ProfilingManager obsługuje przechwytywanie profili na podstawie aktywatorów systemowych. System zarządza procesem nagrywania i udostępnia wynikowy profil aplikacji.
Aktywatory są powiązane ze zdarzeniami, które mają kluczowe znaczenie dla wydajności. Profile zarejestrowane przez system zawierają szczegółowe informacje na potrzeby debugowania dotyczące głównych ścieżek użytkownika powiązanych z tymi aktywatorami.
Przechwytywanie danych historycznych
Wiele reguł wymaga analizy danych historycznych poprzedzających zdarzenie. Samo wywołanie jest często konsekwencją problemu, a nie jego główną przyczyną. Jeśli uruchomisz profil dopiero po wystąpieniu aktywatora, główna przyczyna może już zostać utracona.
Na przykład długo trwająca operacja w wątku UI powoduje błąd Aplikacja nie odpowiada (ANR). Zanim system wykryje błąd ANR i powiadomi aplikację, operacja może się już zakończyć. Rozpoczęcie profilowania w tym momencie nie pozwoli na wykrycie rzeczywistej blokady.
Dokładne przewidzenie, kiedy wystąpią niektóre aktywatory, jest niemożliwe, co uniemożliwia ręczne uruchomienie profilu z wyprzedzeniem.
Dlaczego warto korzystać z przechwytywania na podstawie aktywatora?
Głównym powodem używania aktywatorów profilowania jest przechwytywanie danych dotyczących nieprzewidywalnych zdarzeń, w przypadku których aplikacja nie może rozpocząć nagrywania ręcznie przed ich wystąpieniem. Aktywatory profilowania mogą służyć do:
- Debugowanie problemów z wydajnością: diagnozowanie błędów ANR, wycieków pamięci i innych problemów ze stabilnością.
- Optymalizowanie głównych ścieżek użytkownika: analizowanie i ulepszanie procesów, np. uruchamiania aplikacji.
- Poznawanie zachowań użytkowników: możesz uzyskiwać statystyki dotyczące zdarzeń, np. wyjść z aplikacji zainicjowanych przez użytkownika.
Konfigurowanie aktywatora
Poniższy kod pokazuje, jak zarejestrować regułę TRIGGER_TYPE_APP_FULLY_DRAWN i zastosować do niej ograniczanie liczby żądań.
Kotlin
fun recordWithTrigger() { val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java) val triggers = ArrayList<ProfilingTrigger>() val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN) .setRateLimitingPeriodHours(1) triggers.add(triggerBuilder.build()) val mainExecutor: Executor = Executors.newSingleThreadExecutor() val resultCallback = Consumer<ProfilingResult> { profilingResult -> if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) { Log.d( "ProfileTest", "Received profiling result file=" + profilingResult.resultFilePath ) setupProfileUploadWorker(profilingResult.resultFilePath) } else { Log.e( "ProfileTest", "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage ) } } profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback) profilingManager.addProfilingTriggers(triggers) }
Java
public void recordWithTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN); triggerBuilder.setRateLimitingPeriodHours(1); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = new Consumer<ProfilingResult>() { @Override public void accept(ProfilingResult profilingResult) { if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) { Log.d( "ProfileTest", "Received profiling result file=" + profilingResult.getResultFilePath()); setupProfileUploadWorker(profilingResult.getResultFilePath()); } else { Log.e( "ProfileTest", "Profiling failed errorcode=" + profilingResult.getErrorCode() + " errormsg=" + profilingResult.getErrorMessage()); } } }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
Kod wykonuje te czynności:
- Pobieranie menedżera: pobiera usługę
ProfilingManager. - Zdefiniuj aktywator: tworzy
ProfilingTriggerdlaTRIGGER_TYPE_APP_FULLY_DRAWN. To zdarzenie występuje, gdy aplikacja zgłasza, że zakończyła uruchamianie i jest interaktywna. - Ustaw limity: stosuje limit 1 godziny do tego konkretnego aktywatora (
setRateLimitingPeriodHours(1)). Zapobiega to rejestrowaniu przez aplikację więcej niż 1 profilu uruchamiania na godzinę. - Zarejestruj detektor: wywołuje
registerForAllProfilingResults, aby zdefiniować wywołanie zwrotne, które obsługuje wynik. To wywołanie zwrotne otrzymuje ścieżkę zapisanego profilu przezgetResultFilePath(). - Dodaj aktywatory: rejestruje listę aktywatorów w
ProfilingManagerza pomocąaddProfilingTriggers. - Uruchom zdarzenie: wywołuje
reportFullyDrawn(), co powoduje wysłanie zdarzeniaTRIGGER_TYPE_APP_FULLY_DRAWNdo systemu, który uruchamia zbieranie profili, zakładając, że ślad systemowy w tle był uruchomiony i dostępny jest limit ograniczenia liczby żądań. Ten opcjonalny krok pokazuje cały proces, ponieważ aplikacja musi wywołaćreportFullyDrawn()w przypadku tego aktywatora.
Pobieranie śladu
System zapisuje profile oparte na aktywatorach w tym samym katalogu co inne profile. Nazwa pliku wywołanych logów czasu ma ten format:
profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>
Możesz pobrać plik za pomocą ADB. Na przykład aby pobrać śledzenie systemu zarejestrowane za pomocą przykładowego kodu przy użyciu ADB, może to wyglądać tak:
adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace
Szczegółowe informacje o wizualizacji tych logów czasu znajdziesz w artykule Pobieranie i analizowanie danych profilowania.
Jak działa śledzenie w tle
Aby przechwytywać dane sprzed aktywacji, system operacyjny okresowo uruchamia śledzenie w tle. Jeśli podczas działania śledzenia w tle wystąpi zdarzenie aktywujące, a aplikacja jest zarejestrowana do jego obsługi, system zapisze profil śledzenia w katalogu aplikacji. Profil będzie wtedy zawierać informacje, które doprowadziły do aktywacji.
Po zapisaniu profilu system powiadamia aplikację za pomocą wywołania zwrotnego przekazanego do registerForAllProfilingResults. To wywołanie zwrotne podaje ścieżkę do przechwyconego profilu, do którego można uzyskać dostęp, wywołując funkcję ProfilingResult#getResultFilePath().
Aby zmniejszyć wpływ na wydajność urządzenia i czas pracy na baterii, system nie uruchamia śladów w tle w sposób ciągły. Zamiast tego używa metody próbkowania. System losowo rozpoczyna śledzenie w tle w ustalonym przedziale czasu (z minimalnym i maksymalnym czasem trwania). Losowe rozmieszczenie tych śladów poprawia pokrycie aktywacji.
Profile wywoływane przez system mają maksymalny rozmiar określony przez system, więc używają bufora pierścieniowego. Gdy bufor się zapełni, nowe dane śledzenia zastąpią najstarsze dane. Jak widać na rysunku 1, przechwycony log czasu może nie obejmować całego czasu nagrywania w tle, jeśli bufor się zapełni. Zamiast tego reprezentuje on najnowszą aktywność prowadzącą do aktywacji.
Wdrażanie ograniczania liczby żądań na podstawie reguł
Wyzwalacze o wysokiej częstotliwości mogą szybko wyczerpać limit ogranicznika szybkości aplikacji. Aby lepiej zrozumieć, jak działa ogranicznik liczby żądań, zapoznaj się z artykułem Jak działa ogranicznik liczby żądań. Aby zapobiec wyczerpaniu limitu przez jeden typ aktywatora, możesz wdrożyć ograniczanie liczby żądań dla konkretnego aktywatora.
ProfilingManager obsługuje zdefiniowane przez aplikację ograniczanie liczby żądań dla konkretnych reguł. Dzięki temu możesz dodać kolejną warstwę ograniczania na podstawie czasu oprócz istniejącego ogranicznika liczby żądań. Aby ustawić konkretny czas oczekiwania dla aktywatora, użyj interfejsu API setRateLimitingPeriodHours. Po upływie tego czasu możesz ponownie uruchomić tę funkcję.
Debugowanie aktywatorów lokalnie
Ślady w tle są uruchamiane w losowych momentach, więc debugowanie aktywatorów lokalnie jest trudne. Aby wymusić śledzenie w tle na potrzeby testów, użyj tego polecenia ADB:
adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>
To polecenie wymusza na systemie rozpoczęcie ciągłego śledzenia w tle dla określonego pakietu, dzięki czemu każdy aktywator może zebrać profil, jeśli pozwala na to ograniczanie liczby żądań.
Możesz też włączyć inne opcje debugowania, np. wyłączyć ogranicznik szybkości podczas debugowania lokalnego. Więcej informacji znajdziesz w artykule na temat poleceń debugowania na potrzeby lokalnego profilowania.