arcanum_jp’s blog

おっさんの日記

Androidアプリで例外が発生した場合

良いAndroidアプリを作る139の鉄則

良いAndroidアプリを作る139の鉄則

 これで紹介されていたので、実験

 ActivityのonCreate()とかでハンドラーを登録すると、例外が発生した際にそのハンドラーがコールバックされるよって事なんだけど。便利だなーーと思いこんなコードを

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button b = (Button) findViewById(R.id.button);
        b.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        throw new RuntimeException("なんかエラー!!");
                    }
                }
        );


        Thread.currentThread().setUncaughtExceptionHandler(
                new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread thread, Throwable ex) {

                        Toast.makeText(getBaseContext(), "なんか例外っすーーーーーー", Toast.LENGTH_SHORT).show();
                    }
                }
        );

    }
}


やってみるとわかるけど、なんかうまくいかないっす。ハンドラーまではくるけどトーストができない。それよかアプリが固まる・・・と言うことでググってみたらありましたのメモ

You're not seeing anything because the exception happened on your UI thread and the stack unrolled all the way. So there is no more Looper and there is no support there that is used to display the Toast. If you want to display the exception information on screen you'll most likely need to start another Activity in another process.

http://stackoverflow.com/questions/3171394/using-global-exception-handling-with-setuncaughtexceptionhandler-and-toast

意訳:ルーパー死んじゃったから画面表示できないっす。別プロセスでアクティビティ表示して情報表示したら?

なるほどーー、ちなみに下の方にもっと簡単な解が・・・

It is possible. You need to do it inside another thread
Then it should be like this

意訳:あーー、、できるっす。別スレッドでやればいいすよ。

と言うことでやったのがこちらで、トーストを表示する。先のURLで提示されたサンプルのメッセージ部分変えただけです。

    Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

        @Override
        public void uncaughtException(Thread thread, Throwable ex) {
            new Thread() {
                @Override
                public void run() {
                    Looper.prepare();  
                    Toast.makeText(TicTacToe.this, "例外で死んだっす", toast.LENGTH_LONG).show();
                    Looper.loop();
                }
            }.start();
        }
    });

Looper.prepare() 〜 Looper.loop();で囲んだところに書けばいいみたいですね。アラート表示できたらいいので、このように応用。最後はfinish();しないとそのまんまだから注意ね。

        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                new Thread() {
                    @Override
                    public void run() {
                        Looper.prepare();

                        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
                        // アラートダイアログのタイトルを設定します
                        alertDialogBuilder.setTitle("エラー");
                        // アラートダイアログのメッセージを設定します
                        alertDialogBuilder.setMessage("開発者に連絡してください。Right Now!!");
                        // アラートダイアログの肯定ボタンがクリックされた時に呼び出されるコールバックリスナーを登録します
                        alertDialogBuilder.setPositiveButton(
                                "はい",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        Toast.makeText(MainActivity.this, "はい", Toast.LENGTH_LONG).show();
                                        finish();
                                    }
                                }
                        );
                        // アラートダイアログのキャンセルが可能かどうかを設定します
                        alertDialogBuilder.setCancelable(false);
                        AlertDialog alertDialog = alertDialogBuilder.create();
                        // アラートダイアログを表示します
                        alertDialog.show();

                        Looper.loop();
                    }
                }.start();
            }
        });

しかしこれではANRが発生する

 しかしこれ、うまくいくときと行かないときがあって、なんでだろうなぁと思っていたわけです。そしたらこちらで解説がありました。

この機能、適切に使用すると便利なのですが、使用方法を誤ると ANR (Application Not Responding) が発生する不具合につながります。
以下、その原因と対策について調べてみました。

http://kokufu.blogspot.jp/2013/01/android-uncaughtexceptionhandler-anr.html


UncaughtExceptionHandlerを独自に設定するのはいいのですが、すでに設定されていたハンドラーがあり、それを退避し、最後に実行してやる必要があるというわけです。なるほどーー。

ただ、リンク先の方法だとたとえば「xxxで終了しました。yyyに連絡してください」的なダイアログを表示した後に普通に終了したい場合など、デフォルトのハンドラーの処理が行われてしまうために、デフォルトのかっこ悪いダイアログが表示されてしまします。なので、デフォルトのハンドラー(com.android.internal.os.RuntimeInit.UncaughtHandl)のコードを真似てダイアログ部分だけ変更するのがいいのかなと感じました。