Uygulamaya dayalı profilleme

Android 15 (API düzeyi 35) ve sonraki sürümlerde, uygulama tabanlı profil oluşturma özelliği sayesinde uygulamanız, ProfilingManager API'sini veya AndroidX Core (androidx.core:core 1.15.0 veya sonraki sürümler) derleyicilerini kullanarak kodda desteklenen profil kayıtlarını açıkça başlatıp durdurabilir. Profil oluşturma zamanlamasını veya oturum parametrelerini (ör. süreler, arabellek politikaları ve örnekleme hızları) kontrol etmek ya da belirli bir kullanıcı yolculuğunu veya çalışma zamanı koşulunu hedeflemek istediğinizde doğrudan profil oluşturmayı kullanın.

Bağımlılık ekleme

ProfilingManager API'sini en iyi şekilde kullanmak için aşağıdaki Jetpack kitaplıklarını build.gradle.kts dosyanıza ekleyin.

Kotlin

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.5")
       implementation("androidx.core:core:1.19.1")
   }
   

Modern

   dependencies {
       implementation 'androidx.tracing:tracing-ktx:2.0.5'
       implementation 'androidx.core:core:1.19.1'
   }
   

Sistem izleme kaydetme

Gerekli bağımlılıkları ekledikten sonra sistem izi kaydetmek için aşağıdaki kodu kullanın. Bu örnekte, ana iş parçacığı dışındaki yoğun işlemleri güvenli bir şekilde yönetirken profil oluşturma oturumunun nasıl başlatılacağı gösterilmektedir.

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 profile.
            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 sampleRecordSystemTrace(Context context) {
  // Use the application context: requestProfiling resolves the ProfilingManager
  // system service from it, so there's no reason to hand it a short-lived Activity.
  Context appContext = context.getApplicationContext();

  // 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.
  profilingExecutor.execute(() -> {
    Executor callbackExecutor = Executors.newSingleThreadExecutor();
    Consumer<ProfilingResult> resultCallback = profilingResult -> {
      if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
        Log.d("ProfileTest", "Result file: " + profilingResult.getResultFilePath());
      } else {
        // errorMessage explains the failure (e.g., rate limiting); keep it.
        Log.e(
            "ProfileTest",
            "Profiling failed errorCode=" + profilingResult.getErrorCode()
                + " errorMessage=" + profilingResult.getErrorMessage());
      }
    };

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

    // 1. Start the session. This is asynchronous system IPC. The tracing
    //    engine takes a moment to start and allocate buffers.
    Profiling.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.
    try {
      Thread.sleep(STARTUP_PADDING_MS);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      stopSignal.cancel();
      return;
    }

    // 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. try/finally closes the
    //    section even if heavyOperation() throws.
    Trace.beginSection("MyApp:HeavyOperation");
    try {
      heavyOperation();
    } finally {
      Trace.endSection();
    }

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

// 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 static final long STARTUP_PADDING_MS = 100L;

// Single background thread that runs the profiling orchestration.
private final ExecutorService profilingExecutor = Executors.newSingleThreadExecutor();

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

Örnek kod, profil oluşturma oturumunu aşağıdaki adımlarda ayarlar ve yönetir:

  1. Yürütücüyü ayarlayın: Profil oluşturma sonuçlarını ana iş parçacığından almak için kullanıcı arayüzü olmayan bir Executor oluşturun.
  2. Profil oluşturma sonuçlarını işleme: ProfilingManager'ten tamamlanan sonucu veya hatayı almak için bir Consumer<ProfilingResult> geri çağırma işlevi oluşturun.
  3. Profil oluşturma isteğini oluşturun: Oturum ayarlarını özelleştirmek için bir SystemTraceRequestBuilder oluşturun (veya sistem varsayılanlarını kullanmak için özelleştirmeleri atlayın):

    • setTag(): ProfilingResult.getTag tarafından döndürülen ve çıkış dosya adına eklenen, arayan tarafından sağlanan bir etiket ekler (ilk 20 küçük harfli alfanümerik karakter ve kısa çizgiye kadar).
    • setDurationMs(): Maksimum kayıt süresini milisaniye cinsinden ayarlar (örneğin, 60 saniye için 60000).
    • setBufferFillPolicy(): Arabelleğin kapasite sınırlarını nasıl işlediğini tanımlar: BufferFillPolicy.RING_BUFFER, dolduğunda en eski verilerin üzerine yazar, BufferFillPolicy.DISCARD ise arabellek dolduğunda yeni verileri kaydetmeyi durdurur.
    • setBufferSizeKb(): Kayıt arabellek boyutunu kilobayt cinsinden ayarlar.
  4. İsteğe bağlı: Kaydı erken durdurun: Hedef işlem biter bitmez oturumu durdurmak, o ana kadar kaydedilen verileri kaydetmek ve ProfilingResult değerini geri çağırma işlevinize iletmek için bir CancellationSignal (stopSignal) iletin. Ayrıntılar için Etkin bir kaydı durdurma başlıklı makaleyi inceleyin.

  5. Başlatma ve sonuçları alma: Arka planda kaydı başlatmak için requestProfiling() işlevini çağırın. Oturum sona erdiğinde resultCallback.accept(), ProfilingResult alır:

    • İşlem başarılı olursa (ERROR_NONE) cihaz üzerindeki yolu almak için getResultFilePath() yöntemini çağırın. adb ile dosya alma veya WorkManager ile dosya yükleme talimatları için Profilleri alma ve yükleme başlıklı makaleyi inceleyin.
    • Hata durumunda getErrorCode() ve getErrorMessage() dosyalarını inceleyin. İstekler, sıklık sınırlaması ve diğer çeşitli nedenlerden dolayı başarısız olabilir.
    • Uygulamanız sonuç yayınlanmadan önce sonlandırılırsa sistem sonucu sıraya alır ve uygulamanız registerForAllProfilingResults ile bir genel dinleyici kaydettiğinde yayınlar.
  6. Özel izleme noktaları ekleme: Zaman çizelgesinin bölgelerini etiketlemek için işlemleri trace("...") { ... } (veya Trace.beginSection() / Trace.endSection()) ile sarmalayın.

Etkin bir kaydı durdurma

setDurationMs() süresinin dolmasını beklemek yerine bir etkileşim tamamlanır tamamlanmaz yakalamayı durdurmak için setCancellationSignal() veya requestProfiling() yöntemine bir CancellationSignal iletin. Hem CancellationSignal hem de setDurationMs() değerini belirtirseniz hangisi önce gerçekleşirse oturum sona erer. İkisini de belirtmezseniz sistem varsayılan bir süre uygular.

cancel() numarasını aradığınızda kayıt silinmez. Sistem, yakalama işlemini durdurur, o ana kadar toplanan verileri çıkartıp kaydeder ve ProfilingResult öğesini ERROR_NONE ile dinleyicinize gönderir.

Çağrı yığını örneklemesiyle sistem izlerini kaydetme

Android 17 (API düzeyi 37) ve sonraki sürümleri (veya güncellenmiş Profiling ana modülüne sahip Android 16) çalıştıran cihazlarda ProfilingManager, tek bir oturumda sistem izleme ile birlikte örneklenmiş çağrı yığınlarının toplanmasını destekler. Böylece, açık izleme araçları olmayan yöntemler için CPU çağrı yığınlarıyla birlikte iş parçacığı durumları, kareler ve özel izleme dilimleri gibi zaman çizelgesi etkinliklerini görebilirsiniz.

Bu örnekte, desteklenen profil türlerinden (PROFILING_TYPE_SYSTEM_TRACE) birini belirterek ve bir yapılandırma Bundle ileterek Jetpack oluşturucuları olmadan doğrudan ProfilingManager.requestProfiling yönteminin nasıl çağrılacağı da gösterilmektedir:

Kotlin

val profilingManager = context.getSystemService(ProfilingManager::class.java)

// Pass a Bundle to customize the capture, or pass null for default settings.
val config = Bundle().apply {
    putInt("KEY_DURATION_MS", 15000) // 15 seconds
    putInt("KEY_SIZE_KB", 20480) // 20 MB buffer size

    // Collect sampled CPU call stacks alongside the system trace.
    putBoolean("KEY_COLLECT_STACK_SAMPLING", true)
    putInt("KEY_FREQUENCY_HZ", 100) // Sampling frequency in Hz
    // Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls.
}
profilingManager?.requestProfiling(
    ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
    config,
    "trace-with-callstacks",
    null,
    callbackExecutor,
    resultCallback
)

Java

ProfilingManager profilingManager = context.getSystemService(ProfilingManager.class);
if (profilingManager == null) return;

// Pass a Bundle to customize the capture, or pass null for default settings.
Bundle config = new Bundle();
config.putInt("KEY_DURATION_MS", 15000); // 15 seconds
config.putInt("KEY_SIZE_KB", 20480); // 20 MB buffer size

// Collect sampled CPU call stacks alongside the system trace.
config.putBoolean("KEY_COLLECT_STACK_SAMPLING", true);
config.putInt("KEY_FREQUENCY_HZ", 100); // Sampling frequency in Hz
// Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls.

profilingManager.requestProfiling(
    ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
    config,
    "trace-with-callstacks",
    null,
    callbackExecutor,
    resultCallback
);

Elde edilen profili Perfetto kullanıcı arayüzünde açtığınızda, örneklenen çağrı yığınları uygulamanızın normal izleme dilimlerinin yanında özel Çağrı yığınları kanalları olarak görünür. Şekil 1'de gösterildiği gibi, ilgili zaman aralığı için örneklenen çağrı yığınlarının alev grafiğini görüntülemek üzere çağrı yığını parçasının bir bölgesini seçin.

Örneklenmiş çağrı yığını izleri, RenderThread'deki izleme dilimleri ve seçili alan için çağrı yığını alev grafiği içeren bir sistem izini gösteren Perfetto kullanıcı arayüzü.
Şekil 1. Perfetto kullanıcı arayüzünde çağrı yığını örneklemesiyle birleştirilmiş bir sistem izleme.

Java yığın dökümü yakalama

JavaHeapDumpRequestBuilder (veya PROFILING_TYPE_JAVA_HEAP_DUMP) kullanarak uygulamanızın Java belleğinin anlık görüntüsünü isteyebilirsiniz. Debug.dumpHprofData ile yakalanan standart .hprof yığın dökümlerinin aksine, ProfilingManager yığın dökümleri üretimde güvenli ve düşük ek yükle toplama için tasarlanmıştır:

  • Donmayan toplama: Sistem, uygulamanızın işleminin yazma sırasında kopyalama klonunu oluşturur ve yığın anlık görüntüsünü klonlanan işlemden toplar. Böylece, döküm diske yazılırken uygulamanız donmaz.
  • Yalnızca referans grafiği (ham nesne verisi yok): ProfilingManager, .hprof dosyası gibi ham bellek içeriklerini dökümlemek yerine sınıflar, nesne boyutları, referans zincirleri ve çöp toplama (GC) köklerini içeren bir .perfetto-java-heap-dump protobuf grafiği kaydeder. İlkel değerler, dize içerikleri ve dizi yükleri ise atlanır. Bu, kullanıcı gizliliğini korur ve dosya boyutunu küçültür. Tam döküm için ProfilingManager yığın dökümlerinin HPROF dökümlerinden farkı başlıklı makaleyi inceleyin.

Sistem izleme kaydetme bölümünde gösterilen Context, Executor ve resultCallback kurulumunu kullanarak JavaHeapDumpRequestBuilder ile bir istek oluşturun:

Kotlin

val heapDumpRequest = JavaHeapDumpRequestBuilder()
    .setTag("memory-leak-check")
    .build()

requestProfiling(
    appContext,
    heapDumpRequest,
    callbackExecutor,
    resultCallback
)

Java

ProfilingRequest heapDumpRequest = new JavaHeapDumpRequestBuilder()
    .setTag("memory-leak-check")
    .build();

Profiling.requestProfiling(
    getApplicationContext(),
    heapDumpRequest,
    callbackExecutor,
    resultCallback
);

Bellek sızıntısını teşhis etmek için Java yığın dökümünü analiz etme sürecinin tamamını adım adım incelemek isterseniz Örnek olay: OOM kilitlenmesinde hata ayıklama başlıklı makaleye göz atın.

Sonraki adımlar

Profilleri yakaladıktan sonra adb ile yerel olarak alın veya WorkManager ile arka ucunuza yükleyin.