Android 全局Crash捕捉和處理

有時候app會出現崩潰現象咱們沒法徹底控制,而系統的崩潰界面比較差,可能影響用戶體驗,並且咱們須要對崩潰現象進行處理,所以有了這個需求。android

app運行有一個主線程,也就是UI線程,系統奔潰了通常來講是ui線程崩潰了(咱們本身設置的一異步線程,通常本身都會作catch),因此,處理的核心就是 Thread.setDefaultUncaughtExceptionHandler(this);
得到主線程中的設置出現未捕捉的異常的Handler,而後本身從新設置這個handler服務器

本身實現的類CrashHandlr集成UncaughtExceptionHandler,主要經過重載uncaughtException方法來處理異常。
在代碼中,handleException() 經過一個異步線程顯示toast,並進行
收集設備參數信息: collectDeviceInfo(mContext);
保存日誌文件:saveCrashInfo2File(ex);app

代碼以下:異步

/**
 * 收集手機全局崩潰時的exception,並log到本地
 * 
 * @author Jackland_zgl
 *
 */
public class CrashHandler implements UncaughtExceptionHandler {

public static final String CrashFilePath = "/sdcard/xiaomai/crashlog/";
public static final int LogFileLimit = 10;

public static final String TAG = "CrashHandler";

//系統默認的UncaughtException處理類 
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler實例
private static CrashHandler INSTANCE = new CrashHandler();
//程序的Context對象
private Context mContext;
//用來存儲設備信息和異常信息
private Map<String, String> infos = new HashMap<String, String>();

//用於格式化日期,做爲日誌文件名的一部分
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

/** 保證只有一個CrashHandler實例 */
private CrashHandler() {
}

/** 獲取CrashHandler實例 ,單例模式 */
public static CrashHandler getInstance() {
    return INSTANCE;
}

/**
 * 初始化
 * 
 * @param context
 */
public void init(Context context) {
    mContext = context;
    //獲取系統默認的UncaughtException處理器
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    //設置該CrashHandler爲程序的默認處理器
    Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
 * 當UncaughtException發生時會轉入該函數來處理
 */
@Override
public void uncaughtException(Thread thread, Throwable ex) {
    if (!handleException(ex) && mDefaultHandler != null) {
        //若是用戶沒有處理則讓系統默認的異常處理器來處理
        mDefaultHandler.uncaughtException(thread, ex);
    } else {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Log.e(TAG, "error : ", e);
        }
        //退出程序
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(1);
    }
}


/**
 * 收集設備參數信息
 * @param ctx
 */
public void collectDeviceInfo(Context ctx) {
    try {
        PackageManager pm = ctx.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
        if (pi != null) {
            String versionName = pi.versionName == null ? "null" : pi.versionName;
            String versionCode = pi.versionCode + "";
            infos.put("versionName", versionName);
            infos.put("versionCode", versionCode);
        }
    } catch (NameNotFoundException e) {
        Log.e(TAG, "an error occured when collect package info", e);
    }
    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
        try {
            field.setAccessible(true);
            infos.put(field.getName(), field.get(null).toString());
        } catch (Exception e) {
            Log.e(TAG, "an error occured when collect crash info", e);
        }
    }
}

/**
 * 保存錯誤信息到文件中
 * 
 * @param ex
 * @return  返回文件名稱,便於將文件傳送到服務器
 */
private int saveCrashInfo2File(Throwable ex) {

    //將設備信息變成string
    StringBuffer sb = new StringBuffer();
    for (Map.Entry<String, String> entry : infos.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        sb.append(key + "=" + value + "\n");
    }

    //遞歸獲取所有的exception信息
    Writer writer = new StringWriter();
    PrintWriter printWriter = new PrintWriter(writer);
    ex.printStackTrace(printWriter);
    Throwable cause = ex.getCause();
    while (cause != null) {
        cause.printStackTrace(printWriter);
        cause = cause.getCause();
    }
    printWriter.close();
    String result = writer.toString();
    sb.append(result); //將寫入的結果

    //構造文件名
    long timestamp = System.currentTimeMillis();
    String time = formatter.format(new Date());
    String fileName = "crash-" + time + "-" + timestamp + ".log";

    //寫文件和限制數量
    FileUtils.writeFile2SDCard( CrashFilePath,fileName, sb.toString());
    cleanLogFileToN(CrashFilePath);

    return 1;
}

Comparator<File> newfileFinder = new Comparator<File>(){

    @Override
    public int compare(File x, File y) {
        // TODO Auto-generated method stub
        if (x.lastModified()>y.lastModified()) return 1;
        if (x.lastModified()<y.lastModified()) return -1;
        else return 0;
    }

};

private int cleanLogFileToN(String dirname){
    File dir = new File(dirname);
    if (dir.isDirectory()){
        File[] logFiles = dir.listFiles();
        if (logFiles.length>LogFileLimit){
            Arrays.sort(logFiles,newfileFinder );  //從小到大排
            //刪掉N個之前的
            for (int i=0;i<logFiles.length-LogFileLimit;i++){
                logFiles[i].delete();
            }
        }
    }

    return 1;
}


/**
 * 自定義錯誤處理,收集錯誤信息 發送錯誤報告等操做均在此完成.
 * 
 * @param ex
 * @return true:若是處理了該異常信息;不然返回false.
 */
private boolean handleException(Throwable ex) {
    if (ex == null) {
        return false;
    }
    //使用Toast來顯示異常信息
    new Thread() {
        @Override
        public void run() {
            Looper.prepare();
            Toast.makeText(mContext, "很抱歉,程序出現異常", Toast.LENGTH_LONG).show();
            Looper.loop();
        }
    }.start();

    //收集設備參數信息 
    collectDeviceInfo(mContext);
    //保存日誌文件 
    saveCrashInfo2File(ex);
    return true;
}

}ide

參考: 參考博客1函數


文章爲原創,轉載請註明出處。oop

相關文章
相關標籤/搜索