Global Exception Handler — กลไกแบบรวมศูนย์สำหรับการดักจับข้อยกเว้นที่ไม่ได้รับการจัดการ ป้องกันการปิดตัวของแอปพลิเคชันมือถือโดยไม่คาดคิด ตามข้อมูลจาก Apple Developer, 2024 การจัดการข้อยกเว้นที่ถูกต้องช่วยลดจำนวนการขัดข้องลง 40–60% และปรับปรุงประสบการณ์ผู้ใช้ หากไม่มีตัวจัดการดังกล่าว ข้อยกเว้นที่ไม่ได้รับการจัดการในเธรดพื้นหลังจะนำไปสู่การปิดแอปทันที
ประเด็นสำคัญ
Global Exception Handler — เป็นกลไกแบบรวมศูนย์สำหรับการดักจับข้อยกเว้นที่ไม่ได้รับการจัดการในระดับฟังก์ชันหรือโมดูลแต่ละรายการของแอปพลิเคชัน ในบริบทของการพัฒนาโมบายล์ ตัวจัดการดังกล่าวทำหน้าที่เป็นแนวป้องกันสุดท้ายก่อนที่กระบวนการจะสิ้นสุดอย่างผิดปกติ
iOS และ Android มี API ในตัวสำหรับการตั้งค่าตัวจัดการระดับโลก Apple ใช้ NSSetUncaughtExceptionHandler สำหรับสภาพแวดล้อม Objective-C ในขณะที่ Google มี Thread.setDefaultUncaughtExceptionHandler ใน Java/Kotlin ทั้งสองกลไกดักจับข้อยกเว้นที่ไม่ถูกจับโดยโครงสร้าง try-catch ในทุกเธรดของแอปพลิเคชัน
ตามข้อมูลจาก Crashlytics (Google, 2024) ประมาณ 25% ของการขัดข้อง เกิดจากข้อยกเว้นที่ไม่ได้รับการจัดการในเธรดพื้นหลัง — พื้นที่ที่ Global Exception Handler มีความสำคัญเป็นพิเศษ นักพัฒนามักให้ความสำคัญกับเธรด UI และลืมการทำงานแบบอะซิงโครนัส
การใช้ตัวจัดการระดับโลกไม่ได้แทนที่การจัดการข้อผิดพลาดเฉพาะที่ แต่เป็นการเสริม งานหลักคือ บันทึกข้อมูลสูงสุด เกี่ยวกับสถานะของแอปพลิเคชันในขณะที่เกิดข้อยกเว้นและสิ้นสุดอย่างถูกต้อง
กลไกการทำงาน ของ Global Exception Handler ขึ้นอยู่กับการดักจับสัญญาณของระบบปฏิบัติการหรือข้อยกเว้นขณะรันไทม์ เมื่อโค้ดโยนข้อยกเว้นที่ไม่ถูกจับโดยบล็อก try-catch ใด ๆ การควบคุมจะถูกถ่ายโอนไปยังตัวจัดการที่ลงทะเบียนไว้ล่วงหน้า
บน iOS ตัวจัดการจะถูกลงทะเบียนผ่าน NSSetUncaughtExceptionHandler และรับออบเจกต์ NSException พร้อมร่องรอยสแต็กแบบเต็ม บน Android ใช้ Thread.setDefaultUncaughtExceptionHandler ซึ่งรับ Thread และ Throwable — ให้การเข้าถึงประเภทข้อยกเว้น ข้อความ และสแต็กการเรียก
หลังจากรับข้อมูลการขัดข้อง ตัวจัดการจะดำเนินการ สามสิ่งที่จำเป็น: เขียนบันทึกลงในพื้นที่จัดเก็บในเครื่อง ส่งรายงานไปยัง Crashlytics หรือ Sentry และสิ้นสุดแอปพลิเคชันอย่างถูกต้อง ตาม Apple WWDC 2023 เวลาดำเนินการของตัวจัดการถูกจำกัดไว้ที่ 5 วินาที — หลังจากนั้นระบบจะบังคับสิ้นสุดกระบวนการ
สำหรับแอป Swift ตั้งแต่ iOS 13 เป็นต้นมา มีการนำ Signals API มาใช้ ซึ่งจัดการไม่เพียงแต่ข้อยกเว้น แต่ยังรวมถึงสัญญาณของระบบปฏิบัติการ — SIGABRT, SIGSEGV และ SIGBUS ขยายขอบเขตของตัวจัดการไปยังข้อผิดพลาดหน่วยความจำระดับต่ำ
การใช้งาน ตัวจัดการระดับโลกบน iOS จำเป็นต้องตั้งค่าฟังก์ชัน C ผ่าน NSSetUncaughtExceptionHandler ตัวจัดการจะถูกเรียกแบบซิงโครนัสในขณะที่เกิดข้อยกเว้นที่ไม่ได้รับการจัดการและรับบริบทข้อผิดพลาดแบบเต็ม
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// บันทึกบันทึกข้อขัดข้องในไฟล์ท้องถิ่น
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: ตัวจัดการดักจับเฉพาะ ข้อยกเว้น Objective-C เท่านั้น ข้อผิดพลาด Swift ที่ใช้กลไก throw-catch ไม่ถึงตัวจัดการนี้ — ต้องมีการจัดการแยกต่างหากผ่าน Swift Error Handling ตั้งแต่ iOS 14 เป็นต้นไป Apple แนะนำให้รวม NSSetUncaughtExceptionHandler กับ Signals API เพื่อให้ครอบคลุมสูงสุด
ตาม Apple Technical Note TN2151 หลังจากเรียกตัวจัดการแล้ว แอปพลิเคชันต้องสิ้นสุดภายใน 5 วินาที ความพยายามใด ๆ ที่จะดำเนินการต่อหลังจากกลับจากตัวจัดการจะนำไปสู่พฤติกรรมที่ไม่กำหนดและการขัดข้องซ้ำ
Android มีกลไกที่ยืดหยุ่นกว่าสำหรับการจัดการข้อยกเว้นระดับโลกผ่าน Thread.setDefaultUncaughtExceptionHandler ตัวจัดการได้รับการอ้างอิงไปยังเธรดที่เกิดข้อยกเว้นและออบเจกต์ Throwable เอง
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// บันทึกบันทึกข้อขัดข้องในไฟล์
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: แต่ละเธรดมีตัวจัดการของตัวเอง และ setDefaultUncaughtExceptionHandler จะตั้งค่าตัวจัดการสำหรับเธรดทั้งหมดที่ไม่มีตัวจัดการเฉพาะรายบุคคลที่กำหนดไว้ ซึ่งรับประกัน ความครอบคลุมระดับโลก — จากเธรด UI ไปจนถึง AsyncTask พื้นหลังและโครูทีน
บน Android 12+ มีข้อจำกัด: หลังจากเรียก uncaughtException แอปพลิเคชันต้องสิ้นสุดภายใน 100 มิลลิวินาที หากตัวจัดการดำเนินการที่ใช้เวลานาน ระบบอาจฆ่ากระบวนการก่อนที่บันทึกจะถูกเขียน ขอแนะนำให้ใช้ บริการพื้นหลัง สำหรับการส่งรายงานการขัดข้อง
กฎข้อแรก — อย่าพยายามกู้คืนการทำงานของแอปพลิเคชันหลังจากข้อยกเว้นที่ไม่ได้รับการจัดการ สถานะของแอปพลิเคชันหลังการขัดข้องไม่แน่นอน และการดำเนินการต่ออาจนำไปสู่ความเสียหายของข้อมูลผู้ใช้
ข้อจำกัดด้านเวลา — ข้อจำกัดทางเทคนิคหลักของ Global Exception Handler บน iOS คือ 5 วินาที บน Android — 100 มิลลิวินาที ภายในตัวจัดการ ควรบันทึกเฉพาะชุดข้อมูลขั้นต่ำ: ประเภทข้อยกเว้น สแต็กการเรียก และสถานะของตัวแปรสำคัญสองสามตัว
การส่งคำขอเครือข่าย การเขียนไปยังฐานข้อมูล และการทำให้เป็นอนุกรมที่ซับซ้อนควรเลื่อนไปยัง กลไกที่เลื่อนออกไป — ตัวอย่างเช่น บันทึกลงในไฟล์และส่งเมื่อเริ่มต้นแอปครั้งถัดไป
บริการรายงานการขัดข้อง — Firebase Crashlytics, Sentry, Bugsnag — ตั้งค่าตัวจัดการระดับโลกของตัวเอง หากนักพัฒนาตั้งค่าตัวจัดการแบบกำหนดเองเพิ่มเติม พวกเขาต้องส่งต่อการควบคุมไปยังระบบรายงานการขัดข้องหลังจากดำเนินการของตนเอง บน Android ใช้ การประกอบตัวจัดการ: ดำเนินการตรรกะของคุณ จากนั้นเรียกตัวจัดการก่อนหน้า
สำหรับ Firebase Crashlytics ขอแนะนำไม่ให้ตั้งค่า Thread.setDefaultUncaughtExceptionHandler แบบกำหนดเอง — SDK Crashlytics ทำสิ่งนี้โดยอัตโนมัติเมื่อเริ่มต้น
บริบทผู้ใช้ — นอกเหนือจากสแต็กการเรียกมาตรฐาน ควรบันทึกเวอร์ชันแอป เวอร์ชันระบบปฏิบัติการ ขนาดหน่วยความจำที่ว่าง และเวลาทำงานก่อนการขัดข้อง ข้อมูลนี้มีความสำคัญอย่างยิ่งสำหรับ การทำซ้ำ และแก้ไขปัญหา
บน iOS สามารถใช้ NSSetUncaughtExceptionHandler ไม่เพียงแต่สำหรับการเขียน แต่ยังสำหรับการจัดเก็บข้อมูลชั่วคราวใน NSUserDefaults ด้วยแฟล็ก synchronize — ซึ่งรับประกันความคงอยู่แม้ในกรณีที่กระบวนการสิ้นสุดทันที
การทดสอบที่จำเป็น — Global Exception Handler ต้องได้รับการทดสอบในทุกขั้นตอนของ CI/CD บน iOS สามารถเริ่มข้อยกเว้นทดสอบผ่าน @throw NSException บน Android — ผ่าน throw RuntimeException() ตรวจสอบว่าตัวจัดการถูกเรียก บันทึกถูกบันทึก และแอปพลิเคชันสิ้นสุดอย่างถูกต้อง
ตาม Google I/O 2023 มากกว่า 30% ของการขัดข้อง ในระบบผลิตเกิดบนอุปกรณ์ที่นักพัฒนาไม่ได้ทดสอบ — เวอร์ชัน Android ที่แตกต่างกัน เฟิร์มแวร์ที่กำหนดเอง หน่วยความจำจำกัด
ข้อผิดพลาดแรกและพบบ่อยที่สุด — การพยายามดำเนินการแอปพลิเคชันต่อหลังจากจัดการข้อยกเว้น หลังจากเรียก uncaughtException แอปพลิเคชันอยู่ในสถานะไม่เสถียร และการดำเนินการใด ๆ เพิ่มเติมอาจทำให้เกิด ข้อผิดพลาดแบบลูกโซ่ และความเสียหายของข้อมูล
ข้อผิดพลาดที่สอง — การดำเนินการที่ใช้เวลานานภายในตัวจัดการ คำขอเครือข่าย การเขียนไฟล์ขนาดใหญ่ หรือการคำนวณที่ซับซ้อนไม่เสร็จสิ้นก่อนที่กระบวนการจะถูกบังคับสิ้นสุด ตาม Apple Technical Q&A QA1468 การพยายามส่งคำขอ HTTP ภายในตัวจัดการเป็นสาเหตุหลักของการสูญเสียรายงานการขัดข้อง
ข้อผิดพลาดที่สาม — การละเลยเธรดพื้นหลัง Global Exception Handler ที่ตั้งค่าเฉพาะเธรดหลักไม่ป้องกันการขัดข้องในโครูทีน DispatchQueue AsyncTask หรือ RxJava บน Android แต่ละเธรดควรมีตัวจัดการของตัวเอง — และ setDefaultUncaughtExceptionHandler แก้ไขปัญหานี้เฉพาะสำหรับเธรดที่ไม่มีตัวจัดการเฉพาะรายบุคคล
ข้อผิดพลาดที่สี่ — การไม่มีทางเลือกสำรองสำหรับสัญญาณระบบปฏิบัติการ NSSetUncaughtExceptionHandler บน iOS ไม่ดักจับ SIGABRT, SIGSEGV และ SIGBUS สัญญาณเหล่านี้ต้องการการตั้งค่าตัวจัดการแยกต่างหากผ่าน sigaction API นักพัฒนาจะพบสิ่งนี้ก็ต่อเมื่อแอปขัดข้องโดยไม่มีรายงานการขัดข้องแม้แต่ฉบับเดียว
ข้อผิดพลาดที่ห้า — การบันทึกข้อมูลที่เป็นความลับ บันทึกการขัดข้องอาจมีอีเมล โทเค็นการรับรองความถูกต้อง หรือข้อมูลส่วนบุคคลของผู้ใช้ ซึ่งละเมิด GDPR และ แนวทางการตรวจสอบ App Store ของ Apple กรองข้อมูลที่ส่งผ่านเสมอโดยใช้นิพจน์ปกติหรือรายการที่อนุญาตของฟิลด์ที่อนุญาต
คำถามที่พบบ่อย
ไม่ — หลังจากเรียก Global Exception Handler สถานะของแอปพลิเคชันไม่แน่นอน ความพยายามใด ๆ ที่จะดำเนินการต่ออาจนำไปสู่ความเสียหายของข้อมูล การกระทำที่ถูกต้องเพียงอย่างเดียวคือบันทึกบันทึกการขัดข้องและสิ้นสุดกระบวนการ
ไม่ทั้งหมด — บน iOS NSSetUncaughtExceptionHandler ดักจับเฉพาะข้อยกเว้น Objective-C ข้อผิดพลาด Swift และสัญญาณระบบปฏิบัติการ (SIGSEGV, SIGABRT) ต้องการตัวจัดการแยกต่างหาก บน Android Thread.setDefaultUncaughtExceptionHandler ดักจับ RuntimeExceptions ทั้งหมด แต่ไม่ใช่ข้อผิดพลาดโค้ดเนทีฟผ่าน JNI
บันทึกการอ้างอิงไปยัง ตัวจัดการก่อนหน้า ผ่าน Thread.getDefaultUncaughtExceptionHandler() ก่อนตั้งค่าตัวจัดการของคุณ ในตอนท้ายของตัวจัดการของคุณ เรียก previousHandler.uncaughtException(thread, throwable) — สิ่งนี้รับประกันว่า Crashlytics หรือ Sentry จะได้รับข้อมูลของพวกเขา
สำหรับโค้ดเนทีฟ จำเป็นต้อง การจัดการสัญญาณ ผ่าน sigaction() — SIGSEGV, SIGABRT, SIGBUS บน Android สามารถใช้ Google Breakpad หรือ Crashpad บน iOS ตั้งแต่เวอร์ชัน 13 เป็นต้นมา มี Signals API สำหรับจัดการข้อยกเว้น mach
ไม่ — การตั้งค่าตัวจัดการส่งผลเฉพาะช่วงเวลาที่เกิดข้อยกเว้นเท่านั้น ในการทำงานปกติของแอปพลิเคชัน ไม่มีโอเวอร์เฮด ความเสี่ยงเดียวคือ หน่วยความจำรั่ว หากตัวจัดการเก็บการอ้างอิงไปยัง Activity หรือ Context ป้องกันการเก็บขยะ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม