Global Exception Handler: 本質、動作原理、プロジェクトでの実装

著者: IT Sectr 公開日: 2026-05-27 読了時間: 8 分

Global Exception Handler — モバイルアプリのクラッシュを防ぐ、未処理例外を集中的にキャッチする仕組みです。Apple Developer、2024によると、適切な例外処理によりクラッシュ発生数は40~60%減少し、ユーザーエクスペリエンスが向上します。このようなハンドラがないと、バックグラウンドスレッドでの未処理例外はアプリを即座に終了させます。

重要ポイント

  • Global Exception Handler — アプリ内のすべての未処理例外を収集する中央ポイント、クラッシュを防止
  • iOS NSSetUncaughtExceptionHandler — AppleプラットフォームでObjective-C例外をインターセプトするC関数
  • Android Thread.setDefaultUncaughtExceptionHandler — グローバルな例外キャッチのためのプラットフォーム組み込みメカニズム
  • 終了前のロギング — ハンドラの主なタスク: プロセス終了前にクラッシュ情報を保存する
  • グレースフルデグラデーション — ハンドラにより、クラッシュの代わりに適切なエラー画面をユーザーに表示可能

Global Exception Handlerとは

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が使用され、ThreadThrowableを受け取ります — 例外の種類、メッセージ、コールスタックへのアクセスを提供します。

クラッシュデータを受け取った後、ハンドラは3つの必須アクションを実行します: ローカルストレージへのログ書き込み、CrashlyticsまたはSentryへのレポート送信、アプリの適切な終了。Apple WWDC 2023によると、ハンドラの実行時間は5秒に制限されています — その後、システムはプロセスを強制終了します。

iOS 13以降のSwiftアプリケーションでは、Signals APIが導入され、例外だけでなくオペレーティングシステムシグナル(SIGABRT、SIGSEGV、SIGBUS)も処理し、ハンドラのカバレッジを低レベルのメモリエラーにまで拡張しています。

iOSでのGlobal Exception Handlerの実装

実装 iOSでグローバルハンドラを設定するには、NSSetUncaughtExceptionHandlerを介してC関数を設定する必要があります。ハンドラは未処理例外の発生時に同期的に呼び出され、完全なエラーコンテキストを受け取ります。

objective-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は最大のカバレッジを得るためにNSSetUncaughtExceptionHandlerSignals APIの組み合わせを推奨しています。

Apple Technical Note TN2151によると、ハンドラ呼び出し後、アプリは5秒以内に終了する必要があります。ハンドラから戻った後に実行を継続しようとすると、未定義の動作とさらなるクラッシュを引き起こします。

AndroidでのGlobal Exception Handlerの実装

AndroidThread.setDefaultUncaughtExceptionHandlerを介して、より柔軟なグローバル例外処理メカニズムを提供します。ハンドラは例外が発生したスレッドへの参照とThrowableオブジェクト自体を受け取ります。

kotlin
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のベストプラクティス

最初のルール — 未処理例外の後にアプリの操作を復元しようとしないでください。クラッシュ後のアプリ状態は未定義であり、実行を継続するとユーザーデータが破損する可能性があります。

ハンドラ実行時間の最小化

時間制限 — 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が呼び出された後、アプリの状態は未定義です。実行を継続しようとするとデータ破損を引き起こす可能性があります。唯一の正しい対応は、クラッシュログを保存してプロセスを終了することです。

Global Exception Handlerはすべてのタイプのエラーをキャッチしますか?

すべてではありません — iOSでは、NSSetUncaughtExceptionHandlerはObjective-C例外のみをキャッチします。SwiftエラーとOSシグナル(SIGSEGV、SIGABRT)は個別のハンドラが必要です。Androidでは、Thread.setDefaultUncaughtExceptionHandlerはすべてのRuntimeExceptionをキャッチしますが、JNIを介したネイティブコードエラーはキャッチしません。

自分のハンドラの後にクラッシュレポートシステムに制御を渡すには?

自分のハンドラを設定する前に、Thread.getDefaultUncaughtExceptionHandler()を介して以前のハンドラへの参照を保存します。自分のハンドラの最後でpreviousHandler.uncaughtException(thread, throwable)を呼び出します — これにより、CrashlyticsまたはSentryがデータを受信することが保証されます。

C/C++ネイティブコードでクラッシュが発生した場合は?

ネイティブコードの場合は、sigaction()を介したシグナル処理が必要です — SIGSEGV、SIGABRT、SIGBUS。AndroidではGoogle BreakpadまたはCrashpadを使用できます。iOSバージョン13以降では、mach例外を処理するためのSignals APIが利用可能です。

Global Exception Handlerはパフォーマンスに影響しますか?

いいえ — ハンドラの設定は例外発生時にのみ影響します。通常のアプリ操作ではオーバーヘッドはありません。唯一のリスクは、ハンドラがActivityやContextへの参照を保持し、ガベージコレクションを妨げるメモリリークです。

まとめ

  • Global Exception Handler — クラッシュ前の最後の防御線、本番アプリでは必須
  • iOS NSSetUncaughtExceptionHandlerは5秒の処理制限でObjective-C例外をキャッチ
  • Android Thread.setDefaultUncaughtExceptionHandlerは個人用ハンドラがないすべてのスレッドで機能
  • ハンドラ実行時間は最小限に — データを保存し、復元を試みずにプロセスを終了
  • OSシグナル(SIGSEGV、SIGABRT)は標準ハンドラでキャッチされない — sigaction APIが必要
  • クラッシュレポートシステムはハンドラ合成を介して呼び出し、自身のロジックの後に制御を渡す
  • CI/CDでのハンドラテスト — 本番環境でのクラッシュレポート損失を防ぐ必須段階

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください