کتابخانه JankStats

کتابخانه JankStats به شما کمک می کند تا مشکلات عملکرد برنامه های خود را پیگیری و تجزیه و تحلیل کنید. Jank به فریم های برنامه ای اطلاق می شود که رندر آنها خیلی طول می کشد و کتابخانه JankStats گزارش هایی در مورد آمار jank برنامه شما ارائه می دهد.

قابلیت ها

JankStats بر روی قابلیت‌های پلتفرم اندروید موجود، از جمله FrameMetrics API در اندروید 7 (سطح API 24) و بالاتر یا OnPreDrawListener در نسخه‌های قبلی، ساخته شده است. این مکانیسم ها می توانند به برنامه ها کمک کنند تا مدت زمان تکمیل فریم ها را پیگیری کنند. کتابخانه JanksStats دو قابلیت اضافی را ارائه می دهد که پویاتر و استفاده از آن را آسان تر می کند: jank heuristics و UI State.

اکتشافی جانک

در حالی که می توانید از FrameMetrics برای ردیابی مدت زمان فریم استفاده کنید، FrameMetrics هیچ کمکی در تعیین jank واقعی ارائه نمی دهد. با این حال، JankStats دارای مکانیسم‌های داخلی و قابل تنظیم برای تعیین زمان وقوع jank است که گزارش‌ها را فوراً مفیدتر می‌کند.

وضعیت رابط کاربری

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

JankStats این مشکل را با معرفی یک API state حل می کند که به شما امکان می دهد با کتابخانه ارتباط برقرار کنید تا اطلاعاتی درباره فعالیت برنامه ارائه دهید. هنگامی که JankStats اطلاعات مربوط به یک فریم janky را ثبت می کند، وضعیت فعلی برنامه را در گزارش های jank گنجانده است.

استفاده

برای شروع استفاده از JankStats، کتابخانه را برای هر Window نمونه سازی کرده و فعال کنید. هر شی JankStats داده ها را فقط در یک Window ردیابی می کند. نمونه‌سازی کتابخانه به یک نمونه Window به همراه یک شنونده OnFrameListener نیاز دارد که هر دو برای ارسال معیارها به مشتری استفاده می‌شوند. شنونده با FrameData در هر فریم فراخوانی می شود و موارد زیر را شرح می دهد:

  • زمان شروع فریم
  • مقادیر مدت زمان
  • این که آیا فریم باید jank در نظر گرفته شود یا نه
  • مجموعه ای از جفت رشته ها حاوی اطلاعاتی در مورد وضعیت برنامه در طول فریم

برای مفیدتر کردن JankStats، برنامه‌ها باید کتابخانه را با اطلاعات وضعیت رابط کاربری مربوطه برای گزارش در FrameData پر کنند. شما می توانید این کار را از طریق PerformanceMetricsState API (نه به طور مستقیم JankStats) انجام دهید، جایی که تمام منطق مدیریت ایالت و API ها در آن زندگی می کنند.

مقداردهی اولیه

برای شروع استفاده از کتابخانه JankStats، ابتدا وابستگی JankStats را به فایل Gradle خود اضافه کنید:

implementation "androidx.metrics:metrics-performance:1.0.0-beta01"

سپس، JankStats را برای هر Window مقداردهی اولیه و فعال کنید. همچنین باید ردیابی JankStats را هنگامی که یک فعالیت به پس‌زمینه می‌رود، متوقف کنید. شی JankStats را در موارد لغو فعالیت خود ایجاد و فعال کنید:

class JankLoggingActivity : AppCompatActivity() {

    private lateinit var jankStats: JankStats


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // metrics state holder can be retrieved regardless of JankStats initialization
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // initialize JankStats for current window
        jankStats = JankStats.createAndTrack(window, jankFrameListener)

        // add activity name as state
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
        // ...
    }

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

متد JankStats.createAndTrack یک ارجاع به یک شی Window می گیرد که یک پروکسی برای سلسله مراتب View در داخل آن Window و همچنین برای خود Window است. jankFrameListener در همان رشته ای فراخوانی می شود که برای ارائه آن اطلاعات از پلتفرم به JankStats به صورت داخلی استفاده می شود.

برای فعال کردن ردیابی و گزارش روی هر شی JankStats، isTrackingEnabled = true را فراخوانی کنید. اگرچه به طور پیش‌فرض فعال است، توقف یک فعالیت ردیابی را غیرفعال می‌کند. در این صورت، قبل از ادامه، مطمئن شوید که ردیابی را دوباره فعال کرده اید. برای توقف ردیابی، با isTrackingEnabled = false تماس بگیرید.

override fun onResume() {
    super.onResume()
    jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    jankStats.isTrackingEnabled = false
}

گزارش دهی

کتابخانه JankStats تمام ردیابی داده های شما را برای هر فریم به OnFrameListener برای اشیاء JankStats فعال گزارش می دهد. برنامه‌ها می‌توانند این داده‌ها را برای آپلود در زمان بعدی ذخیره و جمع‌آوری کنند. برای اطلاعات بیشتر، به نمونه های ارائه شده در بخش تجمیع نگاهی بیندازید.

برای دریافت گزارش‌های هر فریم، باید OnFrameListener برای برنامه خود ایجاد و عرضه کنید. این شنونده در هر فریمی فراخوانی می‌شود تا داده‌های jank مداوم را به برنامه‌ها ارائه کند.

private val jankFrameListener = JankStats.OnFrameListener { frameData ->
    // A real app could do something more interesting, like writing the info to local storage and later on report it.
    Log.v("JankStatsSample", frameData.toString())
}

شنونده اطلاعات هر فریم در مورد jank را با شی FrameData ارائه می دهد. این شامل اطلاعات زیر در مورد فریم درخواستی است:

  • isjank : یک پرچم بولی که نشان می دهد آیا jank در فریم رخ داده است یا خیر.
  • frameDurationUiNanos : مدت زمان فریم (بر حسب نانوثانیه).
  • frameStartNanos : زمانی که فریم شروع شد (در نانوثانیه).
  • states : وضعیت برنامه شما در طول فریم.

اگر از Android 12 (سطح API 31) یا بالاتر استفاده می‌کنید، می‌توانید از موارد زیر برای نمایش داده‌های بیشتر درباره مدت زمان فریم استفاده کنید:

از StateInfo در شنونده برای ذخیره اطلاعات مربوط به وضعیت برنامه استفاده کنید.

توجه داشته باشید که OnFrameListener در همان رشته ای فراخوانی می شود که به صورت داخلی برای ارائه اطلاعات هر فریم به JankStats استفاده می شود. در اندروید نسخه 6 (سطح API 23) و پایین تر، موضوع اصلی (UI) است. در اندروید نسخه 7 (سطح API 24) و بالاتر، این رشته ای است که برای FrameMetrics ایجاد شده و مورد استفاده قرار می گیرد. در هر صورت، رسیدگی به تماس و بازگشت سریع برای جلوگیری از مشکلات عملکرد در آن موضوع مهم است.

همچنین، توجه داشته باشید که شی FrameData ارسال شده در فراخوانی مجدداً در هر فریم استفاده می شود تا از تخصیص اشیاء جدید برای گزارش داده جلوگیری شود. این بدان معنی است که شما باید آن داده ها را در جای دیگری کپی و کش کنید زیرا به محض بازگشت تماس، آن شی باید ثابت و منسوخ در نظر گرفته شود.

تجمیع

احتمالاً می خواهید کد برنامه شما داده های هر فریم را جمع آوری کند، که به شما امکان می دهد اطلاعات را به صلاحدید خود ذخیره و آپلود کنید. اگرچه جزئیات مربوط به ذخیره و آپلود فراتر از محدوده انتشار آلفا JankStats API است، می‌توانید با استفاده از JankAggregatorActivity موجود در مخزن GitHub، یک Activity مقدماتی برای جمع‌آوری داده‌های هر فریم در مجموعه‌ای بزرگ‌تر مشاهده کنید.

JankAggregatorActivity از کلاس JankStatsAggregator برای لایه بندی مکانیسم گزارش خود در بالای مکانیسم JankStats OnFrameListener استفاده می کند تا انتزاع سطح بالاتری را برای گزارش تنها مجموعه ای از اطلاعات ارائه دهد که فریم های زیادی را در بر می گیرد.

به جای ایجاد مستقیم یک شی JankStats، JankAggregatorActivity یک شی JankStatsAggregator ایجاد می کند که شی JankStats خود را در داخل ایجاد می کند:

class JankAggregatorActivity : AppCompatActivity() {

    private lateinit var jankStatsAggregator: JankStatsAggregator


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // Metrics state holder can be retrieved regardless of JankStats initialization.
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // Initialize JankStats with an aggregator for the current window.
        jankStatsAggregator = JankStatsAggregator(window, jankReportListener)

        // Add the Activity name as state.
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
    }

مکانیزم مشابهی در JankAggregatorActivity برای توقف و ازسرگیری ردیابی استفاده می‌شود، با افزودن رویداد pause() به عنوان سیگنالی برای صدور گزارش همراه با فراخوانی به issueJankReport() ، زیرا تغییرات چرخه عمر زمان مناسبی برای ثبت وضعیت به نظر می‌رسد. jank در برنامه:

override fun onResume() {
    super.onResume()
    jankStatsAggregator.jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    // Before disabling tracking, issue the report with (optionally) specified reason.
    jankStatsAggregator.issueJankReport("Activity paused")
    jankStatsAggregator.jankStats.isTrackingEnabled = false
}

کد مثال بالا تمام چیزی است که یک برنامه برای فعال کردن JankStats و دریافت داده های فریم نیاز دارد.

ایالت را مدیریت کند

ممکن است بخواهید از API های دیگر برای سفارشی کردن JankStats فراخوانی کنید. به عنوان مثال، تزریق اطلاعات وضعیت برنامه، داده‌های فریم را با فراهم کردن زمینه برای آن فریم‌هایی که در آنها jank رخ می‌دهد، مفیدتر می‌کند.

این روش استاتیک، شی MetricsStateHolder فعلی را برای یک سلسله مراتب View معین بازیابی می کند.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder

هر نما در یک سلسله مراتب فعال ممکن است استفاده شود. در داخل، این بررسی می کند که آیا یک شی Holder موجود مرتبط با آن سلسله مراتب view وجود دارد یا خیر. این اطلاعات در یک نمای در بالای آن سلسله مراتب ذخیره می شود. اگر چنین شی ای وجود نداشته باشد، getHolderForHierarchy() یکی را ایجاد می کند.

متد static getHolderForHierarchy() به شما این امکان را می دهد که از کش کردن نمونه نگهدارنده در جایی برای بازیابی بعدی خودداری کنید و بازیابی یک شیء حالت موجود را از هر کجای کد (یا حتی کد کتابخانه ای که در غیر این صورت به نمونه اصلی).

توجه داشته باشید که مقدار بازگشتی یک شی نگهدارنده است، نه خود شیء حالت. مقدار شیء حالت در داخل دارنده فقط توسط JankStats تنظیم می شود. یعنی اگر یک برنامه یک شی JankStats برای پنجره حاوی سلسله مراتب view ایجاد کند، آنگاه شی state ایجاد و تنظیم می شود. در غیر این صورت، بدون اینکه JankStats اطلاعات را ردیابی کند، نیازی به آبجکت state وجود ندارد و نیازی نیست کد برنامه یا کتابخانه برای تزریق وضعیت.

این رویکرد بازیابی دارنده‌ای را ممکن می‌سازد که JankStats سپس می‌تواند آن را پر کند. کد خارجی می تواند در هر زمانی از دارنده درخواست کند. تماس‌گیرندگان می‌توانند شی سبک‌وزن Holder را در حافظه پنهان نگه دارند و از آن در هر زمان برای تنظیم وضعیت استفاده کنند، بسته به مقدار ویژگی state داخلی آن، مانند کد مثال زیر، که در آن حالت فقط زمانی تنظیم می‌شود که ویژگی حالت داخلی دارنده غیر تهی باشد:

val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)

برای کنترل وضعیت UI/app، یک برنامه می‌تواند یک حالت را با متدهای putState و removeState تزریق (یا حذف) کند. JankStats مهر زمانی این تماس ها را ثبت می کند. اگر یک فریم با زمان شروع و پایان حالت همپوشانی داشته باشد، JankStats گزارش می‌کند که اطلاعات حالت را همراه با داده‌های زمان‌بندی برای فریم نشان می‌دهد.

برای هر وضعیتی، دو بخش از اطلاعات را اضافه کنید: key (یک دسته از وضعیت، مانند "RecyclerView") و value (اطلاعات در مورد آنچه در آن زمان اتفاق می افتاد، مانند "پیمایش").

