読者です 読者をやめる 読者になる 読者になる

プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

例外catch漏れ対策(UncaughtExceptionHandler)

全ての例外を自前でcatchしていれば何の問題もないんだけれども、
もしアプリ内で例外catch漏れしてしまった場合、
Dalvik VM まで例外が通知されて
「〜が予期せず停止しました。やり直してください」
のダイアログが出てアプリが強制終了してしまう。


f:id:dai4649:20110629004538p:image


これはとにかくダサい!!
原因が ぬるぽ とかだともう顔まっか!!
できればこんなプログラムミスはこっそり隠蔽しておきたい!!


そんな都合のいい欲求を満たす方法として、
Thread.UncaughtExceptionHandler という仕組みがあるようだ。
(android固有ではなく、Javaでもともとあるみたい)

アプリ内で予期しない例外発生時の
「開発者にバグ報告する/しない」機能
この仕組みで実装できるみたい。


※こちらの記事を参考にさせていただきました。
androidアプリのバグ報告システムを考える


発生しうる例外をすべて把握/制御できればベストなのだが、
とりあえずこの処理を保険として実装しておくのはアリかも。


と安直に考えて、テストコード書いてみました。


テストコードの流れ

  1. ボタン押下で UIスレ or 別スレ で例外発生させる
  2. catch漏れ例外を uncaughtExceptionHander で catch(ややこしい)
  3. uncaughtExceptionHander で 例外catch したらアプリ終了
package test.uncaughtexception;

import java.lang.Thread.UncaughtExceptionHandler;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(createLayout());

        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable t) {
                Log.e(getClass().getName(), "### uncaughtException ###", t);

                // Activity終了
                finish();
            }
        });
    }

    private LinearLayout createLayout() {
        Button buttonUI = new Button(this);
        buttonUI.setText("UIスレで例外発生");
        buttonUI.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                occurErrorUIThread();
            }
        });

        Button buttonOther = new Button(this);
        buttonOther.setText("別スレで例外発生");
        buttonOther.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                occurErrorOtherThread();
            }
        });

        LinearLayout layout = new LinearLayout(this);
        layout.addView(buttonUI);
        layout.addView(buttonOther);
        return layout;
    }

    private void occurErrorUIThread() {
        throw new RuntimeException("UI");
    }

    private void occurErrorOtherThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException("OTHER");
            }
        }, "OTHER").start();
    }
}


とーこーろーがー!
実装してみたけどもうまく動作しなかった。
動作環境は 2.3.3(API Level10) です。(※1)


ココからは有識者の方に助けを求めますw


まず、例外が発生したスレッド が「UIスレッドかどうか」
例外発生->catch後のアプリ挙動が異なってきます。


例えばこんなかんじになる。

case1
例外発生(=UIスレッド)
↓
UncaughtExceptionHandler#uncaughtException で例外検知
↓
アプリ終了(Activity#finish)
↓
アプリ再起動すると 起動時にANR 発生
case2
例外発生(≠UIスレッド)
↓
UncaughtExceptionHandler#uncaughtException で例外検知
↓
アプリ終了(Activity#finish)
↓
アプリ再起動しても問題なし


case1 はなんだかアプリが正常に終了していないように見えますね。。


case1 で再起動後にANR発生の図
f:id:dai4649:20110629023102p:image


ちなみに、case1 で Activity#finish を呼ばなかった場合は
アプリが終了せず、これまた ANR発生 してしまいます。


うーん。
本来どうするのが正しいのだろうか。。
実装してる方、教えていただけると助かります。
これならcatchせずにダイアログ出したほうがまだマシ、なのか。


そもそも例外catch漏れすることがダメなので、
「例外はすべてcatchしましょう」という原点に戻ってくる気もするけどもw


※1 動作環境追記(20110629)