Androidがアプリケーションのcrash情報をキャプチャする方法


転載は出典を明記してください.http://blog.csdn.net/fishle123/article/details/50823358
私たちのアプリケーションではcrashが発生することは避けられません.デバッグ段階であれば、Logcatを使用して異常情報を表示することができます.しかし、アプリケーションがリリースされたら?ユーザー側でcrashが発生した場合、これらのcrash情報をキャプチャできれば、crashの原因を特定し、問題を修復するのに役立ちます.アプリケーションcrashはJavaレイヤの異常によるものでもnativeレイヤによるものでもありますが、以下では、どのように処理するかをそれぞれ見てみましょう.
1 Javaレイヤの未キャプチャ例外処理
まずJava層のcrash情報収集を見てみましょう.Java層のcrash情報をキャプチャするのは難しくありません.Androidは、システムの未キャプチャの異常を監視するためのインタフェースを提供しています.Thread.s t e D e f a u l t U n c aughtExceptionHandlerを使用すると、アプリケーションの予期せぬcrashを簡単に監視できます.
まず、この方法を見てみましょう.
/**
 * Sets the default uncaught exception handler. This handler is invoked in
 * case any Thread dies due to an unhandled exception.
 *
 * @param handler
 *            The handler to set or null.
 */
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
    Thread.defaultUncaughtHandler = handler;
}

プロセス内(Thread.defaultUncaughtExceptionHandler) 静的変数であるため、プロセス全体のすべてのスレッドに有効)のスレッドに未キャプチャ例外が発生した場合、ここで設定したhandlerが呼び出されます.では、UncaughtExceptionHandlerを見てみましょう. このクラスですね.
/**
 * Implemented by objects that want to handle cases where a thread is being
 * terminated by an uncaught exception. Upon such termination, the handler
 * is notified of the terminating thread and causal exception. If there is
 * no explicit handler set then the thread's group is the default handler.
 */
public static interface UncaughtExceptionHandler {
    /**
     * The thread is being terminated by an uncaught exception. Further
     * exceptions thrown in this method are prevent the remainder of the
     * method from executing, but are otherwise ignored.
     *
     * @param thread the thread that has an uncaught exception
     * @param ex the exception that was thrown
     */
    void uncaughtException(Thread thread, Throwable ex);
}

ソースコードからわかるように、UncaughtExceptionHandler 実は1つのインタフェースで、それはただ1つの方法uncaughtException(Thread)を定義しました thread, Throwable ex)は,スレッドが未キャプチャ異常に遭遇して終了すると,このメソッドを呼び出す.
アプリケーションのcrash情報をキャプチャしたい場合は、独自のUncaughtExceptionHandlerを定義します. もちろん、私たちは自分のUncaughtExceptionHandlerで 中にはcrash情報を保存し、必要に応じて私たちのサーバーにアップロードすることができ、ユーザーのcrash情報を簡単に収集することができます.
2 native層の異常処理
我々の応用がc/c++に用いられる場合,native層の異常処理も収集する必要がある.Androidの下位層がLinuxベースであることはよく知られており,native層の未捕獲異常は捕獲信号で処理できる.Nativeレイヤが異常終了するとSIGKILL信号が発行されるので、sigaactionを使用してSIGKILL信号を処理する信号処理関数を登録することで、nativeレイヤの未キャプチャ異常を収集することができます.ここでは、大まかなコードフレームワークを示します.
void sigkill_handler(int signo){
   //    ,       
}
void install(){
    struct sigaction act, oldact;
    act.sa_handler = sigkill_handler;
    sigaddset(&act.sa_mask, SIGKILL);

    sigaction(SIGKILL, &act, &oldact);//        
    ......
}

3 インプリメンテーション
上記の説明に合わせて、以下では独自のUncaughtExceptionHandlerを定義します. ,この例ではJavaレイヤのcrash収集のみを処理し,収集したcrash情報をsdカードに保存する.ここではカスタムcrashプロセッサにAppCR(Application)という名前を付けました Crash Response).
まずErrorReporterを定義します ,UncaughtExceptionHandlerを実現 :
public class ErrorReporter implements UncaughtExceptionHandler {

    private final Application mContext;
    private final ReporterExecutor mReporterExecutor;

    ErrorReporter(Application context, boolean enabled) {
        mContext = context;

        final Thread.UncaughtExceptionHandler defaultExceptionHandler = Thread
                .getDefaultUncaughtExceptionHandler();
        mReporterExecutor = new ReporterExecutor(context, defaultExceptionHandler);
        mReporterExecutor.setEnabled(enabled);
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(final Thread thread,final Throwable ex) {
        // TODO Auto-generated method stub
        LogUtil.i(AppCR.LOG_TAG,"catch uncaughtException");

        mReporterExecutor.execute(thread, ex);
    }

    public void setEnabled(boolean enabled) {
        LogUtil.i(AppCR.LOG_TAG, "AppCR is" + (enabled ? "enabled" : "disabled") + " for "
                + mContext.getPackageName());
    }
}

ReporterExecutorはThread.setDefaultUncaughtExceptionHandler(this)を呼び出します.を行ないます.未取得の例外が発生した場合は、mReporterExecuter.execute(thread)を呼び出します. ex);異常を処理します.
ReporterExecutor で、異常情報やオペレーティングシステムに関する情報をファイルに保存します.
public class ReporterExecutor {

    public static final String TAG = ReporterExecutor.class.getSimpleName();
    private Context mContext;
    private boolean mEnabled = false;
    private final Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
    private File mCrashInfoFile;

    public ReporterExecutor(Context context,
                            Thread.UncaughtExceptionHandler defaultedExceptionHandler) {

        mContext = context;
        mDefaultExceptionHandler = defaultedExceptionHandler;

        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            File path = Environment.getExternalStorageDirectory();
            File dir = new File(path, "BleFairy");
            if (!dir.exists()) {
                dir.mkdirs();
            }

            mCrashInfoFile = new File(dir, getCrashFileName());
            if (!mCrashInfoFile.exists()) {
                try {
                    mCrashInfoFile.createNewFile();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public boolean isEnabled() {
        return mEnabled;
    }

    public void setEnabled(boolean enabled) {
        mEnabled = enabled;
    }

    public void execute(Thread thread, Throwable ex) {

        if (!mEnabled) {
            endApplication(thread, ex);
            return;
        }

        // log crash info to file
        Log.w(AppCR.LOG_TAG, "getSysInfo.");
        CrashReportData data = CrashReportData.produce(thread, ex, mContext);
        data.writeToFile(mCrashInfoFile);
        endApplication(thread, ex);
       
    }

    private void endApplication(Thread thread, Throwable ex) {

        if (mDefaultExceptionHandler != null) {
            Log.w(AppCR.LOG_TAG, "execute default uncaughtException handler.");
            mDefaultExceptionHandler.uncaughtException(thread, ex);
        } else {
            Log.w(AppCR.LOG_TAG, "kill process and exit.");
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(10);
        }
    }

    private String getCrashFileName() {
        StringBuilder ret = new StringBuilder();
        Calendar calendar = Calendar.getInstance();

        ret.append("crash_");
        ret.append(calendar.get(Calendar.YEAR));
        int month = calendar.get(Calendar.MONTH)+1;
        int date = calendar.get(Calendar.DATE);
        if(month < 10 ){
            ret.append("0");
        }
        ret.append(month);
        if(date<10){
            ret.append("0");
        }
        ret.append(date);
        ret.append(".txt");
        return ret.toString();
    }
}

CrashReportData クラスは、例外情報を保存するために使用します.
public class CrashReportData {

    private final String info;

    private CrashReportData(String crashInfo) {
        this.info = crashInfo;
    }

    public static CrashReportData produce(Thread thread, Throwable ex, Context context) {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream print = new PrintStream(out);
        out.toString();

        print.append("crahtime:" + TimeUtil.getCurTimeString()).append("
"); print.append(SysInfo.getSysInfo(context)).append("
"); print.append(thread.getName()).append("(threadID=" + thread.getId() + ")").append("
"); print.append(ex.getMessage()).append("
"); ex.printStackTrace(print); return new CrashReportData(out.toString()); } public void writeToFile(File file) { PrintWriter printer = null; try { // append to the end of crash file BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file, true)); printer = new PrintWriter(out); printer.println(info); printer.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (printer != null) { printer.close(); } LogUtil.w(AppCR.LOG_TAG, "write exception info to file over."); } } @Override public String toString() { // TODO Auto-generated method stub return info; // return super.toString(); } }

 SysInoクラス:
public class SysInfo {

    public static String getSysInfo(Context context) {
        StringBuilder info = new StringBuilder();
        info.append("osVersion=Android ").append(Build.VERSION.RELEASE).append("
"); info.append("model=").append(Build.MODEL).append("
"); info.append("brand=").append(Build.BRAND).append("
"); LogUtil.i(AppCR.LOG_TAG, "sys info collect over."); return info.toString(); } }

AppCRを使用してcrashプロセッサをインストールします.
public class AppCR {
    public static final String LOG_TAG=AppCR.class.getSimpleName();
    private static ErrorReporter mErrorReporter;

    public static void init(Application application){
        init(application,true);
    }

    public static void init(Application application,boolean enabled){
        mErrorReporter = new ErrorReporter(application, enabled);
    }
}

Applicationに上記のカスタムアプリCRをインストールすればいいです.
public class BeaconApplication extends Application {

    private final String TAG = "BeaconFairy.BeaconApplication";
    

    @Override
    public void onCreate() {
        super.onCreate();
        AppCR.init(this,true);
    }

}

注意しなければならないのは、自分のApplicationを定義してmanifestを変更すればいいので、SDカードを書く権限を加えることを覚えておく必要があります.
<application
        android:name=".BeaconApplication"
        android:allowBackup="true"
        android:allowTaskReparenting="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
         ........
</application>
SDカードを書く権限を申請する:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

ここまで、カスタマイズしたcrash情報収集プログラムAppCRが完了しました.