web-dev-qa-db-ja.com

Androidでグローバルなキャッチされない例外ハンドラを設定する理想的な方法

Androidアプリケーションのすべてのスレッドにキャッチされないグローバルな例外ハンドラーを設定したいので、ApplicationサブクラスでThread.UncaughtExceptionHandlerの実装をデフォルトとして設定しますキャッチされない例外のハンドラ。

Thread.setDefaultUncaughtExceptionHandler(
                new DefaultExceptionHandler(this));

私の実装では、適切な例外メッセージを表示するAlertDialogを表示しようとしています。

しかし、これはうまくいかないようです。処理されないスレッドに対して例外がスローされるたびに、OSのデフォルトのダイアログが表示されます(「申し訳ありません!-アプリケーションが停止しました-予期せずダイアログ」)。

キャッチされない例外のデフォルトハンドラを設定するための正しい理想的な方法は何ですか?

84
Samuh

それがあなたがする必要があるすべてであるべきです。 (プロセスを後で停止させるようにしてください。物事は不確実な状態になる可能性があります。)

最初に確認することは、Androidハンドラーがまだ呼び出されているかどうかです。バージョンが呼び出されているが致命的に失敗し、プロセスがクラッシュしたときにsystem_serverが汎用ダイアログを表示している可能性があります.

ハンドラの上部にログメッセージを追加して、そこに到達しているかどうかを確認します。 getDefaultUncaughtExceptionHandlerの結果を出力し、キャッチされない例外をスローしてクラッシュを引き起こします。 logcatの出力に注目して、何が起こっているのかを確認してください。

24
fadden

Androidがかなり前にクラッシュした場合のカスタム処理のためのシンプルな solution を投稿しました。それは少しハックですが、すべてのAndroidバージョン(Lollipopを含む)。

最初に少し理論。 Androidでキャッチされない例外ハンドラを使用する場合の主な問題は、メイン(別名UI)スレッドでスローされる例外に伴います。アプリがシステム呼び出しを開始するとき ActivityThread .main アプリの メインルーパー を準備して開始するメソッド:

_public static void main(String[] args) {
  …
  …
    Looper.prepareMainLooper();
  …
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
_

メインルーパーは、UIスレッドに投稿されたメッセージ(UIのレンダリングと対話に関連するすべてのメッセージを含む)を処理します。 UIスレッドで例外がスローされた場合、例外ハンドラーによってキャッチされますが、loop()メソッドを使用していないため、ユーザーにダイアログやアクティビティを表示することはできません。 UIメッセージを処理する人はいません。

提案されたソリューションは非常に簡単です。 _Looper.loop_メソッドを独自に実行し、try-catchブロックで囲みます。例外がキャッチされたら、必要に応じて処理し(カスタムレポートアクティビティを開始するなど)、_Looper.loop_メソッドを再度呼び出します。

次のメソッドは、この手法を示しています(_Application.onCreate_リスナーから呼び出す必要があります)。

_private void startCatcher() {
    UncaughtExceptionHandler systemUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();

    // the following handler is used to catch exceptions thrown in background threads
    Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler(new Handler()));

    while (true) {
        try {
            Looper.loop();
            Thread.setDefaultUncaughtExceptionHandler(systemUncaughtHandler);
            throw new RuntimeException("Main thread loop unexpectedly exited");
        } catch (Throwable e) {
            showCrashDisplayActivity(e);
        }
    }
}
_

ご覧のとおり、キャッチされていない例外ハンドラは、バックグラウンドスレッドでスローされた例外に対してのみ使用されます。次のハンドラーはこれらの例外をキャッチし、UIスレッドに伝播します。

_static class UncaughtHandler implements UncaughtExceptionHandler {

    private final Handler mHandler;

    UncaughtHandler(Handler handler) {
        mHandler = handler;
    }

    public void uncaughtException(Thread thread, final Throwable e) {
        mHandler.post(new Runnable() {
            public void run() {
                throw new BackgroundException(e);
            }
        });
    }
}
_

この手法を使用するサンプルプロジェクトは、私のGitHubリポジトリで入手できます。 https://github.com/idolon-github/Android-crash-catcher

11
Idolon

FWIW私はこれが少し話題から外れていることを知っていますが、私たちは Crittercismの無料プラン を使用して成功しています。アプリがクラッシュしないように例外を処理するなど、いくつかのプレミアム機能も提供します。

無料版では、ユーザーにはまだクラッシュが表示されますが、少なくとも電子メールとスタックトレースは表示されます。

また、iOSバージョンも使用しています(ただし、同僚からはあまり良くないと聞いています)。


同様の質問を次に示します。

3

あなたのuncaughtException()メソッドでpreviousHandlerが設定されているpreviousHandler.uncaughtException()を呼び出さないようにすることを無効にすると思います

previousHandler = Thread.getDefaultUncaughtExceptionHandler();
3
Robby Pond

あなたが電話をするまで機能しません

Android.os.Process.killProcess(Android.os.Process.myPid());

uncaughtExceptionHandlerの最後に。

1
Joseph_Marzbani