التباطؤ في تطبيق محمول هو تأخير ملحوظ بين إجراء المستخدم واستجابة الواجهة، يحدث بسبب زيادة تحميل الخيط الرئيسي، تسرب الذاكرة أو عمليات الإدخال/الإخراج غير المثلى. على عكس الأخطاء المرتبطة بالأخطاء المنطقية، التباطؤ هو مشكلة أداء: التطبيق يعمل بشكل صحيح لكنه بطيء. وفقًا لـ AppDynamics Mobile App Performance Report 2024، 62% من المستخدمين يحذفون التطبيق إذا تباطأ لأكثر من 3 ثوانٍ. تشخيص التباطؤ يتطلب تحليل CPU والذاكرة والشبكة باستخدام Android Studio Profiler وXcode Instruments.
النقاط الرئيسية
التباطؤ في تطبيق محمول هو تأخير ملحوظ شخصيًا بين إجراء المستخدم (لمس، سحب، إدخال نص) واستجابة الواجهة. تقنيًا، يُقاس التباطؤ كوقت بين حدث الإدخال وعرض الإطار بالكامل: الحد المريح يصل إلى 100 مللي ثانية، ملحوظ من 200 مللي ثانية، حرج فوق 500 مللي ثانية.
في مصطلحات المستخدم، «يتباطأ» و«يبطئ» غالبًا ما يُستخدمان كمرادفين، لكن تقنيًا التباطؤ هو تأخير ثابت (مثل 300 مللي ثانية عند كل نقرة)، بينما «يبطئ» هو تباطؤ متقطع: التطبيق يعمل بسلاسة ثم يتجمد لثانية. الخلل، على عكس التباطؤ، لا يتعلق بالسرعة بل بصحة العرض.
Google Play وApp Store يأخذان مقاييس الأداء في الاعتبار عند ترتيب التطبيقات. معدل ANR وتكرار jank ووقت التشغيل تؤثر على الرؤية في البحث ومعدل تحويل التثبيتات. التطبيق ذو التباطؤ المستمر يفقد ما يصل إلى 40% من المستخدمين بعد التشغيل الأول.
يحدث التباطؤ عندما يفشل خيط UI الرئيسي في معالجة الإطارات بمعدل 60 FPS (16.6 مللي ثانية لكل إطار) أو 120 FPS (8.3 مللي ثانية). دعنا ننظر إلى المصادر الرئيسية للتأخيرات.
أي عملية متزامنة في خيط UI — قراءة من SharedPreferences، العمل مع قاعدة البيانات عبر Room بدون suspend، فك تشفير صورة إلى Bitmap — تمنع عرض الإطار. على Android يسبب هذا jank، على iOS يسبب تأخير عرض Core Animation.
عندما ينفذ جامع القمامة على Android أو ARC على iOS تحرير الذاكرة، تتوقف جميع الخيوط. تحدث توقفات GC المتكررة عند إنشاء العديد من الكائنات المؤقتة — مثلاً، إنشاء مثيل ViewHolder جديد في كل استدعاء للمحول. يتجلى ذلك كتمرير متقطع.
ConstraintLayout المتداخلة، LinearLayout متعددة، View متداخلة — كل مستوى تداخل يزيد وقت القياس وlayout pass. يشير Xcode إلى أن التسلسل الهرمي العميق للطبقات (أكثر من 10 مستويات) يسبب انخفاض FPS بنسبة 20-30%.
لتحديد أسباب التباطؤ، تُستخدم أدوات التحليل المضمنة في IDE وأدوات مراقبة النظام. كل أداة تحل مهمتها الخاصة.
CPU Profiler يظهر أي الطرق تستهلك وقت CPU وفي أي خيوط تنفذ. إذا كانت طريقة حسابية ثقيلة تنفذ في الخيط الرئيسي — فهذا هو السبب الجذري. تسجيل التتبع مع تفعيل sample Java Method يتيح رؤية مكدس الاستدعاءات في أي لحظة والعثور على النقاط الساخنة.
الأداة المكافئة لنظام iOS — Time Profiler — تجمع عينات المكدس كل مللي ثانية وتظهر النسبة المئوية لوقت CPU التي يستهلكها كل أسلوب. بالدمج مع علامة Main Thread Only، يفلتر عمليات الخيط الرئيسي فقط، مشيرًا مباشرة إلى مصادر التباطؤ.
طلبات الشبكة البطيئة تخلق انطباعًا بالتباطؤ حتى لو لم يكن خيط UI محظورًا. Network Profiler في Android Studio وNetwork Link Conditioner في Xcode يسمحان بمحاكاة الاتصالات البطيئة وتحديد كيفية تصرف التطبيق في الظروف الحقيقية. الاستجابات المجزأة بدون تقدم وحمولات JSON الكبيرة هي مصادر نموذجية للتباطؤ الظاهري.
مثال على تحليل طلب شبكة مع OkHttp مع قياس الوقت:
class TimingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.nanoTime()
val response = chain.proceed(chain.request())
val duration = (System.nanoTime() - start) / 1_000_000
Log.d("Timing", "Request took $duration ms")
return response
}
}
إصلاح التباطؤ يتطلب عملاً منهجيًا: من تحسين أسلوب واحد إلى تغييرات معمارية. دعنا ننظر إلى التقنيات الأكثر فعالية.
Kotlin Coroutines مع Dispatchers.IO لطلبات الشبكة وDispatchers.Default للحسابات تضمن بقاء الخيط الرئيسي حرًا للواجهة. على iOS Grand Central Dispatch مع queue .global(qos: .userInitiated) للمهام الخلفية و.main لتحديثات UI هو النهج القياسي. تجنب عمليات sync بين الطوابير.
RecyclerView على Android وUICollectionView على iOS يتطلبان تكوينًا صحيحًا: ViewHolder مع إنشاء كائنات ضئيل في onBindViewHolder، DiffUtil لحساب التغييرات، التحميل المسبق للبيانات مقدمًا. على iOS استخدم مصدر بيانات diffable للتحديثات المتحركة بدون إدارة يدوية.
تحميل نفس الصورة في كل تمرير هو تباطؤ مضمون. Coil (Android) وKingfisher (iOS) يخزنان الصور مؤقتًا في الذاكرة وعلى القرص، مما يضمن عرضًا فوريًا عند الطلبات المتكررة. للبيانات، استخدم Room مع طبقة تخزين مؤقتة مبنية على Flow أو Combine.
مثال على تكوين التخزين المؤقت للصور مع Coil على Android:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Loading with auto-caching enabled
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
الوقاية من التباطؤ أرخص من إصلاحه في الإنتاج. يتم دمج التدابير الوقائية في عملية التطوير على مستوى الأدوات والهندسة المعمارية.
StrictMode هي أداة مدمجة في Android تكتشف عمليات الإدخال/الإخراج العرضية واستدعاءات الشبكة على الخيط الرئيسي أثناء التطوير. قم بتفعيلها في Application.onCreate بسياسة penaltyDeath للانتهاكات الحرجة. هذه هي الطريقة الوحيدة لضمان رؤية المطور للمشكلة قبل الالتزام.
النظير لنظام iOS — Main Thread Checker في Xcode، جزء من Runtime Sanitization — يتحقق تلقائيًا من أن جميع استدعاءات UIKit وAppKit تنفذ على الخيط الرئيسي. قم بتفعيله في مخطط البناء Debug واسعَ للحصول على صفر تحذيرات في CI.
أضف تشغيل Macrobenchmark (Android) وXCTMetrics (iOS) إلى خط أنابيب CI لقياس وقت التشغيل وFPS التمرير واستخدام الذاكرة. حدد عتبات: إذا زاد commit جديد وقت التشغيل بأكثر من 5% — يفشل البناء.
الأسئلة الشائعة
التباطؤ هو إحساس شخصي بالتأخير يمكن أن يحدث حتى مع FPS مرتفع إذا كان التأخير ناتجًا عن وقت معالجة الإدخال وليس العرض. انخفاض FPS (أقل من 30 إطارًا/ثانية) هو أحد أسباب التباطؤ لكنه ليس الوحيد.
استخدم Frame Timing API على Android (Choreographer) وCADisplayLink على iOS لقياس الوقت بين الإطارات. Google Play Vitals يظهر معدل jank في الظروف الحقيقية. للقياسات الدقيقة استخدم Macrobenchmark مع سيناريوهات التمرير.
الأجهزة القديمة تحتوي على عدد أقل من أنوية CPU وذاكرة RAM أقل وذاكرة أبطأ. عملية تستغرق 5 مللي ثانية على جهاز رائد قد تستغرق 50 مللي ثانية على جهاز اقتصادي. اختبر الأداء على الأجهزة منخفضة التكلفة وضبط Baseline Profiles للترجمة AOT.
نعم، هذه إحدى أكثر الطرق فعالية. الصور عالية الدقة تستهلك الكثير من الذاكرة ووقت CPU لفك التشفير. استخدم downscale إلى حجم View، وتنسيقات WebP (Android) وHEIC (iOS)، والتخزين المؤقت عبر Coil أو Kingfisher.
SwiftUI يحسن التحديثات تلقائيًا من خلال الـ diffing، مما يقلل خطر التباطؤ عند تغير البيانات. ومع ذلك، التسلسلات الهرمية المعقدة وإعادة بناء body المتكررة يمكن أن تسبب انخفاض FPS. UIKit يعطي تحكمًا أكبر في الأداء لكنه يتطلب تحسينًا يدويًا.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.