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

در Android 16 (سطح API‏ ۳۶) و بالاتر، ProfilingManager به شما امکان می‌دهد نمایه‌ها را به‌طور خودکار در پاسخ به راه‌اندازهای سیستم ضبط کنید، مانند خطاهای برنامه پاسخ نمی‌دهد (ANR)، ازکارافتادگی‌های «کمبود حافظه» (OutOfMemoryError)، یا رویدادهای راه‌اندازی برنامه. چون نمی‌توانید پیش‌بینی کنید که بسیاری از این رویدادها چه زمانی در میدان رخ می‌دهند، شروع دستی ضبط ازقبل غیرعملی است. با نمایه‌سازی مبتنی بر راه‌انداز، سیستم ضبط را مدیریت می‌کند و نمایه حاصل را به برنامه‌تان ارائه می‌دهد تا بتوانید:

  • اشکال‌زدایی مشکلات پایداری: خطاهای ANR غیرقابل‌پیش‌بینی و نشت حافظه را عیب‌یابی کنید.
  • بهینه‌سازی مسیرهای قطعی کاربر (CUJ): جریان‌هایی مانند راه‌اندازی برنامه (TRIGGER_TYPE_COLD_START و TRIGGER_TYPE_APP_FULLY_DRAWN) را تحلیل کنید.
  • درک رفتار کاربر: رویدادهایی مانند خروج از برنامه که توسط کاربر آغاز شده است را بررسی کنید.

برای مشاهده فهرست کامل راه‌اندازهای دردسترس، ProfilingTrigger را ببینید.

انواع راه‌اندازها

راه‌اندازها ازنظر نحوه ضبط نمایه توسط سیستم هنگام وقوع رویداد متفاوت هستند.

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

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

راه‌اندازهایی که ضبط جدیدی را شروع می‌کنند

راه‌اندازهای زیر به ردیابی پس‌زمینه وابسته نیستند. درعوض، سیستم هنگام وقوع رویداد، ضبط نمایه جدیدی را شروع می‌کند:

راه‌اندازهایی که رفتار ضبط آن‌ها براساس ناهنجاری متفاوت است

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

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

کد زیر نحوه ثبت‌نام برای راه‌انداز TRIGGER_TYPE_APP_FULLY_DRAWN و تنظیم دوره سرد شدن برای آن را نشان می‌دهد.

کاتلین

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val callbackExecutor: 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(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

جاوا

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 callbackExecutor = 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(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

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

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

چرخه حیات راه‌انداز

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

زمان لغو ثبت راه‌اندازها

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

راه‌اندازها در شرایط زیر لغو ثبت می‌شوند:

  • هدف‌یابی گروه‌های هم‌سنجی یا بازگرداندن پرچم‌های از راه دور: ممکن است بااستفاده از پیکربندی از راه دور یا پرچم‌های آزمایش A/B، نمایه‌سازی بخش‌بندی‌شده در گروه‌های هم‌سنجی انجام دهید. هرگاه دستگاهی از گروه نمونه‌برداری‌شده خارج شود یا پرچمی خاموش شود، راه‌اندازها را لغو ثبت‌نام کنید.
  • پایان دادن به پویش اشکال‌زدایی: وقتی نمایه‌های کافی برای تشخیص مشکل یا ارائه راه‌حل جمع‌آوری کردید، راه‌انداز را بردارید. لغو ثبت‌نام راه‌اندازهای غیرضروری باعث حفظ فضای ذخیره‌سازی دستگاه و سهمیه محدودکننده نرخ برنامه‌تان برای دیگر درخواست‌های نمایه‌سازی می‌شود.

چرخه حیات فراخوانی

برخلاف درخواست‌های نمایه‌سازی مستقیم، نمایه‌های مبتنی بر راه‌انداز فقط نتایج را به شنونده‌ای جهانی که در registerForAllProfilingResults ثبت شده است ارائه می‌دهند. در فراخوانی برگشتی، ProfilingResult.getTriggerType را فراخوانی کنید تا مشخص شود کدام راه‌انداز ضبط را آغاز کرده است، و ProfilingResult.getResultFilePath را فراخوانی کنید تا مکان فایل نمایه ذخیره‌شده را پیدا کنید. برای جزئیات مربوط به نام فایل‌های نمایه راه‌انداز، بیرون کشیدن فایل‌ها با adb، و بارگذاری نتایج در پس‌زمینه، به بازیابی و بارگذاری نمایه‌ها مراجعه کنید.

اگرچه راه‌اندازها در بازراه‌اندازی‌های برنامه و بازراه‌اندازی‌های سیستم باقی می‌مانند، اما فراخوانی برگشتی نتیجه شما باقی نمی‌ماند. پس‌از تکمیل راه‌اندازی برنامه، فراخوانی برگشتی‌تان را در Executor پس‌زمینه ثبت کنید (تا راه‌اندازی سرد را کند نکنید) تا نمایه‌ها را در هر دو سناریو زیر دریافت کنید:

  • درحالی‌که برنامه‌تان درحال اجرا است: برای راه‌اندازهایی که برنامه‌تان را متوقف نمی‌کنند یا ازکار نمی‌اندازند، وقتی راه‌انداز رخ می‌دهد و نمایه ذخیره می‌شود، سیستم فراخوانی برگشتی شما را در Executor ارائه‌شده فراخوانی می‌کند.
  • پس‌از خاتمه برنامه: برای راه‌اندازهایی که اغلب شامل خاتمه برنامه می‌شوند یا پیش‌از آن رخ می‌دهند (مانند TRIGGER_TYPE_ANR، TRIGGER_TYPE_OOM، یا TRIGGER_TYPE_KILL_RECENTS)، یا اگر برنامه شما قبل‌از پایان ضبط متوقف شود، سیستم نمایه را در پس‌زمینه ذخیره می‌کند و نتیجه را در صف بازراه‌اندازی‌های برنامه و راه‌اندازی‌های مجدد سیستم قرار می‌دهد. سیستم نتایج صف‌بندی‌شده تحویل‌نشده را تا ۷ روز نگه می‌دارد و دفعه بعد که برنامه‌تان اجرا می‌شود و registerForAllProfilingResults را فراخوانی می‌کند، آن‌ها را تحویل می‌دهد.

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

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

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

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

تنظیم زمان استراحت مخصوص راه‌انداز

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

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

ازآنجایی‌که ردیابی‌های پس‌زمینه در فواصل زمانی تصادفی در تولید اجرا می‌شوند، آزمایش راه‌اندازها به‌صورت محلی مستلزم اجبار ردیابی پس‌زمینه مداوم و دور زدن محدودکننده نرخ سطح سیستم برای بسته شما است (هرگونه زمان استراحت برای هر راه‌انداز که با setRateLimitingPeriodHours تنظیم شده باشد همچنان اعمال می‌شود و rate_limiter.disabled فقط برای تماس‌های requestProfiling برنامه‌محور اعمال می‌شود).

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