مكتبة JankStats

تساعدك مكتبة JankStats في تتبع وتحليل مشكلات الأداء في تطبيقاتك. تشير Jank إلى إطارات التطبيق التي تستغرق وقتًا طويلاً جدًا لعرضها، وتوفر مكتبة JankStats تقارير حول إحصاءات البيانات غير المحتملة لتطبيقك.

الصلاحيات

تعتمد JankStats على إمكانات نظام Android الأساسي الحالية، بما في ذلك FrameMetrics API في نظام التشغيل Android 7 (المستوى 24 لواجهة برمجة التطبيقات) والإصدارات الأحدث أو OnPreDrawListener في الإصدارات السابقة. ويمكن أن تساعد هذه الآليات التطبيقات في تتبع المدة التي تستغرقها الإطارات حتى تكتمل. تقدم مكتبة JanksStats القدرة على اثنين من الميزات الإضافية التي تجعلها أكثر ديناميكية وأسهل في الاستخدام: استدلاليات البيانات غير المحتملة وحالة واجهة المستخدم.

استدلاليات "جانك"

في حين أنه يمكنك استخدام FrameMetrics لتتبع مدد عرض الإطارات، لا تقدم FrameMetrics أي مساعدة في تحديد البيانات غير المحتملة الفعلية. ومع ذلك، تتضمّن JankStats آليات داخلية قابلة للضبط لتحديد وقت حدوث البيانات غير الواضحة، ما يجعل التقارير أكثر فائدة على الفور.

حالة واجهة المستخدم

من الضروري غالبًا معرفة سياق مشاكل الأداء في تطبيقك. على سبيل المثال، إذا طوَّرت تطبيقًا معقّدًا متعدد الشاشات يستخدم FrameMetrics واكتشفت أن تطبيقك غالبًا ما يحتوي على إطارات رديئة للغاية، ستحتاج إلى توضيح سياق تلك المعلومات من خلال معرفة مكان حدوث المشكلة، وما كان يفعله المستخدم، وكيفية تكرارها.

تحلّ JankStats هذه المشكلة من خلال تقديم واجهة برمجة تطبيقات state تتيح لك التواصل مع المكتبة لتقديم معلومات حول النشاط على التطبيقات. عندما تُسجل JankStats معلومات حول إطار غير مهم، فإنها تتضمن الحالة الحالية للتطبيق في تقارير الأداء غير المهم.

الاستخدام

لبدء استخدام JankStats، قم بإنشاء مثيل للمكتبة وتفعيلها لكل Window. يتتبع كل كائن JankStats البيانات فقط ضمن Window. يتطلب إنشاء نسخة مثيل للمكتبة مثيلاً من Window مع مستمع OnFrameListener، وكلاهما يُستخدَم لإرسال المقاييس إلى العميل. يتم استدعاء المستمع مع إدراج FrameData في كل إطار والتفاصيل التالية:

  • وقت بدء الإطار
  • قيم المدة
  • ما إذا كان يجب اعتبار الإطار غير مؤقت أم لا
  • مجموعة من أزواج السلاسل تحتوي على معلومات حول حالة التطبيق أثناء الإطار

لجعل JankStats أكثر فائدة، يجب أن تملأ التطبيقات المكتبة بمعلومات حالة واجهة المستخدم ذات الصلة لإعداد التقارير في FrameData. يمكنك إجراء ذلك من خلال واجهة برمجة التطبيقات PerformanceMetricsState (وليس JankStats مباشرةً)، حيث يتوفر كل منطق إدارة الحالة وواجهات برمجة التطبيقات.

الإعداد

لبدء استخدام مكتبة 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 هذا معلومات النشاط أيضًا.

تستخدم الطريقة JankStats.createAndTrack مرجعًا لكائن Window، وهو خادم وكيل للتدرّج الهرمي "العرض" في 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 لتطبيقك وتوفيره لكي يتلقّى التقارير التقارير حسب الإطار. ويتم استدعاء أداة معالجة البيانات هذه في كل إطار لتوفير بيانات البيانات غير المحتملة بشكل مستمر للتطبيقات.

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

تقدِّم أداة معالجة البيانات معلومات في كل إطار عن البيانات غير الواضحة باستخدام الكائن FrameData. يحتوي ذلك على المعلومات التالية حول الإطار المطلوب:

  • isjank: علامة منطقية تشير إلى ما إذا كان هناك عطل في الإطار.
  • frameDurationUiNanos: مدة الإطار (بالنانو ثانية).
  • frameStartNanos: الوقت الذي بدأ فيه الإطار (بالنانو ثانية).
  • states: حالة التطبيق أثناء الإطار.

إذا كنت تستخدم نظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) أو إصدارًا أحدث، يمكنك استخدام ما يلي لعرض المزيد من البيانات حول مدد عرض الإطارات:

  • FrameDataApi24 يوفّر frameDurationCpuNanos لعرض الوقت المستغرَق في الأجزاء غير التابعة لوحدة معالجة الرسومات.
  • توفّر FrameDataApi31 frameOverrunNanos لعرض مقدار الوقت بعد الموعد النهائي للإطار الذي استغرقه الإطار لإكماله.

يمكنك استخدام StateInfo في المستمع لتخزين معلومات حول حالة التطبيق.

يُرجى العِلم أنّه يتم استدعاء OnFrameListener في سلسلة التعليمات نفسها المستخدَمة داخليًا لتقديم المعلومات في كل إطار إلى JankStats. هذه هي سلسلة التعليمات الرئيسية (واجهة المستخدم) في الإصدار 6 من Android (المستوى 23 لواجهة برمجة التطبيقات) والإصدارات الأقدم. في الإصدار 7 من Android (المستوى 24 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يكون هذا الرابط هو سلسلة المحادثات التي يتم إنشاؤها واستخدامها بواسطة FrameMetrics. في كلتا الحالتين، من المهم التعامل مع معاودة الاتصال والعودة بسرعة لمنع حدوث مشكلات في الأداء على هذا السلسلة من الرسائل.

تجدُر الإشارة أيضًا إلى أنّ كائن FrameData الذي تم إرساله في الاستدعاء يُعاد استخدامه في كل إطار لمنع تخصيص كائنات جديدة لإعداد تقارير البيانات. يعني هذا أنه يجب عليك نسخ تلك البيانات وتخزينها مؤقتًا في مكان آخر حيث يجب اعتبار ذلك الكائن ثابتًا وعفا عليه الزمن فور ظهور معاودة الاتصال.

تجميع

من المحتمل أنك تريد من رمز تطبيقك تجميع البيانات في كل إطار، مما يسمح لك بحفظ المعلومات وتحميلها وفقًا لتقديرك الخاص. علمًا أنّ التفاصيل حول الحفظ والتحميل تقع خارج نطاق إصدار واجهة برمجة التطبيقات JankStats API، يمكنك الاطّلاع على نشاط أولي لتجميع البيانات لكل إطار في مجموعة أكبر باستخدام JankAggregatorActivity المتاحة في مستودع GitHub.

تستخدم 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()، حيث إنّ التغييرات التي تطرأ على مراحل النشاط تبدو مناسبة لرصد حالة البيانات غير المحتملة في التطبيق:

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 واستلام بيانات الإطار.

إدارة الولاية

قد ترغب في استدعاء واجهات برمجة تطبيقات أخرى لتخصيص JankStats. على سبيل المثال، يؤدي حقن معلومات حالة التطبيق إلى جعل بيانات الإطار أكثر فائدة من خلال توفير سياق لتلك الإطارات التي يحدث فيها خلل في الأداء.

تسترد هذه الطريقة الثابتة عنصر MetricsStateHolder الحالي لتسلسل هرمي محدّد لطريقة العرض.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder

يمكن استخدام أي طريقة عرض في تدرج هرمي نشط. يتم إجراء عملية التحقق داخليًا لمعرفة ما إذا كان هناك عنصر Holder حالي مرتبط بالتسلسل الهرمي لطريقة العرض. يتم تخزين هذه المعلومات مؤقتًا في عرض أعلى هذا التسلسل الهرمي. في حال عدم توفُّر عنصر من هذا النوع، ينشئ getHolderForHierarchy() عنصرًا.

تتيح لك طريقة getHolderForHierarchy() الثابتة تجنُّب الاحتفاظ بنسخة احتياطية من مثيل حامل في مكان ما لاسترداده لاحقًا، كما تسهِّل عملية استرداد كائن الحالة الحالي من أي مكان في الرمز (أو حتى رمز المكتبة الذي لن يكون بإمكانه الوصول إلى المثيل الأصلي).

لاحظ أن القيمة المعروضة هي كائن حامل، وليست كائن الحالة نفسه. يتم تعيين قيمة كائن الحالة داخل الحامل بواسطة JankStats فقط. بمعنى أنه إذا قام أحد التطبيقات بإنشاء كائن JankStats للنافذة التي تحتوي على التسلسل الهرمي هذا، فسيتم إنشاء كائن الحالة وتعيينه. بخلاف ذلك، بدون تتبع JankStats للمعلومات، لن تكون هناك حاجة إلى كائن الحالة، وليس من الضروري إدخال حالة التطبيق أو المكتبة.

يجعل هذا النهج من الممكن استرداد صاحب يمكن لـ JankStats بعد ذلك تعبئته. يمكن للكود الخارجي أن يطلب من حامله في أي وقت. يمكن للمُتصلين تخزين الكائن Holder الخفيف في أي وقت واستخدامه في أي وقت لضبط الحالة، بناءً على قيمة سمة state الداخلية، كما في مثال الرمز البرمجي أدناه، حيث يتم ضبط الحالة فقط عندما تكون خاصية الحالة الداخلية للحامل غير فارغة:

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

للتحكّم في حالة واجهة المستخدم/التطبيق، يمكن للتطبيق إدخال (أو إزالته) حالة باستخدام الطريقتين putState وremoveState. وتسجّل JankStats الطابع الزمني لهذه المكالمات. إذا تداخل إطار مع وقت البدء والانتهاء للحالة، تُعِدّ JankStats تقارير تتضمّن المعلومات التي تشير إلى ذلك مع بيانات التوقيت للإطار.

لأي ولاية، أضف معلومتين: key (فئة حالة، مثل "RecyclerView") وvalue (معلومات حول ما كان يحدث في ذلك الوقت، مثل "التمرير").

أزِل الحالات باستخدام طريقة removeState() عندما لا تصبح تلك الحالة صالحة، لضمان عدم الإبلاغ عن المعلومات الخاطئة أو المضلّلة في بيانات الإطار.

يؤدي استدعاء putState() باستخدام key التي تمت إضافتها سابقًا إلى استبدال value الحالي لتلك الحالة بالحالة الجديدة.

يضيف الإصدار putSingleFrameState() من واجهة برمجة التطبيقات للحالة حالة يتم تسجيلها مرة واحدة فقط في الإطار التالي الذي تم الإبلاغ عنه. سيزيلها النظام تلقائيًا بعد ذلك، مما يضمن عدم وجود حالة قديمة عن طريق الخطأ في التعليمات البرمجية. يُرجى ملاحظة أنّه لا يتوفّر حد أقصى مكافئ لـ 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 فريدة للكائنات التي قد يكون لها مثيلات مختلفة في تطبيقك أو مكتبتك. على سبيل المثال، قد يحتاج تطبيق يحتوي على خمسة أنواع مختلفة من RecyclerViews إلى توفير مفاتيح قابلة للتحديد لكل منها بدلاً من استخدام RecyclerView فقط لكلّ منها، وبالتالي لا يتمكّن بعد ذلك من التعرّف بسهولة على البيانات الناتجة عن إطار بيانات الإطار.

استدلاليات "جانك"

لتعديل الخوارزمية الداخلية لتحديد ما يُعتبَر غير مرغوب فيه، استخدِم السمة jankHeuristicMultiplier.

ويعرّف النظام تلقائيًا مشكلة عدم الإزعاج على أنّها إطار يستغرق عرض معدّل التحديث الحالي ضعف معدل التحديث الحالي. فهو لا يتعامل مع البيانات غير المحتملة على أنها أي مشكلة تتعلّق بمعدل التحديث لأن المعلومات المتعلقة بوقت عرض التطبيق غير واضحة تمامًا. لذلك، يُستحسن إضافة مورد احتياطي والإبلاغ عن المشاكل فقط عندما تسبب مشاكل ملحوظة في الأداء.

يمكن تغيير هاتين القيمتين من خلال هاتين الطريقتين لتناسب حالة التطبيق عن كثب، أو في الاختبار لفرض حدوث مشكلة عدم ظهور البيانات أو عدم حدوثها، حسب الضرورة لإجراء الاختبار.

الاستخدام في 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، يمكنك الاطّلاع على نموذج تطبيق الأداء.

تقديم ملاحظات

يُرجى مشاركة ملاحظاتك وآرائك معنا من خلال الموارد التالية:

أداة تتبّع المشاكل
يمكنك الإبلاغ عن المشاكل حتى نتمكّن من إصلاح الأخطاء.