Android 收集程式崩潰異常資訊

DeMonnnnnn發表於2018-09-21

前言

在日常開發中,如果遇到Android程式崩潰,我們只需要開啟AndroidStudio的控制檯的Logcat便能檢視到程式的崩潰資訊。
可是當程式上線後,如果出現程式崩潰的情況,我們可能很難找到問題。這就需要我們的程式能夠自己收集到崩潰的異常資訊,然後再適當的時候將這些資訊上傳到伺服器,然後我們獲取到這些異常資訊後,在下個更新的版本將其修復。

思路

  1. Android使用Thread.UncaughtExceptionHandler介面類來處理程式崩潰的情況,我們也可以通過該介面實現程式崩潰時的資訊收集等操作。
  2. 單例模式實現,初始化獲取系統預設的UncaughtException處理器,可以將部分操作交給預設處理器處理,然後設定CrashHandler為程式的預設處理器。
  3. 實現介面方法uncaughtException(Thread thread, Throwable ex),收集程式,裝置,崩潰異常等資訊。收集完資訊後,交給系統自己處理。
  4. 為方便檢視,收集的資訊儲存到txt檔案中,預設儲存在sd卡根目錄/你的app_name/Crash/ (如:/storage/emulated/0/ErrorCatch/Crash/2018-09-21 09:42:59.text)
  5. getCrashReportFiles(Context ctx)方法可以返回所有的錯誤資訊檔案路徑,可以根據檔案路徑上傳到伺服器,然後將其刪除,防止重複上傳。

程式碼&Demo

GitHub:https://github.com/DeMonLiu623/CrashHandler

效果

在Demo中我們模擬實現了一個陣列越界異常,收集到的異常資訊如下圖:

在這裡插入圖片描述

實現

程式碼很簡單,而且註釋很詳細。

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "CrashHandler";
    /**
     * 系統預設的UncaughtException處理類
     */
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    /**
     * 程式的Context物件
     */
    private Context mContext;
    /**
     * 錯誤報告檔案的副檔名
     */
    private static final String CRASH_REPORTER_EXTENSION = ".text";

    /**
     * CrashHandler例項
     */
    private static CrashHandler INSTANCE;

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

    /**
     * 獲取CrashHandler例項 ,單例模式
     */
    public static CrashHandler getInstance() {
        if (INSTANCE == null) {
            synchronized (CrashHandler.class) {
                if (INSTANCE == null) {
                    INSTANCE = new CrashHandler();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 初始化,註冊Context物件,
     * 獲取系統預設的UncaughtException處理器,可以將部分操作交給預設處理器處理
     * 設定該CrashHandler為程式的預設處理器
     *
     * @param ctx
     */
    public void init(Context ctx) {
        mContext = ctx;
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 當UncaughtException發生時會轉入該函式來處理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        handleException(ex);
        if (mDefaultHandler != null) {
            //收集完資訊後,交給系統自己處理崩潰
            mDefaultHandler.uncaughtException(thread, ex);
        }
    }

    /**
     * 自定義錯誤處理,收集錯誤資訊
     * 傳送錯誤報告等操作均在此完成.
     * 開發者可以根據自己的情況來自定義異常處理邏輯
     */
    private void handleException(Throwable ex) {
        if (ex == null) {
            Log.w(TAG, "handleException--- ex==null");
            return;
        }
        String msg = ex.getLocalizedMessage();
        if (msg == null) {
            return;
        }
        //收集裝置資訊
        //儲存錯誤報告檔案
        saveCrashInfoToFile(ex);
    }


    /**
     * 獲取錯誤報告檔案路徑
     *
     * @param ctx
     * @return
     */
    public static String[] getCrashReportFiles(Context ctx) {
        File filesDir = new File(getCrashFilePath(ctx));
        String[] fileNames = filesDir.list();
        int length = fileNames.length;
        String[] filePaths = new String[length];
        for (int i = 0; i < length; i++) {
            filePaths[i] = getCrashFilePath(ctx) + fileNames[i];
        }
        return filePaths;
    }

    /**
     * 儲存錯誤資訊到檔案中
     *
     * @param ex
     * @return
     */
    private void saveCrashInfoToFile(Throwable ex) {
        Writer info = new StringWriter();
        PrintWriter printWriter = new PrintWriter(info);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        String result = info.toString();
        printWriter.close();
        StringBuilder sb = new StringBuilder();
        @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
        String now = sdf.format(new Date());
        sb.append("TIME:").append(now);//崩潰時間
        //程式資訊
        sb.append("\nAPPLICATION_ID:").append(BuildConfig.APPLICATION_ID);//軟體APPLICATION_ID
        sb.append("\nVERSION_CODE:").append(BuildConfig.VERSION_CODE);//軟體版本號
        sb.append("\nVERSION_NAME:").append(BuildConfig.VERSION_NAME);//VERSION_NAME
        sb.append("\nBUILD_TYPE:").append(BuildConfig.BUILD_TYPE);//是否是DEBUG版本
        //裝置資訊
        sb.append("\nMODEL:").append(android.os.Build.MODEL);
        sb.append("\nRELEASE:").append(Build.VERSION.RELEASE);
        sb.append("\nSDK:").append(Build.VERSION.SDK_INT);
        sb.append("\nEXCEPTION:").append(ex.getLocalizedMessage());
        sb.append("\nSTACK_TRACE:").append(result);
        try {
            FileWriter writer = new FileWriter(getCrashFilePath(mContext) + now + CRASH_REPORTER_EXTENSION);
            writer.write(sb.toString());
            writer.flush();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取資料夾路徑
     *
     * @param context
     * @return
     */
    private static String getCrashFilePath(Context context) {
        String path = null;
        try {
            path = Environment.getExternalStorageDirectory().getCanonicalPath() + "/" + context.getResources().getString(R.string.app_name) + "/Crash/";
            File file = new File(path);
            if (!file.exists()) {
                file.mkdirs();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return path;
    }
}

相關文章