Crash در توسعه موبایل: چیست، انواع و روش‌های جلوگیری

نویسنده: IT Sectr منتشر شده: 2026-03-29 زمان مطالعه: 9 دقیقه

Crash — پایان اجباری برنامه موبایل به دلیل استثنای مدیریت‌نشده یا خرابی مهلک سیستم. طبق داده‌های Firebase Crashlytics، حدود ۲٪ از کاربران روزانه با خرابی مواجه می‌شوند و هر خرابی retention را ۱۰–۲۰٪ کاهش می‌دهد. درک علل و روش‌های جلوگیری از خرابی یک مهارت اجباری برای توسعه‌دهنده موبایل است.

نکات اصلی

  • Crash — استثنای مدیریت‌نشده که منجر به پایان اجباری فرآیند می‌شود
  • NullPointerException — رایج‌ترین نوع خرابی در برنامه‌های Java/Kotlin
  • گزارشگرهای خرابی stack trace، وضعیت دستگاه و داده‌های کاربر را جمع‌آوری می‌کنند
  • Firebase Crashlytics — ابزار استاندارد برای نظارت بر خرابی‌ها در توسعه موبایل
  • جلوگیری شامل مدیریت صحیح خطاها، تست و بررسی امنیت null است

Crash چیست

Crash — پایان اجباری برنامه به دلیل استثنای مدیریت‌نشده یا سیگنال مهلک سیستمی است که در کد برنامه مدیریت نشده است. هنگامی که سیستم یا ماشین مجازی (JVM, ART) وضعیت مهلکی را تشخیص می‌دهد — NullPointerException، IndexOutOfBoundsException، OutOfMemoryError — بلافاصله فرآیند را متوقف کرده و از حافظه خارج می‌کند. کاربر بسته شدن ناگهانی برنامه را بدون هیچ اعلان خطای سیستمی می‌بیند. طبق داده‌های Google، برنامه‌هایی با نرخ crash-free کمتر از ۹۹٪ تا ۲۰٪ از کاربران فعال خود را در ماه از دست می‌دهند.

در Android مکانیزم مدیریت خرابی با سیستم‌های دسکتاپ متفاوت است. به جای دیالوگ دیباگ با stack trace، Android به سادگی فرآیند را می‌کشد و اطلاعات دقیق را ذخیره نمی‌کند. جمع‌آوری اطلاعات درباره خرابی وظیفه کتابخانه‌های شخص ثالث (Crashlytics, Sentry, Bugsnag) است که استثناها را از طریق Thread.setDefaultUncaughtExceptionHandler قبل از پایان فرآیند دریافت می‌کنند.

iOS از مکانیزم مشابهی با NSException و Mach exceptions برای مدیریت خطاهای مهلک استفاده می‌کند. در صورت استثنای مدیریت‌نشده، سیستم برنامه را پایان می‌دهد و گزارش به صورت فایل .crash ذخیره می‌شود. جمع‌آوری خرابی در iOS نیاز به یکپارچه‌سازی با Crashlytics یا گزارش داخلی از طریق Xcode Organizer دارد.

انواع اصلی خرابی‌ها

پنج دسته خرابی ۹۰٪ از تمام خرابی‌ها در برنامه‌های موبایل را پوشش می‌دهند. درک هر نوع به تشخیص و رفع سریع‌تر مشکلات در محیط تولید کمک می‌کند.

NullPointerException — پادشاه خرابی‌ها

NullPointerException (NPE) — رایج‌ترین نوع خرابی در تمام برنامه‌های Java/Kotlin. هنگام تلاش برای فراخوانی متد یا دسترسی به فیلد شیئی که null است رخ می‌دهد. سناریوهای معمول: فیلد Activity مقداردهی‌نشده هنگام چرخش صفحه، پاسخ null از سرور هنگام deserialization JSON، ناوبری نادقیق در آداپتر RecyclerView.

Kotlin مشکل NPE را در سطح زبان از طریق انواع null-safe حل می‌کند: String? بدون بررسی صریح قابل استفاده نیست. با این حال، سازگاری با Java و Reflection همچنان خطر ایجاد می‌کنند. از حاشیه‌نویسی‌های @NonNull و @Nullable استفاده کنید و strictNullChecks را در ابزارهای تحلیل ایستا فعال کنید.

kotlin
fun safeLength(text: String?): Int {
    return text?.length ?: 0 // مدیریت ایمن null
}

IndexOutOfBoundsException و خطاهای مجموعه

IndexOutOfBoundsException هنگام دسترسی به ایندکس ناموجود لیست یا آرایه رخ می‌دهد. سناریوی رایج: حذف عنصر از RecyclerView بدون همگام‌سازی با آداپتر، تغییر ArrayList در چند رشته بدون قفل، محاسبه نادرست موقعیت در ViewPager. ConcurrentModificationException — خویشاوند نزدیک هنگام تکرار و تغییر همزمان مجموعه‌ها.

برای دسترسی چندرشته‌ای از CopyOnWriteArrayList یا مجموعه‌های Lock-free از java.util.concurrent استفاده کنید. برای همگام‌سازی با UI از DiffUtil استفاده کنید که تفاوت بین لیست قدیم و جدید را ایمن و کارآمد محاسبه می‌کند.

ClassCastException — مشکلات نوع

ClassCastException هنگام تبدیل شیء به نوع ناسازگار رخ می‌دهد. در Android علل معمول: نوع نادرست ViewHolder در RecyclerView (انواع مختلف سلول بدون getItemViewType صحیح)، تبدیل نادرست Fragment هنگام ناوبری، اشیاء Serializable با نسخه‌های مختلف کلاس.

از تبدیل امن Kotlin با عملگر as? استفاده کنید که در صورت ناسازگاری نوع null برمی‌گرداند. در Java — بررسی با instanceof قبل از تبدیل. برای اشیاء Parcelable حتماً CREATOR را در هر کلاس اعلام کنید.

IllegalStateException و خطاهای منطقی

IllegalStateException نشان‌دهنده فراخوانی متد در وضعیت نامناسب شیء است. مثال معمول در Android — getSupportFragmentManager() بعد از onSaveInstanceState، زمانی که commit() فرگمنت مجاز نیست. مورد رایج دیگر — فراخوانی dismiss() روی دیالوگی که قبلاً بسته شده است.

قبل از عملیات با FragmentManager وضعیت چرخه حیات را بررسی کنید. از commitAllowingStateLoss() فقط زمانی استفاده کنید که مطمئن هستید از دست دادن وضعیت بحرانی نیست. در Kotlin سازنده‌های DSL-مانندی ایجاد کنید که وضعیت‌های نادرست را در سطح نوع حذف می‌کنند.

Native Crash (سیگنال‌های SIGSEGV, SIGABRT)

Native Crash در کد بومی C/C++ هنگام نقض حافظه رخ می‌دهد: دسترسی به اشاره‌گر null، double-free، سرریز بافر پشته. در Android چنین خرابی‌هایی در کتابخانه‌های NDK، موتورهای بازی (Unity, Unreal) و وابستگی‌های سیستمی رخ می‌دهد. Native Crash توسط Thread.setDefaultUncaughtExceptionHandler دریافت نمی‌شود — فرآیند را فوراً می‌کشد.

برای تشخیص خرابی‌های بومی از فایل‌های minidump (Breakpad) یا tombstone های Android استفاده کنید. Firebase Crashlytics جمع‌آوری خرابی‌های بومی را از طریق NDK SDK پشتیبانی می‌کند. در iOS مشکل مشابه از طریق PLCrashReporter حل می‌شود.

ابزارهای گزارش خرابی

سه ابزار در بازار گزارش خرابی موبایل غالب هستند. هر کدام جمع‌آوری stack trace، تجمیع بر اساس نسخه‌های برنامه و اعلان‌های خرابی‌های جدید را فراهم می‌کنند.

Firebase Crashlytics

Crashlytics — محبوب‌ترین گزارشگر خرابی برای برنامه‌های موبایل است که بخشی از اکوسیستم Firebase می‌باشد. این ابزار به طور خودکار stack trace، داده‌های دستگاه، نسخه سیستم عامل و کلیدهای سفارشی کاربر را جمع‌آوری می‌کند. یکپارچه‌سازی ۱۰ دقیقه از طریق Firebase Console و Gradle Plugin طول می‌کشد. Crashlytics همچنین از لاگ‌های واقعی (Logcat) و رهگیری سفارشی پشتیبانی می‌کند.

kotlin
FirebaseCrashlytics.getInstance()
    .setCustomKey("current_screen", "ProfileFragment")

FirebaseCrashlytics.getInstance()
    .log("User tapped login button")

Sentry

Sentry — جایگزینی برای Crashlytics با سیستم فیلتر کردن انعطاف‌پذیرتر و پشتیبانی از ۹۰+ پلتفرم. برخلاف Firebase، Sentry سرور خودمیزبان (self-hosted) را برای شرکت‌هایی با الزامات سختگیرانه داده فراهم می‌کند. Sentry از رهگیری توزیعی، breadcrumbs و یکپارچه‌سازی با خطوط لوله CI/CD پشتیبانی می‌کند.

Bugsnag و AppCenter

Bugsnag با پشتیبانی از هشدارهای مبتنی بر شدت متمایز می‌شود: خرابی‌ها را به بحرانی، خطا و اخطار تقسیم می‌کند. AppCenter از Microsoft — ابزار رایگان با عملکرد پایه برای پروژه‌های کوچک. هر دو از Android, iOS, React Native و Flutter پشتیبانی می‌کنند.

چگونه خرابی را تحلیل کنیم

تحلیل خرابی فرآیند بازسازی تصویر کامل رویداد است. Stack trace فقط آخرین نقطه خرابی را نشان می‌دهد، اما زمینه‌ای که منجر به مشکل شده را ارائه نمی‌دهد. رویکرد حرفه‌ای شامل چهار مرحله است.

مرحله اول — خواندن stack trace. کلاس، متد و خط کدی که استثنا در آن رخ داده را مشخص کنید. زنجیره فراخوانی را از فریم بالا به پایین دنبال کنید: آخرین خط در پشته محل خرابی است و خطوط بالا دنباله فراخوانی‌ها هستند. ابهام‌زدایی (ProGuard/R8 mapping) برای ساخت‌های تولید الزامی است.

مرحله دوم — زمینه دستگاه. Crashlytics مدل دستگاه، نسخه سیستم عامل، حافظه موجود و نسخه برنامه را نشان می‌دهد. مثلاً خرابی فقط در Samsung Galaxy S10 با Android 11 نشان‌دهنده مشکل با نسخه خاص One UI است، نه خطای کلی کد.

مرحله سوم — بازتولید در دستگاه تست. اگر خرابی پایدار بازتولید نمی‌شود، مراحل دقیق را از کاربر بپرسید یا از Remote Config برای لاگ‌گیری قبل از بخش مشکل‌دار کد استفاده کنید. تست AB رفع روی بخشی از مخاطب به تأیید راه‌حل کمک می‌کند.

مرحله چهارم — نظارت پس از رفع. پس از انتشار رفع، فراوانی خرابی را به مدت ۳–۵ روز مشاهده کنید. اگر خرابی کاملاً ناپدید شد — رفع مؤثر بوده. اگر فراوانی کاهش یافت اما به صفر نرسید — سناریوی دومی وجود دارد که نیاز به تحلیل جداگانه دارد.

روش‌های جلوگیری از خرابی

رویکرد سیستماتیک به جلوگیری از خرابی شامل ابزارهای تحلیل ایستا، تست اجباری موارد مرزی و مدیریت صحیح خطاها در تمام سطوح برنامه است.

تحلیل ایستای کد

Detekt (Kotlin) و Lint (Android) مشکلات بالقوه را در مرحله کامپایل پیدا می‌کنند: متغیرهای استفاده‌نشده، NPE بالقوه، استفاده نادرست از API. این ابزارها را در خط لوله CI با آستانه خطا فعال کنید. مثلاً Detekt با پیکربندی ۳۰+ اخطار یا هر error-blocking ساخت را متوقف می‌کند.

تست‌های واحد و تست‌های UI

پوشش سناریوهای کلیدی استفاده با تست‌های واحد — محافظ پایه در برابر خرابی‌های بازگشتی. مدل‌های داده، ViewModel و لایه‌های UseCase را با موارد مرزی تست کنید: مقادیر null، لیست‌های خالی، JSON نادرست. تست‌های UI از طریق Espresso یا Compose Test جریان‌های بحرانی را پوشش می‌دهند: احراز هویت، پرداخت، راه‌اندازی.

تخریب تدریجی

برنامه را طوری طراحی کنید که خرابی در یک ماژول کل صفحه را خراب نکند. از بلوک‌های catch در سطح ViewModel با بازگرداندن حالت جایگزین استفاده کنید: نمایش placeholder به جای لیست، داده‌های کش در صورت عدم وجود شبکه، تصویر جایگزین در صورت خطای بارگذاری. این خرابی بالقوه را به سناریوی UX کنترل‌شده تبدیل می‌کند.

انتشار تدریجی با نظارت

Staged rollouts — رویه استاندارد Google Play و App Store: نسخه جدید ابتدا روی ۵٪، سپس ۲۰٪ و در نهایت ۱۰۰٪ مخاطب با فاصله ۱–۳ روز منتشر می‌شود. در هر مرحله فراوانی خرابی نظارت می‌شود: اگر نرخ crash-free به زیر ۹۹.۵٪ برسد، انتشار به طور خودکار متوقف می‌شود. Firebase Remote Config امکان غیرفعال کردن ویژگی‌های مشکل‌دار را بدون انتشار نسخه جدید فراهم می‌کند.

کنترل نسخه وابستگی‌ها

Renovate یا Dependabot در CI به طور خودکار کتابخانه‌ها را از نظر آسیب‌پذیری‌های شناخته شده و اشکالات بحرانی بررسی می‌کنند. به‌روزرسانی یک وابستگی می‌تواند یک دسته کامل از خرابی‌ها را حذف کند. با این حال، به‌روزرسانی‌ها را قبل از انتشار در محیط staging تست کنید — نسخه جدید کتابخانه ممکن است شامل تغییرات ناسازگار باشد.

سوالات متداول

آیا می‌توان ۱۰۰٪ از خرابی‌ها را جلوگیری کرد؟

خیر. بخشی از خرابی‌ها توسط عوامل خارج از کنترل توسعه‌دهنده ایجاد می‌شوند: خطاهای سیستم، مشکلات سخت‌افزاری، ناسازگاری فریم‌ور. هدف کاهش فراوانی به ۰.۱٪ و پایین‌تر است و خرابی‌های باقی‌مانده از نظر زمان واکنش به حداقل می‌رسند.

گزارشگر خرابی چه تفاوتی با تحلیل دارد؟

گزارشگر خرابی stack trace، وضعیت حافظه و دستگاه را در لحظه خرابی جمع‌آوری می‌کند. تحلیل داده‌های رفتاری کاربر را جمع‌آوری می‌کند. Crashlytics هر دو رویکرد را ترکیب می‌کند و زمینه خرابی را همراه با کلیدهای سفارشی کاربر ارائه می‌دهد.

چرا stack trace مبهم است؟

ProGuard و R8 کد را برای حفاظت از مالکیت فکری مبهم می‌کنند. برای ابهام‌زدایی فایل mapping را در Crashlytics هنگام انتشار بارگذاری کنید. بدون فایل mapping، stack trace به جای نام‌های واقعی کلاس‌ها و متدها a.a(), b.b() را نشان می‌دهد.

گزارشگر خرابی چگونه استثناها را دریافت می‌کند؟

از طریق Thread.setDefaultUncaughtExceptionHandler در Android: کتابخانه handler خود را ثبت می‌کند که اولین دریافت‌کننده استثنای مدیریت‌نشده است، داده‌ها را ذخیره می‌کند و تنها پس از آن فرآیند را پایان می‌دهد. در iOS از NSSetUncaughtExceptionHandler برای NSException و Mach exception handler برای سیگنال‌ها استفاده می‌شود.

خرابی fatal و non-fatal چیست؟

Fatal — برنامه پایان یافت. Non-fatal (استثنای گرفته‌شده) — توسعه‌دهنده استثنا را از طریق try-catch گرفته است، اما این می‌تواند نشان‌دهنده مشکل بالقوه باشد. Crashlytics این انواع را تشخیص می‌دهد و امکان فیلتر کردن non-fatal را به طور جداگانه فراهم می‌کند تا داشبورد شلوغ نشود.

خلاصه

  • Crash — پایان اجباری برنامه به دلیل استثنای مدیریت‌نشده یا سیگنال مهلک
  • NullPointerException رایج‌ترین نوع خرابی در برنامه‌های موبایل باقی می‌ماند
  • Firebase Crashlytics — ابزار استاندارد برای جمع‌آوری و تحلیل خرابی‌ها در تولید
  • تحلیل خرابی شامل خواندن stack trace، زمینه دستگاه و بازتولید در محیط تست است
  • تحلیل ایستا (Detekt, Lint) از بخشی از خرابی‌ها در مرحله کامپایل جلوگیری می‌کند
  • تخریب تدریجی خرابی‌های بالقوه را به سناریوهای مدیریت‌شده با داده‌های جایگزین تبدیل می‌کند
  • فایل‌های mapping برای ابهام‌زدایی stack trace در ساخت‌های تولید الزامی هستند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید