پروفایل‌سازی مبتنی بر برنامه

این صفحه نحوه ثبت ردیابی سیستم با استفاده از API ProfilingManager را نشان می‌دهد.

ProfilingManager همچنین می‌تواند انواع دیگر پروفایل‌ها را ثبت کند. این فرآیند مشابه ثبت ردیابی سیستم است، اما هر نوع از سازنده متفاوتی استفاده می‌کند. پروفایل‌های پشتیبانی شده و سازندگان آنها عبارتند از:

  • ردیابی‌های سیستم: با استفاده از SystemTraceRequestBuilder ضبط می‌شوند، که برای تحلیل تأخیر و اشکال‌زدایی عملکرد عمومی مفید هستند.

  • داده‌های هیپ (Heap dumps): با استفاده از JavaHeapDumpRequestBuilder ضبط می‌شوند که برای تشخیص و بهینه‌سازی نشت حافظه مفید هستند.

  • پروفایل‌های Heap: با استفاده از HeapProfileRequestBuilder ضبط می‌شوند که برای بهینه‌سازی حافظه مفید هستند.

  • پروفایل‌های پشته فراخوانی: با استفاده از StackSamplingRequestBuilder ضبط شده‌اند که برای درک اجرای کد و تحلیل تأخیر مفید هستند.

وابستگی‌ها را اضافه کنید

برای بهترین تجربه با API ProfilingManager ، کتابخانه‌های Jetpack زیر را به فایل build.gradle.kts خود اضافه کنید.

کاتلین

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

گرووی

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

ردیابی سیستم را ثبت کنید

پس از افزودن وابستگی‌های مورد نیاز، از کد زیر برای ثبت ردیابی سیستم استفاده کنید. این مثال نحوه شروع یک جلسه پروفایلینگ از یک composable را نشان می‌دهد، در حالی که با خیال راحت عملیات سنگین را از نخ اصلی مدیریت می‌کند.

کاتلین

@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.
}

جاوا

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();
}

کد نمونه، جلسه پروفایلینگ را با طی کردن مراحل زیر تنظیم و مدیریت می‌کند:

  1. اجراکننده را تنظیم کنید. یک Executor ایجاد کنید تا نخی را که نتایج پروفایلینگ را دریافت می‌کند، تعریف کند. پروفایلینگ در پس‌زمینه اتفاق می‌افتد. استفاده از یک اجراکننده نخ غیر UI به جلوگیری از خطاهای عدم پاسخگویی برنامه (ANR) در صورت اضافه کردن پردازش بیشتر به فراخوانی بعدی کمک می‌کند.

  2. مدیریت نتایج پروفایلینگ. یک شیء Consumer<ProfilingResult> ایجاد کنید. سیستم از این شیء برای ارسال نتایج پروفایلینگ از ProfilingManager به برنامه شما استفاده می‌کند.

  3. درخواست پروفایلینگ را بسازید. یک SystemTraceRequestBuilder برای تنظیم جلسه پروفایلینگ خود ایجاد کنید. این سازنده به شما امکان می‌دهد تنظیمات ردیابی ProfilingManager سفارشی کنید. سفارشی‌سازی سازنده اختیاری است؛ در غیر این صورت، سیستم از تنظیمات پیش‌فرض استفاده می‌کند.

    • یک برچسب تعریف کنید. setTag() برای اضافه کردن یک برچسب به نام ردیابی استفاده کنید. این برچسب به شما در شناسایی ردیابی کمک می‌کند.
    • اختیاری: مدت زمان را تنظیم کنید. از setDurationMs() برای تعیین مدت زمان نمایش پروفایل بر حسب میلی‌ثانیه استفاده کنید. برای مثال، 60000 یک ردیابی 60 ثانیه‌ای تنظیم می‌کند. اگر CancellationSignal قبل از آن فعال نشود، ردیابی به طور خودکار پس از مدت زمان مشخص شده پایان می‌یابد.
    • یک سیاست بافر انتخاب کنید. از setBufferFillPolicy() برای تعریف نحوه ذخیره داده‌های ردیابی استفاده کنید. BufferFillPolicy.RING_BUFFER به این معنی است که وقتی بافر پر می‌شود، داده‌های جدید روی قدیمی‌ترین داده‌ها بازنویسی می‌شوند و سابقه‌ای مداوم از فعالیت‌های اخیر را نگه می‌دارند.
    • اندازه بافر را تنظیم کنید. از setBufferSizeKb() برای تعیین اندازه بافر برای ردیابی استفاده کنید که می‌توانید از آن برای کنترل اندازه فایل ردیابی خروجی استفاده کنید.
  4. اختیاری: مدیریت چرخه عمر جلسه. ایجاد یک CancellationSignal . این شیء به شما امکان می‌دهد جلسه پروفایلینگ را هر زمان که بخواهید متوقف کنید و کنترل دقیقی بر طول آن به شما می‌دهد.

  5. شروع و دریافت نتایج. وقتی requestProfiling() را فراخوانی می‌کنید، ProfilingManager یک جلسه پروفایلینگ را در پس‌زمینه آغاز می‌کند. پس از انجام پروفایلینگ، ProfilingResult به متد resultCallback#accept شما ارسال می‌کند. اگر پروفایلینگ با موفقیت به پایان برسد، ProfilingResult مسیری را که ردیابی در دستگاه شما ذخیره شده است از طریق ProfilingResult#getResultFilePath ارائه می‌دهد. می‌توانید این فایل را به صورت برنامه‌نویسی یا برای پروفایلینگ محلی، با اجرای adb pull <trace_path> از رایانه خود دریافت کنید.

  6. نقاط ردیابی سفارشی اضافه کنید. می‌توانید نقاط ردیابی سفارشی را در کد برنامه خود اضافه کنید. در مثال کد قبلی، بلوک trace("MyApp:HeavyOperation") { ... } یک برش سفارشی در پروفایل تولید شده ایجاد می‌کند.