Profilowanie oparte na aktywatorach

ProfilingManager obsługuje rejestrowanie profili na podstawie wyzwalaczy systemowych. System zarządza procesem nagrywania i przekazuje wynikowy profil do Twojej aplikacji.

Aktywatory są powiązane ze zdarzeniami o kluczowym znaczeniu dla wydajności. Profile rejestrowane przez system zawierają szczegółowe informacje do debugowania dotyczące kluczowych ścieżek użytkownika powiązanych z tymi wyzwalaczami.

Przechwytywanie danych historycznych

Wiele wyzwalaczy wymaga analizy danych historycznych prowadzących do zdarzenia. Sama przyczyna jest często konsekwencją problemu, a nie jego główną przyczyną. Jeśli rozpoczniesz profilowanie dopiero po wystąpieniu reguły, główna przyczyna może już być utracona.

Na przykład długo trwająca operacja w wątku interfejsu powoduje błąd Aplikacja nie odpowiada (ANR). Zanim system wykryje błąd ANR i powiadomi o nim aplikację, operacja może się już zakończyć. Rozpoczęcie profilowania w tym momencie pomija rzeczywiste blokowanie.

Dokładne przewidzenie, kiedy wystąpią niektóre czynniki uruchamiające, jest niemożliwe, co uniemożliwia ręczne uruchomienie profilu z wyprzedzeniem.

Dlaczego warto korzystać z rejestrowania opartego na wyzwalaczach?

Głównym powodem używania wyzwalaczy profilowania jest rejestrowanie 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ą.
  • Optymalizuj główne ścieżki użytkownika: analizuj i ulepszaj ścieżki, np. uruchamianie aplikacji.
  • Poznawanie zachowań użytkowników: uzyskuj statystyki dotyczące zdarzeń, np. zamykania aplikacji przez użytkowników.
.

Konfigurowanie aktywatora

Poniższy kod pokazuje, jak zarejestrować wywołanie TRIGGER_TYPE_APP_FULLY_DRAWN i zastosować do niego ograniczenie liczby wywołań.

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:

  1. Pobierz menedżera: pobiera usługę ProfilingManager.
  2. Zdefiniuj aktywator: tworzy ProfilingTrigger dlaTRIGGER_TYPE_APP_FULLY_DRAWN. To zdarzenie występuje, gdy aplikacja zgłosi, że zakończyła uruchamianie i jest interaktywna.
  3. Ustawianie limitów częstotliwości: stosuje 1-godzinny limit częstotliwości do tego konkretnego wyzwalacza (setRateLimitingPeriodHours(1)). Zapobiega to rejestrowaniu przez aplikację więcej niż 1 profilu uruchamiania na godzinę.
  4. Zarejestruj odbiorcę: wywołuje registerForAllProfilingResults, aby zdefiniować wywołanie zwrotne, które obsługuje wynik. To wywołanie zwrotne otrzymuje ścieżkę zapisanego profilu za pomocą parametru getResultFilePath().
  5. Dodaj aktywatory: rejestruje listę aktywatorów w ProfilingManager za pomocą addProfilingTriggers.
  6. Uruchom zdarzenie: wywołuje funkcję reportFullyDrawn(), która wysyła zdarzenie TRIGGER_TYPE_APP_FULLY_DRAWN do systemu, co powoduje uruchomienie zbierania profilu, przy założeniu, że w tle działa śledzenie systemowe i dostępny jest limit ogranicznika szybkości. Ten opcjonalny krok pokazuje pełny proces, ponieważ aplikacja musi wywołać reportFullyDrawn() dla tego aktywatora.

Pobieranie śladu

System zapisuje profile oparte na wyzwalaczach w tym samym katalogu co inne profile. Nazwa pliku ze śladami wywołanymi ma ten format:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

Możesz pobrać plik za pomocą ADB. Aby na przykład pobrać ślad systemowy zarejestrowany za pomocą przykładowego kodu za pomocą ADB, możesz użyć tego polecenia:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

Więcej informacji o wizualizacji tych śladów znajdziesz w artykule Pobieranie i analizowanie danych profilowania.

Jak działa śledzenie w tle

Aby rejestrować dane sprzed wywołania, system operacyjny okresowo uruchamia śledzenie w tle. Jeśli podczas aktywnego śledzenia w tle wystąpi wyzwalacz, na który Twoja aplikacja jest zarejestrowana, system zapisze profil śledzenia w katalogu aplikacji. Profil będzie zawierać informacje o zdarzeniach, które doprowadziły do wywołania.

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 ProfilingResult#getResultFilePath().

Diagram pokazujący, jak działają zrzuty logów czasu w tle, z buforem pierścieniowym rejestrującym dane przed zdarzeniem wywołującym.
Ilustracja 1. Działanie zrzutów śledzenia w tle.

Aby zmniejszyć wpływ na wydajność urządzenia i czas pracy baterii, system nie śledzi w tle w sposób ciągły. Zamiast tego stosuje metodę próbkowania. System losowo rozpoczyna śledzenie w tle w określonym przedziale czasu (o minimalnym i maksymalnym czasie trwania). Losowe rozmieszczenie tych śladów zwiększa zasięg wyzwalania.

Profile wywoływane przez system mają zdefiniowany przez system maksymalny rozmiar, dlatego używają bufora pierścieniowego. Gdy bufor jest pełny, nowe dane śledzenia zastępują najstarsze dane. Jak widać na rysunku 1, zarejestrowany ślad może nie obejmować całego czasu trwania nagrywania w tle, jeśli bufor się zapełni. Zamiast tego przedstawia on najnowszą aktywność prowadzącą do wywołania.

Wdrażanie ograniczeń liczby żądań w przypadku poszczególnych wywołań

Triggery o wysokiej częstotliwości mogą szybko wyczerpać limit ogranicznika szybkości aplikacji. Aby lepiej zrozumieć ogranicznik szybkości, zapoznaj się z artykułem Jak działa ogranicznik szybkości. Aby zapobiec wyczerpaniu limitu przez jeden typ wyzwalacza, możesz wdrożyć ograniczenie liczby wywołań dla poszczególnych wyzwalaczy.

ProfilingManager obsługuje ograniczenie liczby wywołań zależne od wyzwalacza zdefiniowanego przez aplikację. Dzięki temu możesz dodać kolejną warstwę ograniczania na podstawie czasu oprócz istniejącego ogranicznika szybkości. Użyj interfejsu setRateLimitingPeriodHours API, aby ustawić konkretny czas odstępu dla aktywatora. Po upływie okresu oczekiwania możesz ponownie wywołać to działanie.

Debugowanie aktywatorów lokalnie

Ślady w tle są uruchamiane w losowych momentach, więc lokalne debugowanie wywołań jest trudne. Aby wymusić śledzenie w tle na potrzeby testowania, użyj tego polecenia ADB:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

To polecenie wymusza rozpoczęcie ciągłego śledzenia w tle dla określonego pakietu, co umożliwia każdemu aktywatorowi zbieranie profilu, jeśli zezwala na to ogranicznik szybkości.

Możesz też włączyć inne opcje debugowania, np. wyłączyć ogranicznik szybkości podczas debugowania lokalnego. Więcej informacji znajdziesz w artykule Polecenia debugowania do profilowania lokalnego.