برای اطمینان از اینکه اطلاعات اشتباه یا گمراه‌کننده با داده‌های فریم گزارش نمی‌شوند، حالت‌ها را با استفاده از روش removeState() زمانی که آن حالت دیگر معتبر نیست حذف کنید.

فراخوانی putState() با key که قبلا اضافه شده بود، value موجود آن حالت را با مقدار جدید جایگزین می کند.

نسخه putSingleFrameState() از state API حالتی را اضافه می کند که فقط یک بار در فریم گزارش شده بعدی ثبت می شود. پس از آن، سیستم به طور خودکار آن را حذف می کند و اطمینان حاصل می کند که به طور تصادفی وضعیت منسوخ در کد خود نداشته باشید. توجه داشته باشید که هیچ معادل singleFrame برای removeState() وجود ندارد، زیرا JankStats حالت های تک فریم را به طور خودکار حذف می کند.

private val scrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        // check if JankStats is initialized and skip adding state if not
        val metricsState = metricsStateHolder?.state ?: return

        when (newState) {
            RecyclerView.SCROLL_STATE_DRAGGING -> {
                metricsState.putState("RecyclerView", "Dragging")
            }
            RecyclerView.SCROLL_STATE_SETTLING -> {
                metricsState.putState("RecyclerView", "Settling")
            }
            else -> {
                metricsState.removeState("RecyclerView")
            }
        }
    }
}

توجه داشته باشید که کلید مورد استفاده برای حالت ها باید به اندازه کافی معنادار باشد تا امکان تجزیه و تحلیل بعدی را فراهم کند. به ویژه، از آنجایی که وضعیتی با همان key که قبلاً اضافه شده است، جایگزین آن مقدار قبلی می‌شود، باید سعی کنید از نام‌های key منحصربه‌فرد برای اشیایی استفاده کنید که ممکن است نمونه‌های مختلفی در برنامه یا کتابخانه شما داشته باشند. به عنوان مثال، یک برنامه با پنج RecyclerView مختلف ممکن است بخواهد به جای استفاده از RecyclerView برای هر یک، کلیدهای قابل شناسایی را برای هر یک از آنها ارائه دهد و سپس نتواند به راحتی در داده های به دست آمده تشخیص دهد که داده های فریم به کدام نمونه اشاره دارد.

اکتشافی جانک

برای تنظیم الگوریتم داخلی برای تعیین اینکه چه چیزی jank در نظر گرفته می شود، از ویژگی jankHeuristicMultiplier استفاده کنید.

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

هر دوی این مقادیر را می‌توان از طریق این روش‌ها تغییر داد تا موقعیت برنامه را بیشتر تطبیق دهد، یا در آزمایش برای مجبور کردن jank رخ دهد یا رخ ندهد، در صورت لزوم برای آزمایش.

استفاده در Jetpack Compose

در حال حاضر تنظیمات بسیار کمی برای استفاده از JankStats در Compose مورد نیاز است. برای حفظ PerformanceMetricsState در تغییرات پیکربندی، آن را به این صورت به خاطر بسپارید:

/**
 * Retrieve MetricsStateHolder from compose and remember until the current view changes.
 */
@Composable
fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {
    val view = LocalView.current
    return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) }
}

و برای استفاده از JankStats، همانطور که در اینجا نشان داده شده است، وضعیت فعلی را به stateHolder اضافه کنید:

val metricsStateHolder = rememberMetricsStateHolder()

// Reporting scrolling state from compose should be done from side effect to prevent recomposition.
LaunchedEffect(metricsStateHolder, listState) {
    snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->
        if (isScrolling) {
            metricsStateHolder.state?.putState("LazyList", "Scrolling")
        } else {
            metricsStateHolder.state?.removeState("LazyList")
        }
    }
}

برای جزئیات کامل در مورد استفاده از JankStats در برنامه Jetpack Compose، برنامه نمونه عملکرد ما را بررسی کنید.

بازخورد ارائه دهید

نظرات و ایده های خود را از طریق این منابع با ما در میان بگذارید:

ردیاب مشکل
مشکلات را گزارش کنید تا بتوانیم اشکالات را برطرف کنیم.
{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}