Global Exception Handler — مکانیزم متمرکز رهگیری استثناهای مدیریتنشده که از بسته شدن ناگهانی برنامه موبایل جلوگیری میکند. طبق دادههای Apple Developer, 2024، مدیریت صحیح استثناها تعداد crashها را 40–60% کاهش میدهد و تجربه کاربری را بهبود میبخشد. بدون چنین handlerای، هر استثنای مدیریتنشده در نخ پسزمینه منجر به بسته شدن فوری برنامه میشود.
نکات اصلی
Global Exception Handler — یک مکانیزم متمرکز برای رهگیری استثناهایی است که در سطح توابع یا ماژولهای جداگانه برنامه مدیریت نشدهاند. در زمینه توسعه موبایل، چنین handlerای به عنوان آخرین خط دفاعی قبل از پایان اجباری فرآیند عمل میکند.
iOS و Android APIهای داخلی برای نصب handler سراسری ارائه میدهند. اپل از NSSetUncaughtExceptionHandler برای محیط Objective-C استفاده میکند و گوگل Thread.setDefaultUncaughtExceptionHandler را در Java/Kotlin ارائه میدهد. هر دو مکانیزم استثناهایی را رهگیری میکنند که توسط ساختارهای try-catch در تمام نخهای برنامه گرفته نشدهاند.
طبق دادههای Crashlytics (Google, 2024)، حدود 25% از crashها به دلیل استثناهای مدیریتنشده در نخهای پسزمینه رخ میدهند — منطقهای که Global Exception Handler در آن بسیار حیاتی است. توسعهدهندگان اغلب روی نخ UI تمرکز میکنند و عملیات ناهمگام را فراموش میکنند.
استفاده از handler سراسری جایگزین مدیریت محلی خطاها نمیشود، بلکه آن را تکمیل میکند. وظیفه اصلی — ذخیره حداکثر اطلاعات درباره وضعیت برنامه در لحظه استثنا و پایان صحیح کار است.
مکانیزم کار Global Exception Handler بر اساس رهگیری سیگنالهای سیستم عامل یا استثناهای زمان اجرا است. وقتی کد استثنایی را پرتاب میکند که توسط هیچ بلوک try-catch گرفته نشده، کنترل به handler از پیش تعیینشده منتقل میشود.
در پلتفرم iOS، handler از طریق NSSetUncaughtExceptionHandler ثبت میشود و شیء NSException را با stack trace کامل دریافت میکند. در Android از Thread.setDefaultUncaughtExceptionHandler استفاده میشود که Thread و Throwable را دریافت میکند — این امکان دسترسی به نوع استثنا، پیام و پشته فراخوانی را فراهم میکند.
پس از دریافت دادههای crash، handler سه اقدام اجباری را انجام میدهد: نوشتن لاگ در حافظه محلی، ارسال گزارش به Crashlytics یا Sentry و پایان صحیح برنامه. طبق دادههای Apple WWDC 2023، زمان کار handler به 5 ثانیه محدود است — پس از آن سیستم به اجبار فرآیند را خاتمه میدهد.
برای برنامههای Swift از iOS 13 به بعد، Signals API ظاهر شد که نه تنها استثناها، بلکه سیگنالهای سیستم عامل — SIGABRT، SIGSEGV و SIGBUS را نیز پردازش میکند و دامنه پوشش handler را به خطاهای حافظه سطح پایین گسترش میدهد.
پیادهسازی handler سراسری در iOS نیاز به تنظیم تابع C از طریق NSSetUncaughtExceptionHandler دارد. handler در لحظه استثنای مدیریتنشده به صورت همزمان فراخوانی میشود و زمینه کامل خطا را دریافت میکند.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// لاگ crash را در فایل محلی ذخیره میکنیم
NSString logPath = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
[exceptionLog writeToFile:logPath atomically:YES];
}
int main(int argc, char argv[]) {
NSSetUncaughtExceptionHandler(&handleUncaughtException);
return UIApplicationMain(argc, argv, nil, nil);
}
ویژگی مهم پیادهسازی iOS: handler فقط استثناهای Objective-C را رهگیری میکند. خطاهای Swift که از مکانیزم throw-catch استفاده میکنند، به این handler نمیرسند — برای آنها پردازش جداگانه از طریق Swift Error Handling لازم است. از iOS 14 به بعد، اپل ترکیب NSSetUncaughtExceptionHandler با Signals API را برای حداکثر پوشش توصیه میکند.
طبق Apple Technical Note TN2151، پس از فراخوانی handler، برنامه باید در عرض 5 ثانیه خاتمه یابد. هرگونه تلاش برای ادامه اجرا پس از بازگشت از handler منجر به رفتار نامشخص و crash مجدد میشود.
Android مکانیزم انعطافپذیرتری برای مدیریت سراسری استثناها از طریق Thread.setDefaultUncaughtExceptionHandler ارائه میدهد. handler ارجاع به نخی که استثنا در آن رخ داده و خود شیء Throwable را دریافت میکند.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// لاگ crash را در فایل ذخیره میکنیم
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// به Crashlytics ارسال میکنیم
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// فرآیند را خاتمه میدهیم
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// نصب در Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
تفاوت کلیدی پیادهسازی Android: هر نخ handler مخصوص خود را دارد و setDefaultUncaughtExceptionHandler handler را برای تمام نخهایی که handler فردی ندارند تنظیم میکند. این پوشش سراسری را تضمین میکند — از نخ UI گرفته تا AsyncTask و کوروتینهای پسزمینه.
در Android 12+ محدودیتی ایجاد شد: پس از فراخوانی uncaughtException، برنامه باید در عرض 100 میلیثانیه خاتمه یابد. اگر handler عملیات طولانی انجام دهد، سیستم ممکن است فرآیند را قبل از اتمام نوشتن لاگ بکشد. توصیه میشود از سرویس پسزمینه برای ارسال گزارش crash استفاده شود.
اولین قانون — سعی در بازیابی برنامه پس از استثنای مدیریتنشده نکنید. وضعیت برنامه پس از crash نامشخص است و ادامه کار میتواند منجر به آسیب دیدن دادههای کاربر شود.
محدودیت زمانی — اصلیترین محدودیت فنی Global Exception Handler است. در iOS این 5 ثانیه، در Android — 100 میلیثانیه است. در داخل handler فقط ذخیره حداقل مجموعه داده مجاز است: نوع استثنا، پشته فراخوانی و وضعیت چند متغیر کلیدی.
ارسال درخواستهای شبکه، نوشتن در پایگاه داده و سریالسازی پیچیده باید به مکانیزم به تأخیر افتاده منتقل شود — مثلاً ذخیره لاگ در فایل و ارسال آن در راهاندازی بعدی برنامه.
سرویسهای گزارش crash — Firebase Crashlytics، Sentry، Bugsnag — خودشان handler سراسری را تنظیم میکنند. اگر توسعهدهنده handler خود را روی آنها تنظیم کند، باید پس از اقدامات خود کنترل را به سیستم گزارش crash واگذار کند. در Android از ترکیب handlerها استفاده میشود: اجرای منطق خود، سپس فراخوانی handler قبلی.
برای Firebase Crashlytics توصیه میشود که اصلاً Thread.setDefaultUncaughtExceptionHandler سفارشی تنظیم نکنید — Crashlytics SDK این کار را به صورت خودکار در هنگام مقداردهی اولیه انجام میدهد.
زمینه کاربر — علاوه بر پشته فراخوانی استاندارد، ثبت نسخه برنامه، نسخه سیستم عامل، مقدار حافظه آزاد و زمان کار تا crash مفید است. این دادهها برای بازتولید و رفع مشکل حیاتی هستند.
در iOS میتوان از NSSetUncaughtExceptionHandler نه تنها برای نوشتن، بلکه برای ذخیره موقت دادهها در NSUserDefaults با پرچم synchronize استفاده کرد — این امر ذخیرهسازی دادهها را حتی با خاتمه فوری فرآیند تضمین میکند.
تست اجباری — Global Exception Handler باید در هر مرحله از CI/CD آزمایش شود. در iOS میتوان استثنای آزمایشی را از طریق @throw NSException و در Android از طریق throw RuntimeException() ایجاد کرد. بررسی میشود که handler فراخوانی شده، لاگ ذخیره شده و برنامه به درستی خاتمه مییابد.
طبق Google I/O 2023، بیش از 30% از crashها در تولید روی دستگاههایی رخ میدهد که توسعهدهنده آنها را آزمایش نکرده است — نسخههای مختلف Android، سیستمعامل سفارشی، حافظه محدود.
اولین و رایجترین اشتباه — تلاش برای ادامه اجرای برنامه پس از پردازش استثنا. پس از فراخوانی uncaughtException، برنامه در وضعیت ناپایداری قرار دارد و هرگونه عملیات بعدی میتواند باعث آبشاری از خطاها و آسیب دیدن دادهها شود.
دومین اشتباه — انجام عملیات طولانی در داخل handler. درخواستهای شبکه، نوشتن فایلهای بزرگ یا محاسبات پیچیده قبل از خاتمه اجباری فرآیند زمان اتمام ندارند. طبق Apple Technical Q&A QA1468، تلاش برای ارسال درخواست HTTP در داخل handler علت اصلی از دست رفتن گزارشهای crash است.
سومین اشتباه — نادیده گرفتن نخهای پسزمینه. Global Exception Handler که فقط برای نخ اصلی تنظیم شده، از crash در کوروتینها، DispatchQueue، AsyncTask یا RxJava محافظت نمیکند. در Android هر نخ باید handler مخصوص خود را داشته باشد — و setDefaultUncaughtExceptionHandler این مشکل را فقط برای نخهای بدون handler فردی حل میکند.
چهارمین اشتباه — عدم وجود fallback برای سیگنالهای سیستم عامل. NSSetUncaughtExceptionHandler در iOS SIGABRT، SIGSEGV و SIGBUS را رهگیری نمیکند. برای این سیگنالها نیاز به تنظیم جداگانه handlerها از طریق sigaction API است. توسعهدهندگان تنها زمانی متوجه این میشوند که برنامه بدون هیچ گزارش crashی از کار میافتد.
پنجمین اشتباه — ثبت لاگ دادههای محرمانه. ایمیلها، توکنهای احراز هویت یا دادههای شخصی کاربران وارد لاگ crash میشوند. این GDPR و Apple App Store Review Guidelines را نقض میکند. همیشه دادههای ارسالی را از طریق regex یا whitelist فیلدهای مجاز فیلتر کنید.
سوالات متداول
خیر — پس از فراخوانی Global Exception Handler وضعیت برنامه نامشخص است. هرگونه تلاش برای ادامه کار میتواند منجر به آسیب دیدن دادهها شود. تنها اقدام صحیح — ذخیره لاگ crash و خاتمه فرآیند است.
همه را نه — در iOS، NSSetUncaughtExceptionHandler فقط استثناهای Objective-C را میگیرد. خطاهای Swift و سیگنالهای سیستم عامل (SIGSEGV, SIGABRT) نیاز به handlerهای جداگانه دارند. در Android، Thread.setDefaultUncaughtExceptionHandler همه RuntimeExceptionها را رهگیری میکند اما خطاهای کد native از طریق JNI را رهگیری نمیکند.
ارجاع به handler قبلی را از طریق Thread.getDefaultUncaughtExceptionHandler() قبل از تنظیم handler خود ذخیره کنید. در پایان handler خود previousHandler.uncaughtException(thread, throwable) را فراخوانی کنید — این تضمین میکند که Crashlytics یا Sentry دادههای خود را دریافت کنند.
برای کد native، پردازش سیگنالها از طریق sigaction() مورد نیاز است — SIGSEGV, SIGABRT, SIGBUS. در Android میتوان از Google Breakpad یا Crashpad استفاده کرد. در iOS از نسخه 13 به بعد Signals API برای پردازش استثناهای mach در دسترس است.
خیر — تنظیم handler فقط در لحظه وقوع استثنا تأثیر دارد. در کار عادی برنامه هیچ سرباری وجود ندارد. تنها خطر — نشت حافظه اگر handler ارجاع به Activity یا Context را نگه دارد و از جمعآوری زباله آنها جلوگیری کند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید