android 之 Crash信息的持久化处理

需求: 持久化运行时异常的信息

 

1.CrashHandler.java

 

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

/**
 * 保存运行时异常的信息到手机上
 * TODO 异常会保存3次, 应用会重启两次,这个不知道什么原因
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private final String FILE_NAME_FORMAT = "yyyy_MM_dd";
    private final String FILE_NAME_PREFIXES = "crash_";
    private final String FILE_NAME_EXTENSION = ".log";
    private final String CRASH_TIME_FORMAT = "[yyyy-MM-dd HH:mm:ss:sss]";
    private final String PARENT_DIRECTORY_NAME = "logs";

    private Context mContext;
    private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    /** 文件路径 */
    private String filePath;
    private String fileName;

    ///////////////////////////////////////////////////////////////////////////
    // 单例模式
    private static CrashHandler mInstance = new CrashHandler();

    private CrashHandler() {}

    public static CrashHandler getInstance() {
        return mInstance;
    }
    ///////////////////////////////////////////////////////////////////////////

    /** 在 自定义的Application 中 调用此方法即可 */
    public void init(Context context) {
        mContext = context;
        filePath = mContext.getExternalFilesDir(PARENT_DIRECTORY_NAME) + File.separator;
        fileName = FILE_NAME_PREFIXES + new SimpleDateFormat(FILE_NAME_FORMAT).format(System.currentTimeMillis()) + FILE_NAME_EXTENSION;
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /** 异常发生时,系统回调的函数,我们在这里处理一些操作 */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        // 如果没有自定义处理方式就使用系统的方式
        if (!handleException(ex) && defaultUncaughtExceptionHandler != null) {
            defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
        } else {
            // 让线程停止一会是为了显示Toast信息给用户,然后Kill程序
            SystemClock.sleep(1000);
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(0);
        }
    }

    /** 自定义 crash 处理 */
    private boolean handleException(Throwable ex) {
        if (ex == null) return true;
        // 保存信息
        saveInfo(mContext, ex);

        // 显示提示信息,需要在线程中显示Toast
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序遭遇异常,即将退出!", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }).start();

        return true;
    }

    /** 保存数据 */
    private String saveInfo(Context context, Throwable ex) {
        StringBuffer sb = new StringBuffer("\n");
        // crash 的时间
        sb.append("crashTime = ").append(new SimpleDateFormat(CRASH_TIME_FORMAT).format(System.currentTimeMillis())).append("\n");
        // 设备信息
        for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append(" = ").append(value).append("\n");
        }
        // 异常信息
        sb.append(obtainExceptionInfo(ex));
        // 数据存储
        File dir = new File(filePath);
        if (!dir.exists()) dir.mkdir();
        try {
            FileOutputStream fos = new FileOutputStream(filePath + fileName, true); // 追加的方式
            fos.write(sb.toString().getBytes());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return fileName;
    }


    /** 获取设备信息 */
    private HashMap<String, String> obtainSimpleInfo(Context context) {
        HashMap<String, String> map = new HashMap<String, String>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        map.put("versionName", mPackageInfo.versionName);
        map.put("versionCode", "" + mPackageInfo.versionCode);

        map.put("MODEL", "" + Build.MODEL);
        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
        map.put("PRODUCT", "" + Build.PRODUCT);

        return map;
    }


    /** 获取 crash 信息 */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter mStringWriter = new StringWriter();
        PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
        throwable.printStackTrace(mPrintWriter);
        mPrintWriter.close();
        return mStringWriter.toString();
    }

}

 

 

2.自定义的Application 中注册

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler.getInstance().init(this);
        throw new NullPointerException("test crash " + android.os.Process.myPid());
    }
}

 

3.adb shell cat /sdcard/Android/data/<package_name>/files/logs/crash_2016_04_20.log

 

posted @ 2016-04-20 23:09  MarcoReus  阅读(294)  评论(0编辑  收藏  举报