Jank لتطبيقات الجوال — ما هو، الأسباب والحلول

المؤلف: IT Sectr نُشر: 2026-04-01 وقت القراءة: 10 دق

Jank هو مصطلح يشير إلى التلعثم الملحوظ أو «التأتأة» في حركة الواجهة الناتج عن تخطي إطارات فردية. في تطبيقات الجوال، يحدث Jank عندما يتجاوز وقت عرض الإطار الميزانية المخصصة حسب معدل تحديث الشاشة. وفقًا لـ Android Developers، 2025، Jank هو السبب الرئيسي للشعور الذاتي بـ «البطء» — قد يكون التطبيق مثاليًا وظيفيًا، لكن المستخدم يشعر به بطيئًا بسبب FPS غير المستقر.

الملخص

  • Jank — إطارات مفقودة تظهر على شكل تلعثم في الحركة.
  • السبب الرئيسي هو تجاوز ميزانية الوقت لكل إطار (16.6 مللي ثانية لـ 60 FPS).
  • يحدث Jank بسبب Layout الثقيل، GC الطويل، حظر الخيط الرئيسي أو overdraw.
  • للتشخيص تُستخدم FrameTimeline (Android) و Instruments (iOS).
  • إزالة Jank تزيد من NPS والاحتفاظ بالمستخدمين بنسبة 15–25%.

ما هو Jank

Jank هو مصطلح من مجال الرسوميات الحاسوبية يشير إلى عيب بصري تتحرك فيه الحركة بشكل متقطع بدلاً من الانسيابية السلسة. في تطوير الجوال، يُقاس Jank بعدد الإطارات المفقودة (skipped frames) لكل وحدة زمنية. إذا فشل النظام في تجهيز إطار بحلول لحظة VSync، تعرض الشاشة الإطار السابق — يحدث توقف بمقدار 16.6 مللي ثانية عند 60 هرتز. قد لا يُلاحظ إطار مفقود واحد، لكن سلسلة من 3–5 إطارات مفقودة متتالية تخلق إحساسًا بـ «التلعثم» لمدة 50–80 مللي ثانية، يلاحظها المستخدم بوضوح.

Jank مهم بشكل خاص للحركات التي يجب أن تعمل بسرعة ثابتة: تمرير القوائم، حركة فتح القائمة، تأثيرات parallax، الانتقالات بين الشاشات. وفقًا لدراسة تجربة المستخدم من Google (2024)، التطبيق الذي تزيد نسبة Jank فيه عن 3% من جلسات التمرير يحصل على مراجعات نجمة واحدة أكثر بنسبة 22% مقارنة بالتطبيق الذي تقل نسبته عن 0.5%. أداة Android Vitals تتتبع Jank تلقائيًا وتصنفه حسب الخطورة: متوسط، شديد، وحرج.

الأسباب الرئيسية لـ Jank

تنقسم أسباب Jank إلى عدة فئات. الأولى — Layout Jank: ناتج عن استدعاءات requestLayout() المتكررة بسبب تغير أحجام Views، حركات LayoutTransition أو تحميل المحتوى الديناميكي. كل استدعاء requestLayout يطلق Measure + Layout للشجرة الفرعية بالكامل، مما قد يستغرق 5–30 مللي ثانية. الثانية — Draw Jank: مرتبط بـ overdraw واستخدام drawables ثقيلة. الثالثة — Thread Jank: حظر الخيط الرئيسي بسبب عمليات متزامنة — تحميل الملفات، العمل مع قاعدة البيانات على الخيط الرئيسي، فك تشفير Bitmap.

الفئة الرابعة — GC Jank: جمع القمامة (Garbage Collection) في ART/Dalvik أو Swift ARC. عندما تتراكم العديد من الكائنات في الكومة، يبدأ GC في إيقاف العالم (Stop-The-World) لمدة 5–15 مللي ثانية. على Android، تحدث توقفات GC غالبًا عند التخصيصات المتكررة في الحلقات: إنشاء كائنات في onDraw()، تخصيص في المحولات، تعبيرات lambda غير المستخدمة. الخامسة — IPC Jank: التواصل بين العمليات (ContentProvider، Binder) على الخيط الرئيسي. السادسة — Rendering Jank: عرض GPU بطيء بسبب shaders غير مثلى أو نسيج بحجم كبير.

