Profilowanie oparte na aplikacjach

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

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

  • Ślady systemowe: rejestrowane za pomocą SystemTraceRequestBuilder, które są przydatne do analizy opóźnień i ogólnego debugowania wydajności.

  • Zrzuty sterty: rejestrowane za pomocą JavaHeapDumpRequestBuilder, które są przydatne do wykrywania wycieków pamięci i optymalizacji.

  • Profile sterty: rejestrowane za pomocą HeapProfileRequestBuilder, które są przydatne do optymalizacji pamięci.

  • Profile stosu wywołań: nagrywane za pomocą StackSamplingRequestBuilder, które są przydatne do analizowania wykonywania kodu i opóźnień.

Dodawanie zależności

Aby w pełni korzystać z interfejsu ProfilingManager API, dodaj do pliku build.gradle.kts te biblioteki Jetpack:

Kotlin

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

Dynamiczny

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

Nagrywanie śledzenia systemu

Po dodaniu wymaganych zależności użyj tego kodu, aby zarejestrować ślad systemowy. Ten przykład pokazuje, jak rozpocząć sesję profilowania z poziomu funkcji kompozycyjnej, bezpiecznie zarządzając złożonymi operacjami 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. Skonfiguruj wykonawcę. 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 pomaga uniknąć błędów typu „Aplikacja nie odpowiada” (ANR), jeśli później dodasz do wywołania zwrotnego więcej przetwarzania.

  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. Utwórz żądanie profilowania. Utwórz SystemTraceRequestBuilder, aby skonfigurować sesję profilowania. Ten kreator umożliwia dostosowywanie ustawień śledzeniaProfilingManager. Dostosowanie narzędzia do tworzenia jest opcjonalne. Jeśli tego nie zrobisz, system użyje ustawień domyślnych.

    • Zdefiniuj tag. Użyj ikony setTag(), aby dodać tag do nazwy śladu. Ten tag pomoże Ci zidentyfikować ślad.
    • Opcjonalnie: ustaw czas trwania. Użyj setDurationMs(), aby określić, jak długo ma trwać profilowanie (w milisekundach). Na przykład 60000 ustawia ślad o długości 60 sekund. Śledzenie kończy się automatycznie po określonym czasie trwania, jeśli przed jego upływem nie zostanie wywołane zdarzenie CancellationSignal.
    • Wybierz zasadę buforowania. Użyj parametru setBufferFillPolicy(), aby określić sposób przechowywania danych śledzenia. BufferFillPolicy.RING_BUFFER oznacza, że gdy bufor jest pełny, nowe dane zastępują najstarsze dane, dzięki czemu zachowywany jest ciągły zapis ostatniej aktywności.
    • Ustaw rozmiar bufora. Użyj setBufferSizeKb(), aby określić rozmiar bufora śledzenia, za pomocą którego możesz kontrolować rozmiar pliku wyjściowego śledzenia.
  4. Opcjonalnie: zarządzaj cyklem życia sesji. Utwórz CancellationSignal. Ten obiekt umożliwia zatrzymanie sesji profilowania w dowolnym momencie, co daje precyzyjną kontrolę nad jej długością.

  5. Rozpocznij i uzyskaj wyniki. Gdy zadzwonisz pod numer requestProfiling(),ProfilingManager rozpocznie sesję profilowania w tle. Po zakończeniu profilowania wysyła ProfilingResult do Twojej resultCallback#accept. Jeśli profilowanie zakończy się pomyślnie, ProfilingResult poda ścieżkę, w której ślad został zapisany na urządzeniu, za pomocą ProfilingResult#getResultFilePath. Ten plik możesz uzyskać programowo lub w przypadku profilowania lokalnego, uruchamiając adb pull <trace_path> na komputerze.

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