Global Exception Handler — モバイルアプリのクラッシュを防ぐ、未処理例外を集中的にキャッチする仕組みです。Apple Developer、2024によると、適切な例外処理によりクラッシュ発生数は40~60%減少し、ユーザーエクスペリエンスが向上します。このようなハンドラがないと、バックグラウンドスレッドでの未処理例外はアプリを即座に終了させます。
重要ポイント
Global Exception Handler — アプリの個別の関数やモジュールレベルで処理されなかった例外をキャッチする集中型メカニズムです。モバイル開発の文脈では、このようなハンドラはプロセスの異常終了前の最後の防御線として機能します。
iOSとAndroidはグローバルハンドラを設定するための組み込みAPIを提供しています。AppleはObjective-C環境にNSSetUncaughtExceptionHandlerを使用し、GoogleはJava/KotlinでThread.setDefaultUncaughtExceptionHandlerを提供しています。どちらのメカニズムも、アプリのすべてのスレッドでtry-catch構造によってキャッチされなかった例外を捕捉します。
Crashlytics(Google、2024)によると、約25%のクラッシュがバックグラウンドスレッドでの未処理例外によって発生しています — Global Exception Handlerが特に重要な領域です。開発者はUIスレッドに集中しがちで、非同期操作を忘れがちです。
グローバルハンドラの使用はローカルエラー処理を置き換えるものではなく、それを補完します。主なタスクは、例外発生時のアプリ状態に関する最大限の情報を保存し、適切に終了することです。
動作メカニズム Global Exception Handlerは、オペレーティングシステムシグナルまたはランタイム例外のインターセプトに基づいています。コードがtry-catchブロックでキャッチされない例外をスローすると、制御は事前に登録されたハンドラに移譲されます。
iOSでは、ハンドラはNSSetUncaughtExceptionHandlerを介して登録され、完全なスタックトレースを持つNSExceptionオブジェクトを受け取ります。Androidでは、Thread.setDefaultUncaughtExceptionHandlerが使用され、ThreadとThrowableを受け取ります — 例外の種類、メッセージ、コールスタックへのアクセスを提供します。
クラッシュデータを受け取った後、ハンドラは3つの必須アクションを実行します: ローカルストレージへのログ書き込み、CrashlyticsまたはSentryへのレポート送信、アプリの適切な終了。Apple WWDC 2023によると、ハンドラの実行時間は5秒に制限されています — その後、システムはプロセスを強制終了します。
iOS 13以降のSwiftアプリケーションでは、Signals APIが導入され、例外だけでなくオペレーティングシステムシグナル(SIGABRT、SIGSEGV、SIGBUS)も処理し、ハンドラのカバレッジを低レベルのメモリエラーにまで拡張しています。
実装 iOSでグローバルハンドラを設定するには、NSSetUncaughtExceptionHandlerを介してC関数を設定する必要があります。ハンドラは未処理例外の発生時に同期的に呼び出され、完全なエラーコンテキストを受け取ります。
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例外のみをキャッチします。throw-catchメカニズムを使用するSwiftエラーはこのハンドラに到達しません — 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を設定しないことをお勧めします — Crashlytics SDKが初期化時に自動的にこれを行います。
ユーザーコンテキスト — 標準のコールスタックに加えて、アプリバージョン、OSバージョン、利用可能メモリサイズ、クラッシュ前の稼働時間をログ記録すると便利です。これらのデータは問題の再現と修正に非常に重要です。
iOSでは、NSSetUncaughtExceptionHandlerを書き込みだけでなく、synchronizeフラグ付きのNSUserDefaultsへの一時データ保存にも使用できます — 即時のプロセス終了時でも永続性を保証します。
必須テスト — Global Exception HandlerはCI/CDの各段階でテストする必要があります。iOSでは@throw NSExceptionを介してテスト例外をトリガーでき、Androidではthrow RuntimeException()を介してトリガーできます。ハンドラが呼び出され、ログが保存され、アプリが正しく終了することを確認します。
Google I/O 2023によると、本番環境での30%以上のクラッシュが、開発者がテストしていないデバイスで発生しています — 異なるAndroidバージョン、カスタムファームウェア、制限されたメモリ。
最初で最も一般的なミス — 例外処理後にアプリの実行を継続しようとすること。uncaughtException呼び出し後、アプリは不安定な状態にあり、それ以上の操作は連鎖エラーとデータ破損を引き起こす可能性があります。
2つ目のミス — ハンドラ内で長時間の操作を実行すること。ネットワークリクエスト、大きなファイルの書き込み、複雑な計算は、プロセスの強制終了前に完了しません。Apple Technical Q&A QA1468によると、ハンドラ内でHTTPリクエストを送信しようとすることは、クラッシュレポートが失われる主な原因です。
3つ目のミス — バックグラウンドスレッドを無視すること。メインスレッドのみに設定されたGlobal Exception Handlerは、コルーチン、DispatchQueue、AsyncTask、RxJavaでのクラッシュから保護しません。Androidでは、各スレッドに独自のハンドラが必要です — setDefaultUncaughtExceptionHandlerは個別ハンドラがないスレッドに対してのみこれを解決します。
4つ目のミス — OSシグナルのフォールバックがないこと。iOSのNSSetUncaughtExceptionHandlerはSIGABRT、SIGSEGV、SIGBUSをキャッチしません。これらのシグナルにはsigaction APIを介した個別のハンドラ設定が必要です。開発者は、アプリがクラッシュレポートを1つも残さずに落ちたときに初めてこれに気づきます。
5つ目のミス — 機密データのロギング。クラッシュログにメール、認証トークン、ユーザーの個人データが含まれる可能性があります。これはGDPRおよびApple App Store Review Guidelinesに違反します。regexまたは許可フィールドのホワイトリストを使用して、送信データを常にフィルタリングしてください。
よくある質問
いいえ — Global Exception Handlerが呼び出された後、アプリの状態は未定義です。実行を継続しようとするとデータ破損を引き起こす可能性があります。唯一の正しい対応は、クラッシュログを保存してプロセスを終了することです。
すべてではありません — iOSでは、NSSetUncaughtExceptionHandlerはObjective-C例外のみをキャッチします。SwiftエラーとOSシグナル(SIGSEGV、SIGABRT)は個別のハンドラが必要です。Androidでは、Thread.setDefaultUncaughtExceptionHandlerはすべてのRuntimeExceptionをキャッチしますが、JNIを介したネイティブコードエラーはキャッチしません。
自分のハンドラを設定する前に、Thread.getDefaultUncaughtExceptionHandler()を介して以前のハンドラへの参照を保存します。自分のハンドラの最後でpreviousHandler.uncaughtException(thread, throwable)を呼び出します — これにより、CrashlyticsまたはSentryがデータを受信することが保証されます。
ネイティブコードの場合は、sigaction()を介したシグナル処理が必要です — SIGSEGV、SIGABRT、SIGBUS。AndroidではGoogle BreakpadまたはCrashpadを使用できます。iOSバージョン13以降では、mach例外を処理するためのSignals APIが利用可能です。
いいえ — ハンドラの設定は例外発生時にのみ影響します。通常のアプリ操作ではオーバーヘッドはありません。唯一のリスクは、ハンドラがActivityやContextへの参照を保持し、ガベージコレクションを妨げるメモリリークです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。