نمایه‌سازی مبتنی بر راه‌انداز

‫ProfilingManager از ضبط نمایه‌ها براساس راه‌اندازهای سیستم پشتیبانی می‌کند. سیستم فرایند ضبط را مدیریت می‌کند و نمایه حاصل را دراختیار برنامه شما قرار می‌دهد.

راه‌اندازها به رویدادهای حیاتی عملکرد مرتبط هستند. نمایه‌های ضبط‌شده توسط سیستم اطلاعات اشکال‌زدایی دقیقی برای مسیرهای حساس کاربر (CUJ) مرتبط با این راه‌اندازها ارائه می‌دهند.

ضبط داده‌های سابقه

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

برای مثال، عملیات طولانی‌مدت در رشته واسط کاربر باعث خطای برنامه پاسخ نمی‌دهد (ANR) می‌شود. زمانی که سیستم خطای ANR را شناسایی می‌کند و به برنامه اطلاع می‌دهد، ممکن است عملیات تمام شده باشد. شروع نمایه در آن لحظه باعث ازدست رفتن کار مسدودسازی واقعی می‌شود.

پیش‌بینی دقیق زمان وقوع برخی‌از راه‌اندازها غیرممکن است، بنابراین شروع دستی نمایه ازقبل امکان‌پذیر نیست.

چرا از ضبط براساس محرک استفاده کنیم؟

دلیل اصلی استفاده از راه‌اندازهای نمایه‌سازی، ضبط داده‌ها برای رویدادهای غیرقابل‌پیش‌بینی است که در آن‌ها برنامه نمی‌تواند قبل‌از وقوع این رویدادها به‌صورت دستی ضبط را شروع کند. از محرک‌های نمایه‌سازی می‌توان برای موارد زیر استفاده کرد:

  • عیب‌یابی مشکلات عملکرد: خطاهای ANR، نشت حافظه، و دیگر مشکلات پایداری را تشخیص دهید.
  • بهینه‌سازی سفرهای کاربر مهم: جریان‌ها را، برای مثال، راه‌اندازی برنامه، تجزیه‌وتحلیل و بهبود دهید.
  • درک رفتار کاربر: درباره رویدادها اطلاعات آماری کسب کنید، برای نمونه، خروج کاربر از برنامه.

راه‌اندازی راه‌انداز

کد زیر نحوه ثبت کردن برای راه‌انداز TRIGGER_TYPE_APP_FULLY_DRAWN و اعمال محدودیت نرخ برای آن را نشان می‌دهد.

کاتلین

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)

}

جاوا

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

}

کد این مراحل را انجام می‌دهد:

  1. دریافت مدیر: سرویس ProfilingManager را بازیابی می‌کند.
  2. تعریف راه‌انداز: ProfilingTrigger را برای TRIGGER_TYPE_APP_FULLY_DRAWN می‌سازد. این رویداد زمانی رخ می‌دهد که برنامه گزارش دهد راه‌اندازی آن تمام شده است و تعاملی است.
  3. تنظیم حدود نرخ: حد نرخ ۱ ساعته‌ای را برای این راه‌انداز خاص اعمال می‌کند (setRateLimitingPeriodHours(1)). این کار مانع از آن می‌شود که برنامه در هر ساعت بیش‌از یک نمایه راه‌اندازی را ضبط کند.
  4. ثبت شنونده: registerForAllProfilingResults را برای تعریف بازخوانی که نتیجه را مدیریت می‌کند فرا می‌خواند. این برگشتی مسیر نمایه ذخیره‌شده را ازطریق getResultFilePath() دریافت می‌کند.
  5. افزودن راه‌اندازها: فهرست راه‌انداز را با ProfilingManager بااستفاده از addProfilingTriggers ثبت می‌کند.
  6. رویداد آتش: reportFullyDrawn() را فرا می‌خواند، که رویداد TRIGGER_TYPE_APP_FULLY_DRAWN را به سیستم ارسال می‌کند و باعث راه‌اندازی جمع‌آوری نمایه می‌شود، با این فرض که ردیابی پس‌زمینه‌ای سیستم درحال اجرا است و سهمیه محدودکننده نرخ دردسترس است. این مرحله اختیاری جریان سرتاسری را نشان می‌دهد زیرا برنامه شما باید برای این راه‌انداز reportFullyDrawn() را فراخوانی کند.

بازیابی ردپا

سیستم نمایه‌های مبتنی بر راه‌انداز را در همان دایرکتوری نمایه‌های دیگر ذخیره می‌کند. نام فایل ردهای راه‌اندازی‌شده از این قالب پیروی می‌کند:

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

می‌توانید فایل را بااستفاده از ADB بکشید. برای مثال، برای کشیدن ردیابی سیستم ضبط‌شده با کد نمونه بااستفاده از ADB، ممکن است به این شکل باشد:

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

برای جزئیات مربوط به دیداری‌سازی این ردگیری‌ها، بازیابی و تجزیه‌وتحلیل داده‌های نمایه‌سازی را ببینید.

نحوه عملکرد ردیابی پس‌زمینه

برای ضبط داده‌های قبل‌از راه‌اندازی، سیستم‌عامل به‌طور دوره‌ای ردیابی پس‌زمینه‌ای را شروع می‌کند. اگر محرکی درحالی‌که این ردیابی پس‌زمینه‌ای فعال است رخ دهد و برنامه شما برای آن ثبت شده باشد، سیستم نمایه ردیابی را در دایرکتوری برنامه شما ذخیره می‌کند. سپس نمایه شامل اطلاعاتی خواهد بود که منجر به راه‌اندازی شده است.

پس‌از ذخیره شدن نمایه، سیستم بااستفاده از بازخوان ارائه‌شده به registerForAllProfilingResults به برنامه شما اطلاع می‌دهد. این برگشت مسیر نمایه ضبط‌شده را ارائه می‌دهد که با فراخوانی ProfilingResult#getResultFilePath() می‌توان به آن دسترسی داشت.

نموداری که نحوه عملکرد لحظه‌ای ردیابی پس‌زمینه را نشان می‌دهد، با یک بافر حلقوی که داده‌ها را قبل‌از رویداد راه‌انداز ضبط می‌کند.
شکل ۱: عکس‌های آنی ردیابی پس‌زمینه چگونه کار می‌کنند.

برای کاهش تأثیر بر عملکرد دستگاه و عمر باتری، سیستم ردیابی‌های پس‌زمینه را به‌طور مداوم اجرا نمی‌کند. درعوض، از روش نمونه‌گیری استفاده می‌کند. سیستم به‌طور تصادفی ردیابی پس‌زمینه‌ای را در یک بازه زمانی معین (با حداقل و حداکثر مدت) شروع می‌کند. فاصله‌گذاری تصادفی این ردگیری‌ها پوشش راه‌انداز را بهبود می‌بخشد.

نمایه‌های راه‌اندازی‌شده توسط سیستم دارای حداکثر اندازه تعریف‌شده توسط سیستم هستند، بنابراین از بافر حلقوی استفاده می‌کنند. وقتی بافر پر شود، داده‌های ردیابی جدید روی قدیمی‌ترین داده‌ها بازنویسی می‌شود. همان‌طور که در «شکل ۱» نشان داده شده است، اگر میان‌گیر پر شود، ردیابی ضبط‌شده ممکن است کل مدت زمان ضبط پس‌زمینه را پوشش ندهد؛ درعوض، ردیابی ضبط‌شده نشان‌دهنده جدیدترین فعالیت منتهی به راه‌انداز است.

پیاده‌سازی محدود کردن نرخ ویژه راه‌انداز

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

‫ProfilingManager از محدود کردن نرخ مختص محرک تعریف‌شده توسط برنامه پشتیبانی می‌کند. این کار به شما امکان می‌دهد علاوه‌بر محدودکننده نرخ موجود، لایه دیگری از محدودسازی مبتنی بر زمان اضافه کنید. از «میانای برنامه‌سازی کاربردی» setRateLimitingPeriodHours برای تنظیم زمان استراحت معین برای یک راه‌انداز استفاده کنید. پس‌از انقضای دوره استراحت، می‌توانید دوباره آن را راه‌اندازی کنید.

اشکال‌زدایی کردن محرک‌ها به‌صورت محلی

ازآنجایی‌که ردگیری‌های پس‌زمینه در زمان‌های تصادفی اجرا می‌شوند، اشکال‌زدایی کردن راه‌اندازها به‌صورت محلی دشوار است. برای اجبار کردن ردیابی پس‌زمینه برای آزمایش، از فرمان ADB زیر استفاده کنید:

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

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

همچنین می‌توانید گزینه‌های اشکال‌زدایی دیگر را فعال کنید، برای مثال، هنگام اشکال‌زدایی محلی، محدودکننده نرخ را غیرفعال کنید. برای اطلاعات بیشتر، دستورات اشکال‌زدایی برای نمایه‌سازی محلی را ببینید.