Profilowanie oparte na aplikacjach

Na tej stronie dowiesz się, jak zarejestrować śledzenie systemu za pomocą interfejsu ProfilingManager API.

ProfilingManager może też rejestrować inne typy profili. Ten proces jest podobny do rejestrowania śledzenia systemu, ale każdy typ używa innego narzędzia do tworzenia. Obsługiwane profile i ich narzędzia do tworzenia:

  • Śledzenie systemu: rejestrowane za pomocą SystemTraceRequestBuilder. Przydaje się do analizy opóźnień i ogólnego debugowania wydajności.

  • Zrzuty sterty: rejestrowane za pomocą JavaHeapDumpRequestBuilder. Pomagają wykrywać wycieki pamięci i optymalizować jej wykorzystanie.

  • Profile sterty: rejestrowane za pomocą HeapProfileRequestBuilder. Przydają się do optymalizacji pamięci.

  • Profile stosu wywołań: rejestrowane za pomocą StackSamplingRequestBuilder. Przydają się do analizy wykonywania kodu i opóźnień.

Dodawanie zależności

Aby uzyskać najlepsze wrażenia z korzystania z interfejsu ProfilingManager API, dodaj te biblioteki Jetpack do pliku build.gradle.kts.

Kotlin

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.1")
       implementation("androidx.core:core:1.19.0")
   }
   

Dynamiczny

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.1'
       implementation 'androidx.core:core:1.19.0'
   }
   

Rejestrowanie śledzenia systemu

Po dodaniu wymaganych zależności użyj tego kodu, aby zarejestrować śledzenie systemu. Ten przykład pokazuje, jak rozpocząć sesję profilowania z poziomu elementu kompozycyjnego, bezpiecznie zarządzając operacjami wymagającymi dużej ilości zasobów poza wątkiem głównym.

Kotlin

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun ProfiledScreen(modifier: Modifier = Modifier) {
    // Use the application context: requestProfiling resolves the ProfilingManager
    // system service from it, so there's no reason to hand it a short-lived Activity.
    val appContext = LocalContext.current.applicationContext
    val scope = rememberCoroutineScope()

    Button(
        onClick = {
            // Run the orchestration off the main thread. Profiling a heavy operation
            // on the UI thread would freeze the UI (ANR) and distort the very metrics
            // you're trying to capture.
            //
            // Note: this scope is tied to composition. If the user leaves this screen
            // mid-session, the coroutine is cancelled and stopSignal.cancel() might not
            // run, but setDurationMs() acts as a safety net and ends the trace.
            scope.launch(Dispatchers.Default) {
                val callbackExecutor = Dispatchers.IO.asExecutor()
                val resultCallback = Consumer<ProfilingResult> { profilingResult ->
                    if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
                        Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}")
                    } else {
                        // errorMessage explains the failure (e.g., rate limiting); keep it.
                        Log.e(
                            "ProfileTest",
                            "Profiling failed errorCode=${profilingResult.errorCode} " +
                                "errorMessage=${profilingResult.errorMessage}"
                        )
                    }
                }

                val stopSignal = CancellationSignal()
                val requestBuilder = SystemTraceRequestBuilder().apply {
                    setCancellationSignal(stopSignal)
                    setTag("FOO") // Caller-supplied tag for identification.
                    setDurationMs(60000) // Hard cap: ends the session if cancel() never fires.
                    setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
                    setBufferSizeKb(32768)
                }

                // 1. Start the session. This is asynchronous system IPC. The tracing
                //    engine takes a moment to start and allocate buffers.
                requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback)

                // 2. The API exposes no "profiling started" signal, so pad with a short,
                //    best-effort delay before running the code you care about. This is
                //    approximate. Increase it on slower or heavily loaded devices.
                delay(STARTUP_PADDING_MS)

                // 3. The session is already recording every thread in your app. This slice
                //    doesn't scope what's captured. It just labels this region of the
                //    timeline so heavyOperation() is easier to find. trace { } closes the
                //    section even if the block throws.

                trace("MyApp:HeavyOperation") {
                    heavyOperation()
                }

                // 4. Stop recording. Until this fires or the setDurationMs() cap is
                //    reached (whichever comes first), the session keeps capturing app-wide
                //    activity.

                stopSignal.cancel()
            }
        }
    ) {
        Text("Run & Profile Heavy Operation")
    }
}

// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private const val STARTUP_PADDING_MS = 100L

fun heavyOperation() {
    // Background computations to profile.
}

Java

void heavyOperation() {
  // Computations you want to profile
}

void sampleRecordSystemTrace() {
  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());
          }
        }
      };
  CancellationSignal stopSignal = new CancellationSignal();

  SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
  requestBuilder.setCancellationSignal(stopSignal);
  requestBuilder.setTag("FOO");
  requestBuilder.setDurationMs(60000);
  requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
  requestBuilder.setBufferSizeKb(32768);
  Profiling.requestProfiling(getApplicationContext(), requestBuilder.build(), mainExecutor,
      resultCallback);

  // Wait some time for profiling to start.

  Trace.beginSection("MyApp:HeavyOperation");
  heavyOperation();
  Trace.endSection();

  // Once the interesting code section is profiled, stop profile
  stopSignal.cancel();
}

Przykładowy kod konfiguruje sesję profilowania i zarządza nią, wykonując te czynności:

  1. Konfigurowanie wykonawcy. Utwórz Executor, aby zdefiniować wątek, który będzie otrzymywać wyniki profilowania. Profilowanie odbywa się w tle. Użycie wykonawcy wątku innego niż wątek interfejsu użytkownika pomaga zapobiegać błędom typu „Aplikacja nie odpowiada” (ANR), jeśli później dodasz więcej przetwarzania do wywołania zwrotnego.

  2. Obsługa wyników profilowania. Utwórz obiekt Consumer<ProfilingResult>. System używa tego obiektu do wysyłania wyników profilowania z ProfilingManager z powrotem do aplikacji.

  3. Tworzenie żądania profilowania. Utwórz SystemTraceRequestBuilder, aby skonfigurować sesję profilowania. Ten narzędzie do tworzenia umożliwia dostosowanie ustawień śledzenia ProfilingManager. Dostosowanie narzędzia do tworzenia jest opcjonalne. Jeśli tego nie zrobisz, system użyje ustawień domyślnych.

    • Definiowanie tagu. Użyj setTag(), aby dodać tag do nazwy śledzenia. Ten tag pomaga zidentyfikować śledzenie.
    • Opcjonalnie: ustawianie czasu trwania. Użyj setDurationMs(), aby określić, jak długo ma trwać profilowanie (w milisekundach). Na przykład 60000 ustawia śledzenie na 60 sekund. Śledzenie automatycznie kończy się po określonym czasie, jeśli wcześniej nie zostanie wywołany CancellationSignal.
    • Wybieranie zasady buforowania. Użyj setBufferFillPolicy(), aby określić sposób przechowywania danych śledzenia. BufferFillPolicy.RING_BUFFER oznacza, że gdy bufor jest pełny, nowe dane zastępują najstarsze, co zapewnia ciągły zapis ostatniej aktywności.
    • Ustawianie rozmiaru bufora. Użyj setBufferSizeKb(), aby określić rozmiar bufora na potrzeby śledzenia, który możesz wykorzystać do kontrolowania rozmiaru wyjściowego pliku śledzenia.
  4. Opcjonalnie: zarządzanie cyklem życia sesji. Utwórz CancellationSignal. Ten obiekt umożliwia zatrzymanie sesji profilowania w dowolnym momencie, co pozwala precyzyjnie kontrolować jej długość.

  5. Rozpoczynanie i odbieranie wyników. Gdy wywołasz requestProfiling(), ProfilingManager rozpocznie sesję profilowania w tle. Po zakończeniu profilowania wysyła ProfilingResult do metody resultCallback#accept. Jeśli profilowanie zakończy się pomyślnie, the ProfilingResult poda ścieżkę, w której ślad został zapisany na urządzeniu za pomocą ProfilingResult#getResultFilePath. Możesz pobrać ten plik programowo lub, w przypadku profilowania lokalnego, uruchamiając adb pull <trace_path> na komputerze.

  6. Dodawanie niestandardowych punktów śledzenia. Możesz dodawać niestandardowe punkty śledzenia w kodzie aplikacji. W poprzednim przykładzie kodu blok trace("MyApp:HeavyOperation") { ... } tworzy niestandardowy wycinek w wygenerowanym profilu.