نوع Jankالسببالمدة النموذجيةأداة الكشف
LayoutrequestLayout، relayout5–30 مللي ثانيةPerfetto، Systrace
DrawOverdraw، drawables ثقيلة3–20 مللي ثانيةGPU Profiling
Threadحظر الخيط الرئيسي10–200 مللي ثانيةAndroid Studio Profiler
GCGarbage Collection5–15 مللي ثانيةMemory Profiler
Renderingتحميل GPU10–50 مللي ثانيةGPU Tracer، Xcode GPU

تشخيص Jank في Android

في Android، يبدأ تشخيص Jank بتتبع النظام Perfetto. يسجل Perfetto نشاط جميع الخيوط ووحدة المعالجة المركزية ووحدة معالجة الرسوميات والجدول. مؤشر واضح على Jank هو سطور Choreographer.doFrame و Choreographer.doCallbacks: إذا تجاوز الفاصل بين استدعاءين متتاليين لـ doFrame 16.6 مللي ثانية، فقد تم فقدان إطار. يُظهر Perfetto السبب الدقيق — أي استدعاء نظام أو lock أو GC تسبب في التأخير. في Android Studio Profiler، تتوفر وظيفة مماثلة عبر CPU Profiler.

للكشف التلقائي عن Jank في الإنتاج، يُستخدم FrameMetricsAggregator — واجهة برمجية تجمع إحصائيات عن كل إطار وتجمعها لكل جلسة. في Android 12+ ظهر PerformanceHintManager — واجهة برمجية لتلميح النظام حول معدل الإطارات المستهدف. إذا أشار التطبيق إلى أنه يعمل في سيناريو 120 FPS، يمكن للنظام زيادة تردد CPU/GPU لمنع Jank. للتسجيل البسيط لجميع الإطارات المفقودة، يكفي الاشتراك في Choreographer.FrameCallback.

تسجيل Jank عبر Choreographer

كود Kotlin يشترك في Choreographer.FrameCallback ويسجل كل إطار مفقود مع تحديد مدة التأخير. يتم استدعاء الاستجابة عند كل VSync.

kotlin
class JankDetector {

    private val frameBudget = 16_666_666L
    private var previousFrameTime = 0L

    private val callback =
        Choreographer.FrameCallback { currentTime ->
            if (previousFrameTime != 0L) {
                val frameDuration =
                    currentTime - previousFrameTime
                val skippedFrames =
                    (frameDuration / frameBudget) - 1
                if (skippedFrames > 0) {
                    Log.w("Jank",
                        "Skipped $skippedFrames frames")
                }
            }
            previousFrameTime = currentTime
            Choreographer.getInstance()
                .postFrameCallback(this)
        }

    fun start() {
        Choreographer.getInstance()
            .postFrameCallback(callback)
    }
}

تشخيص Jank في iOS

في iOS، يتم تشخيص Jank عبر Instruments باستخدام قالب Core Animation. يُظهر Instruments FPS في الوقت الفعلي وعدد مرات العرض خارج الشاشة واختبارات الضرب. المؤشرات الرئيسية لـ Jank في iOS: أعمدة حمراء في مقياس زمن Core Animation (تجاوز ميزانية الإطار)، قيمة عالية لـ Renderer (تعني عرضًا خارج الشاشة) و FPS منخفض. لمراقبة الإنتاج، يجمع MetricKit تقاريرًا بمقياس MXAnimatoryMetric، الذي يتضمن متوسط FPS ووقت الإطار P50 و P95.

يشمل التشخيص الأصلي لـ Jank في iOS CADisplayLink مع التحقق من timestamp و targetTimestamp. إذا كان timestamp الحالي متأخرًا بشكل كبير عن targetTimestamp، فهذا يعني فقدان إطار أو أكثر. توصي Apple أيضًا باستخدام os_signpost للتوصيف المخصص: وضع signpost-interval في بداية ونهاية عرض الإطار ومشاهدة في Instruments أي الفواصل تتجاوز 16.6 مللي ثانية. في SwiftUI، يُستخدم UIView.invalidateIntrinsicContentSize لتشخيص Jank — الاستدعاء المتكرر لهذه الطريقة يشير إلى Layout غير مستقر.

CADisplayLink للكشف عن Jank

كود Swift يكتشف الإطارات المفقودة عبر CADisplayLink. إذا تجاوز الفرق بين timestamp و targetTimestamp 16.6 مللي ثانية، يتم تسجيل Jank.

swift
class JankMonitor {

    private var displayLink: CADisplayLink?
    private var totalJank = 0

    func start() {
        displayLink = CADisplayLink(
            target: self,
            selector: #selector(detectJank)
        )
        displayLink?.add(to: .current,
            forMode: .common)
    }

    @objc
    private func detectJank() {
        guard let link = displayLink else { return }
        let delay = link.targetTimestamp
            - link.timestamp
        if delay > 0.0167 {
            totalJank += 1
        }
    }
}

أدوات تحليل Jank

لتحليل Jank، تُستخدم أدوات النظام المدمجة و SDK الخارجية على حد سواء. على Android، الأداة الرئيسية هي Perfetto (التي حلت محل Systrace). يسمح Perfetto بتسجيل تتبعات لمدة تصل إلى 30 ثانية وتحليلها عبر الواجهة الإلكترونية ui.perfetto.dev. يُظهر جدولًا زمنيًا دقيقًا مع نشاط Choreographer وخيوط العرض (RenderThread) و GPU. للتحليل التفصيلي لمشاكل GPU، يُستخدم AGI (Android GPU Inspector)، الذي يُظهر ليس فقط وقت الإطار ولكن أيضًا حمل كتل GPU المحددة — shaders، rasterizer، وحدة النسيج.

على iOS، النظير هو Instruments مع قوالب Core Animation و Metal System Trace و GPU Driver. يُظهر Core Animation FPS ووقت الإطار، ويُظهر Metal System Trace عمل GPU بتفصيل حتى كل draw call. للتحليل على الأجهزة الحقيقية تحت الحمل، تُستخدم Firebase Performance (تجمع مقياس Screen Rendering) و Sentry (يلتقط stack trace عند Jank). واجهة برمجية جديدة Android 15 Performance Hint تسمح للمطور بإخبار النظام بالإطارات المهمة وتلقي تحذيرات من النظام عند اقتراب Jank.

FrameMetricsAggregator في الإنتاج

كود Kotlin يستخدم FrameMetricsAggregator لجمع إحصائيات الإطارات عبر جلسة. بعد إيقاف المجمع، يتم عرض عدد الإطارات المفقودة.

kotlin
class JankAggregator(private val activity: Activity) {

    private val aggregator = FrameMetricsAggregator()

    fun startCollection() {
        aggregator.add(activity.window)
    }

    fun stopAndReport() {
        aggregator.remove()
        val result = aggregator.getMetrics()
        val totalFrames = result
            ?.get(FrameMetrics.TOTAL_DURATION)
            ?.size ?: 0
        val jankFrames = result
            ?.get(FrameMetrics.TOTAL_DURATION)
            ?.count { it > 16_666_666L} ?: 0
        Log.d("JankReport",
            "Jank ratio: \${jankFrames * 100 / totalFrames}%")
    }
}

طرق إزالة تلعثم الإطارات

تتطلب إزالة Jank مجموعة من التقنيات حسب نوعه. لـ Layout Jank: استبدال التسلسلات الهرمية العميقة بـ ConstraintLayout/Compose/SwiftUI، استخدام علامات merge، تجنب requestLayout في الحركات. لـ Draw Jank: استخدام Debug GPU Overload للعثور على overdraw 4x+، استبدال drawables الثقيلة بالرسوم المتجهة (VectorDrawable/PDF)، استخدام hardware layers بحذر — فهي تسرع العرض لكنها تستهلك ذاكرة GPU أكبر. لـ Thread Jank: نقل جميع عمليات الإدخال/الإخراج والعمل مع قاعدة البيانات وفك تشفير Bitmap إلى خيوط خلفية، استخدام Kotlin Coroutines مع Dispatcher الصحيح أو RxJava مع Schedulers.io().

لـ GC Jank: تقليل التخصيصات في onDraw() و getView()، استخدام تجمعات الكائنات (ObjectPool)، استبدال for-each بـ for المفهرس، استخدام data class غير قابلة للتغيير في Kotlin مع copy() بحذر — copy تنشئ كائنًا جديدًا. لـ IPC Jank: تهيئة ContentProvider بتكاسل عبر App Startup، نقل استدعاءات Binder إلى خيط خلفي. لـ Rendering Jank: تقليل حجم النسيج إلى أقصى دقة شاشة، استخدام ضغط ASTC أو ETC2، تجنب ترجمة shaders المفرطة (ترجمة shaders مسبقًا). الحل الشامل — التشغيل المنتظم لتحليل Perfetto/Instruments في CI وتتبع تراجعات Jank.

نمط Anti-Jank: التخطيط غير المتزامن

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

kotlin
class JankSafeLoader {

    suspend fun loadAfterFirstFrame(
        activity: Activity
    ) {
        // نضمن أن الإطار الأول قد تم عرضه بالفعل
        if (Build.VERSION.SDK_INT >= 29) {
            activity.reportFullyDrawn()
        }

        // تحميل ثقيل — بعد الإطار الأول
        withContext(Dispatchers.IO) {
            val data = fetchHeavyData()
            withContext(Dispatchers.Main) {
                updateUI(data)
            }
        }
    }
}

الأسئلة الشائعة

ما هو Jank في تطبيقات الجوال؟

Jank هو إطارات عرض مفقودة تظهر على شكل تلعثم أو هزات ملحوظة في الحركة. يحدث عندما يتجاوز وقت تجهيز الإطار الميزانية الزمنية (16.6 مللي ثانية لـ 60 FPS).

ما هي الأسباب الرئيسية لـ Jank؟

Layout Jank (requestLayout المتكرر)، Draw Jank (overdraw)، Thread Jank (حظر الخيط الرئيسي)، GC Jank (جمع القمامة)، IPC Jank (استدعاءات Binder) و Rendering Jank (shaders ثقيلة).

كيف تشخص Jank في Android؟

استخدم Perfetto لتتبع النظام، GPU Profiling لتحليل مراحل الإطار و FrameMetricsAggregator لمراقبة الإنتاج. في Android Studio — CPU Profiler مع Deep Java Trace.

كيف تقيس Jank في iOS؟

عبر Instruments مع قالب Core Animation أو Metal System Trace. للإنتاج — MetricKit مع MXAnimatoryMetric. برمجيًا — CADisplayLink للتحقق من الفرق بين timestamp و targetTimestamp.

ما هي نسبة Jank التي تعتبر حرجة؟

وفقًا لـ Google، نسبة Jank التي تتجاوز 3% من جلسات التمرير (3 من كل 100 تمريرة تحتوي على تلعثم) تؤدي إلى زيادة المراجعات السلبية بنسبة 22%. النسبة المستهدفة — أقل من 0.5% من جلسات التمرير.

الخلاصة

  • Jank — إطارات مفقودة تسبب تلعثمًا مرئيًا في حركة تطبيقات الجوال.
  • الأسباب الرئيسية: Layout Jank، Draw Jank، Thread Jank، GC Jank، IPC Jank و Rendering Jank.
  • تشخيص Jank في Android — عبر Perfetto، GPU Profiling و FrameMetricsAggregator.
  • تشخيص Jank في iOS — عبر Instruments، CADisplayLink و MetricKit.
  • إزالة Jank تتطلب مزيجًا من: تسلسلات هرمية مسطحة، خيوط خلفية، تقليل التخصيصات، التخزين المؤقت.
  • نسبة Jank المستهدفة — أقل من 0.5% من جلسات التمرير مع تلعثم.
  • التحليل المنتظم في CI يمنع تراجعات الأداء قبل وصولها إلى الإنتاج.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا