Crash — پایان اجباری برنامه موبایل به دلیل استثنای مدیریتنشده یا خرابی مهلک سیستم. طبق دادههای Firebase Crashlytics، حدود ۲٪ از کاربران روزانه با خرابی مواجه میشوند و هر خرابی retention را ۱۰–۲۰٪ کاهش میدهد. درک علل و روشهای جلوگیری از خرابی یک مهارت اجباری برای توسعهدهنده موبایل است.
نکات اصلی
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 (NPE) — رایجترین نوع خرابی در تمام برنامههای Java/Kotlin. هنگام تلاش برای فراخوانی متد یا دسترسی به فیلد شیئی که null است رخ میدهد. سناریوهای معمول: فیلد Activity مقداردهینشده هنگام چرخش صفحه، پاسخ null از سرور هنگام deserialization JSON، ناوبری نادقیق در آداپتر RecyclerView.
Kotlin مشکل NPE را در سطح زبان از طریق انواع null-safe حل میکند: String? بدون بررسی صریح قابل استفاده نیست. با این حال، سازگاری با Java و Reflection همچنان خطر ایجاد میکنند. از حاشیهنویسیهای @NonNull و @Nullable استفاده کنید و strictNullChecks را در ابزارهای تحلیل ایستا فعال کنید.
fun safeLength(text: String?): Int {
return text?.length ?: 0 // مدیریت ایمن null
}
IndexOutOfBoundsException هنگام دسترسی به ایندکس ناموجود لیست یا آرایه رخ میدهد. سناریوی رایج: حذف عنصر از RecyclerView بدون همگامسازی با آداپتر، تغییر ArrayList در چند رشته بدون قفل، محاسبه نادرست موقعیت در ViewPager. ConcurrentModificationException — خویشاوند نزدیک هنگام تکرار و تغییر همزمان مجموعهها.
برای دسترسی چندرشتهای از CopyOnWriteArrayList یا مجموعههای Lock-free از java.util.concurrent استفاده کنید. برای همگامسازی با UI از DiffUtil استفاده کنید که تفاوت بین لیست قدیم و جدید را ایمن و کارآمد محاسبه میکند.
ClassCastException هنگام تبدیل شیء به نوع ناسازگار رخ میدهد. در Android علل معمول: نوع نادرست ViewHolder در RecyclerView (انواع مختلف سلول بدون getItemViewType صحیح)، تبدیل نادرست Fragment هنگام ناوبری، اشیاء Serializable با نسخههای مختلف کلاس.
از تبدیل امن Kotlin با عملگر as? استفاده کنید که در صورت ناسازگاری نوع null برمیگرداند. در Java — بررسی با instanceof قبل از تبدیل. برای اشیاء Parcelable حتماً CREATOR را در هر کلاس اعلام کنید.
IllegalStateException نشاندهنده فراخوانی متد در وضعیت نامناسب شیء است. مثال معمول در Android — getSupportFragmentManager() بعد از onSaveInstanceState، زمانی که commit() فرگمنت مجاز نیست. مورد رایج دیگر — فراخوانی dismiss() روی دیالوگی که قبلاً بسته شده است.
قبل از عملیات با FragmentManager وضعیت چرخه حیات را بررسی کنید. از commitAllowingStateLoss() فقط زمانی استفاده کنید که مطمئن هستید از دست دادن وضعیت بحرانی نیست. در Kotlin سازندههای DSL-مانندی ایجاد کنید که وضعیتهای نادرست را در سطح نوع حذف میکنند.
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، تجمیع بر اساس نسخههای برنامه و اعلانهای خرابیهای جدید را فراهم میکنند.
Crashlytics — محبوبترین گزارشگر خرابی برای برنامههای موبایل است که بخشی از اکوسیستم Firebase میباشد. این ابزار به طور خودکار stack trace، دادههای دستگاه، نسخه سیستم عامل و کلیدهای سفارشی کاربر را جمعآوری میکند. یکپارچهسازی ۱۰ دقیقه از طریق Firebase Console و Gradle Plugin طول میکشد. Crashlytics همچنین از لاگهای واقعی (Logcat) و رهگیری سفارشی پشتیبانی میکند.
FirebaseCrashlytics.getInstance()
.setCustomKey("current_screen", "ProfileFragment")
FirebaseCrashlytics.getInstance()
.log("User tapped login button")
Sentry — جایگزینی برای Crashlytics با سیستم فیلتر کردن انعطافپذیرتر و پشتیبانی از ۹۰+ پلتفرم. برخلاف Firebase، Sentry سرور خودمیزبان (self-hosted) را برای شرکتهایی با الزامات سختگیرانه داده فراهم میکند. Sentry از رهگیری توزیعی، breadcrumbs و یکپارچهسازی با خطوط لوله CI/CD پشتیبانی میکند.
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 ساخت را متوقف میکند.
پوشش سناریوهای کلیدی استفاده با تستهای واحد — محافظ پایه در برابر خرابیهای بازگشتی. مدلهای داده، 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 هر دو رویکرد را ترکیب میکند و زمینه خرابی را همراه با کلیدهای سفارشی کاربر ارائه میدهد.
ProGuard و R8 کد را برای حفاظت از مالکیت فکری مبهم میکنند. برای ابهامزدایی فایل mapping را در Crashlytics هنگام انتشار بارگذاری کنید. بدون فایل mapping، stack trace به جای نامهای واقعی کلاسها و متدها a.a(), b.b() را نشان میدهد.
از طریق Thread.setDefaultUncaughtExceptionHandler در Android: کتابخانه handler خود را ثبت میکند که اولین دریافتکننده استثنای مدیریتنشده است، دادهها را ذخیره میکند و تنها پس از آن فرآیند را پایان میدهد. در iOS از NSSetUncaughtExceptionHandler برای NSException و Mach exception handler برای سیگنالها استفاده میشود.
Fatal — برنامه پایان یافت. Non-fatal (استثنای گرفتهشده) — توسعهدهنده استثنا را از طریق try-catch گرفته است، اما این میتواند نشاندهنده مشکل بالقوه باشد. Crashlytics این انواع را تشخیص میدهد و امکان فیلتر کردن non-fatal را به طور جداگانه فراهم میکند تا داشبورد شلوغ نشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید