Crash هو إنهاء غير طبيعي لتطبيق محمول بسبب استثناء غير معالج أو عطل نظامي fatal. وفقًا لـ Firebase Crashlytics، حوالي 2% من المستخدمين يواجهون الأعطال يوميًا، وكل عطل يقلل الاحتفاظ بالمستخدمين بنسبة 10–20%. فهم أسباب وطرق منع الأعطال هو مهارة أساسية لأي مطور تطبيقات محمولة.
الخلاصة
Crash هو إنهاء غير طبيعي للتطبيق بسبب استثناء غير معالج أو إشارة نظام fatal لم يتم معالجتها في كود التطبيق. عندما يكتشف النظام أو الآلة الافتراضية (JVM, ART) حالة fatal — NullPointerException, IndexOutOfBoundsException, OutOfMemoryError — يقوم فورًا بإيقاف العملية وتفريغها من الذاكرة. يرى المستخدم إغلاقًا مفاجئًا للتطبيق دون أي إشعار نظامي بالخطأ. وفقًا لـ Google، التطبيقات التي يقل معدل خلوها من الأعطال عن 99% تفقد ما يصل إلى 20% من المستخدمين النشطين شهريًا.
في Android، تختلف آلية معالجة الأعطال عن أنظمة سطح المكتب. بدلاً من حوار تصحيح مع stack trace، يقوم Android ببساطة بإنهاء العملية دون حفظ معلومات مفصلة. جمع معلومات العطل هو مهمة المكتبات الخارجية (Crashlytics, Sentry, Bugsnag) التي تعترض الاستثناءات عبر Thread.setDefaultUncaughtExceptionHandler قبل إنهاء العملية.
iOS يستخدم آلية مشابهة مع NSException و Mach exceptions لمعالجة الأخطاء fatal. عند حدوث استثناء غير معالج، ينهي النظام التطبيق، ويُحفظ التقرير كملف .crash. يتطلب جمع الأعطال على iOS التكامل مع Crashlytics أو التقرير المدمج عبر Xcode Organizer.
خمس فئات من الأعطال تغطي 90% من جميع حالات الفشل في التطبيقات المحمولة. يساعد فهم كل نوع على تشخيص المشكلات وإصلاحها في الإنتاج بشكل أسرع.
NullPointerException (NPE) هو أكثر أنواع الأعطال شيوعًا في جميع تطبيقات Java/Kotlin. يحدث عند محاولة استدعاء دالة أو الوصول إلى حقل لكائن قيمته null. السيناريوهات النموذجية: حقل Activity غير مهيأ عند تدوير الشاشة، استجابة null من الخادم أثناء إلغاء تسلسل JSON، تنقل غير دقيق عبر محول RecyclerView.
Kotlin يحل مشكلة NPE على مستوى اللغة من خلال أنواع آمنة من القيم الفارغة: String? لا يمكن استخدامه دون فحص صريح. ومع ذلك، فإن التوافق مع Java و Reflection لا يزالان يخلقان مخاطر. استخدم التعليقات التوضيحية @NonNull و @Nullable وفعّل strictNullChecks في أدوات التحليل الثابت.
fun safeLength(text: String?): Int {
return text?.length ?: 0 // معالجة آمنة للقيم الفارغة null
}
IndexOutOfBoundsException يحدث عند الوصول إلى فهرس غير موجود في قائمة أو مصفوفة. السيناريوهات الشائعة: إزالة عنصر من RecyclerView دون مزامنة مع المحول، تعديل ArrayList متعدد الخيوط دون قفل، حساب غير صحيح للموضع في ViewPager. ConcurrentModificationException قريب له عند التكرار وتعديل المجموعات في وقت واحد.
استخدم CopyOnWriteArrayList للوصول متعدد الخيوط أو المجموعات الخالية من القفل من java.util.concurrent. لمزامنة واجهة المستخدم، استخدم DiffUtil الذي يحسب الفرق بين القائمتين القديمة والجديدة بأمان وكفاءة.
ClassCastException يحدث عند تحويل كائن إلى نوع غير متوافق. في Android، الأسباب النموذجية: نوع ViewHolder غير صحيح في RecyclerView (أنواع خلايا مختلفة بدون getItemViewType مناسب)، تحويل Fragment غير صحيح أثناء التنقل، كائنات Serializable بإصدارات فئات مختلفة.
استخدم التحويل الآمن في Kotlin عبر عامل التشغيل as?، الذي يعيد null عند عدم توافق الأنواع. في Java — تحقق عبر instanceof قبل التحويل. بالنسبة لكائنات Parcelable، أعلن دائمًا عن CREATOR في كل فئة.
IllegalStateException يشير إلى استدعاء دالة في حالة غير مناسبة للكائن. مثال نموذجي في Android — getSupportFragmentManager() بعد onSaveInstanceState، عندما لا يُسمح بـ commit() لـ fragment. حالة شائعة أخرى — استدعاء dismiss() على حوار مغلق بالفعل.
تحقق من حالة دورة الحياة قبل عمليات FragmentManager. استخدم commitAllowingStateLoss() فقط عندما تكون متأكدًا من أن فقدان الحالة ليس حرجًا. في Kotlin، أنشئ منشئات شبيهة بـ DSL تستبعد الحالات غير الصالحة على مستوى الأنواع.
Native Crash يحدث في كود C/C++ الأصلي بسبب انتهاكات الذاكرة: إلغاء مرجع لمؤشر فارغ، double-free، تجاوز سعة المخزن المؤقت للمكدس. في Android، تحدث هذه الأعطال في مكتبات NDK ومحركات الألعاب (Unity, Unreal) والتبعيات النظامية. Native Crash لا يتم اعتراضه بواسطة Thread.setDefaultUncaughtExceptionHandler — فهو ينهي العملية فورًا.
لتشخيص الأعطال الأصلية، استخدم ملفات minidump (Breakpad) أو tombstones في Android. يدعم Firebase Crashlytics جمع الأعطال الأصلية عبر NDK SDK. على iOS، يتم حل مشكلة مماثلة باستخدام PLCrashReporter.
ثلاث أدوات تهيمن على سوق الإبلاغ عن الأعطال في التطبيقات المحمولة. كل منها يوفر جمع stack trace، وتجميع حسب إصدار التطبيق، وإشعارات بالأعطال الجديدة.
Crashlytics هي أداة الإبلاغ عن الأعطال الأكثر شيوعًا للتطبيقات المحمولة، وهي جزء من نظام Firebase البيئي. تجمع تلقائيًا stack traces ومعلومات الجهاز وإصدار نظام التشغيل والمفاتيح المخصصة للمستخدم. يستغرق التكامل 10 دقائق عبر Firebase Console و Gradle Plugin. تدعم Crashlytics أيضًا السجلات في الوقت الفعلي (Logcat) وتتبعات المستخدم.
FirebaseCrashlytics.getInstance()
.setCustomKey("current_screen", "ProfileFragment")
FirebaseCrashlytics.getInstance()
.log("User tapped login button")
Sentry هو بديل لـ Crashlytics بنظام تصفية أكثر مرونة ودعم لأكثر من 90 منصة. على عكس Firebase، يوفر Sentry خادمًا مستضافًا ذاتيًا (self-hosted) للشركات ذات المتطلبات الصارمة للبيانات. يدعم Sentry التتبع التوزيعي و breadcrumbs والتكامل مع خطوط أنابيب CI/CD.
Bugsnag يتميز بدعم التنبيهات القائمة على الشدة: يصنف الأعطال إلى critical و error و warning. AppCenter من Microsoft هي أداة مجانية بوظائف أساسية للمشاريع الصغيرة. كلاهما يدعم Android و iOS و React Native و Flutter.
تحليل العطل هو عملية إعادة بناء الصورة الكاملة لما حدث. يظهر stack trace فقط نقطة الفشل الأخيرة ولكنه لا يوفر السياق الذي أدى إلى المشكلة. يتضمن النهج الاحترافي أربع مراحل.
المرحلة الأولى — قراءة stack trace. حدد الفئة والدالة وسطر الكود حيث حدث الاستثناء. تتبع سلسلة الاستدعاءات من الإطار العلوي إلى السفلي: آخر سطر في المكدس هو موقع العطل، والأسطر العلوية هي تسلسل الاستدعاءات. إزالة التعتيم (خريطة ProGuard/R8) إلزامية لإصدارات الإنتاج.
المرحلة الثانية — سياق الجهاز. يعرض Crashlytics طراز الجهاز وإصدار نظام التشغيل والذاكرة المتاحة وإصدار التطبيق. على سبيل المثال، عطل يحدث فقط على Samsung Galaxy S10 مع Android 11 يشير إلى مشكلة في إصدار معين من One UI، وليس خطأ عام في الكود.
المرحلة الثالثة — إعادة الإنتاج على جهاز اختبار. إذا لم يتكرر العطل بشكل مستقر، اسأل المستخدم عن الخطوات الدقيقة أو استخدم Remote Config لتسجيل الدخول قبل قسم الكود المشكل. اختبار AB للإصلاح على جزء من الجمهور يساعد في تأكيد الحل.
المرحلة الرابعة — المراقبة بعد الإصلاح. بعد نشر الإصلاح، راقب معدل العطل لمدة 3–5 أيام. إذا اختفى العطل تمامًا — نجح الإصلاح. إذا انخفض التكرار لكنه لم يصل إلى الصفر — يوجد سيناريو ثانٍ يتطلب تحليلًا منفصلًا.
نهج منهجي لمنع الأعطال يشمل أدوات التحليل الثابت والاختبار الإلزامي للحالات الحدية ومعالجة الأخطاء بشكل صحيح على جميع مستويات التطبيق.
Detekt (Kotlin) و Lint (Android) يجدان المشكلات المحتملة في وقت الترجمة: المتغيرات غير المستخدمة، احتمالات NPE، استخدام غير صحيح لـ API. قم بتضمين هذه الأدوات في خط أنابيب CI مع حد للأخطاء. على سبيل المثال، Detekt مع تهيئة 30+ تحذيرًا أو أي خطأ مانع لا يمرر البناء.
تغطية السيناريوهات الرئيسية للاستخدام باختبارات الوحدة هي الحماية الأساسية ضد أعطال الانحدار. اختبر نماذج البيانات و ViewModel وطبقات UseCase مع الحالات الحدية: قيم null وقوائم فارغة و JSON غير صالح. تغطي اختبارات واجهة المستخدم عبر Espresso أو Compose Test التدفقات الحرجة: المصادقة والدفع والتوجيه.
صمم التطبيق بحيث لا يؤدي فشل في وحدة واحدة إلى تحطيم الشاشة بأكملها. استخدم كتل catch على مستوى ViewModel مع حالة احتياطية: إظهار عنصر نائب بدلاً من قائمة، بيانات مخبأة عند عدم وجود اتصال، صورة احتياطية عند خطأ التحميل. هذا يحول العطل المحتمل إلى سيناريو UX متحكم فيه.
الإصدارات المرحلية هي ممارسة قياسية في Google Play و App Store: يتم توزيع إصدار جديد على 5%، ثم 20%، ثم 100% من الجمهور بفاصل 1–3 أيام. في كل مرحلة، يتم مراقبة معدل الأعطال: إذا انخفض معدل الخلو من الأعطال عن 99.5%، يتوقف الإصدار تلقائيًا. يتيح Firebase Remote Config تعطيل الميزات المشكلة دون نشر إصدار جديد.
Renovate أو Dependabot في CI يتحققان تلقائيًا من المكتبات بحثًا عن ثغرات معروفة وأخطاء حرجة. تحديث تبعية واحدة قد يزيل فئة كاملة من الأعطال. ومع ذلك، اختبر التحديثات على بيئة staging قبل الإصدار إلى الإنتاج — قد يحتوي إصدار المكتبة الجديد على تغييرات غير متوافقة.
الأسئلة الشائعة
لا. بعض الأعطال تسببها عوامل خارجة عن سيطرة المطور: أخطاء النظام، مشاكل الأجهزة، عدم توافق البرامج الثابتة. الهدف هو تقليل المعدل إلى 0.1% أو أقل وتقليل وقت الاستجابة للأعطال المتبقية.
أداة الإبلاغ عن الأعطال تجمع stack trace وحالة الذاكرة ومعلومات الجهاز عند لحظة العطل. التحليلات تجمع بيانات سلوك المستخدم. Crashlytics تجمع بين كلا النهجين، وتوفر سياق العطل مع المفاتيح المخصصة للمستخدم.
ProGuard و R8 يشوشان الكود لحماية الملكية الفكرية. لإزالة التشويش، قم بتحميل ملف mapping إلى Crashlytics أثناء النشر. بدون ملف mapping، سيعرض stack trace a.a(), b.b() بدلاً من أسماء الفئات والدوال الحقيقية.
عبر Thread.setDefaultUncaughtExceptionHandler على Android: تسجل المكتبة معالجها الخاص الذي يستقبل الاستثناء غير المعالج أولاً، ويحفظ البيانات، وعندها فقط ينهي العملية. على iOS، يُستخدم NSSetUncaughtExceptionHandler لـ NSException و Mach exception handler للإشارات.
Fatal — تم إنهاء التطبيق. غير fatal (استثناء تم捕获ه) — المطور التقط الاستثناء عبر try-catch، لكنه قد يشير إلى مشكلة محتملة. تميز Crashlytics بين هذه الأنواع وتسمح بتصفية غير fatal بشكل منفصل لتجنب ازدحام لوحة البيانات.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا