1. 程式人生 > >Android 應用崩潰日誌的收集和上傳

Android 應用崩潰日誌的收集和上傳

如何將應用崩潰日誌收集起來?

Android 應用難以避免的會 crash ,也稱為崩潰,無論你的程式多完美,總是無法避免 crash 的發生。這對使用者來說是很不友好的,也是開發者所不願意看到的。更糟糕的是,當用戶發生了 crash ,開發者卻不知道程式為何 crash,即使開發者想去解決這個 crash 也由於不知道使用者當時的 crash 資訊,所以往往也無能為力。這篇部落格,便教大家怎樣獲取到應用的崩潰日誌並且上傳。(本篇部落格部分素材取自開發藝術探索)

下面就開始吧

Android 系統提供了處理這類問題的方法,Thread 類中提供了一個方法 setDefaultUncaughtExceptionHandler

    /**
     * Set the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception, and no other handler has been defined
     * for that thread.
     * 
     * @param eh the object to use as the default uncaught exception handler.
     * If <tt>null</tt> then
there is no default handler. * / public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { defaultUncaughtExceptionHandler = eh; }

這個方法可以設定系統的預設異常處理器,這個方法就可以解決上面所提的 crash 問題,當 crash 發生的時候,系統就會回撥 UncaughtExceptionHandler 的 uncaughtException 方法,在 uncaughtException 方法中可以獲取到異常資訊,可以選擇把異常資訊儲存到 SD卡中*,然後在合適的時機通過網路將 crash 資訊上傳到伺服器上,這樣開發人員就可以分析使用者 crash 的場景,從而在後面的版本修復此類 crash 了。

經過上面的分析,我們就有思路了。首先,我們需要實現一個 UncaughtExceptionHandler 物件,在它的 uncaughtException 方法中獲取異常資訊並將其存入 SD卡中或者上傳到伺服器上,然後呼叫 Thread 的 setDefultUncaughtExceptionHandler 方法將它設定為執行緒預設的異常處理器。說到這裡,可別蒙圈,下面直接上程式碼:


import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Create by ChenHao on 2018/6/299:30
 * use : 應用異常處理類
 * 使用方式: 在Application 中初始化  CrashHandler.getInstance().init(this);
 */
public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    public static final boolean DEBUG = true;
    /**
     * 檔名
     */
    public static final String FILE_NAME = "crash";
    /**
     * 異常日誌 儲存位置為根目錄下的 Crash資料夾
     */
    private static final String PATH = Environment.getExternalStorageDirectory().getPath() +
            "/Crash/log/";
    /**
     * 檔名字尾
     */
    private static final String FILE_NAME_SUFFIX = ".trace";

    private static CrashHandler sInstance = new CrashHandler();
    private UncaughtExceptionHandler mDefaultCrashHandler;
    private Context mContext;


    private CrashHandler() {

    }

    public static CrashHandler getInstance() {
        return sInstance;
    }

    /**
     * 初始化
     *
     * @param context
     */
    private void init(Context context) {
        //得到系統的應用異常處理器
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        //將當前應用異常處理器改為預設的
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();

    }


    /**
     * 這個是最關鍵的函式,當系統中有未被捕獲的異常,系統將會自動呼叫 uncaughtException 方法
     *
     * @param thread 為出現未捕獲異常的執行緒
     * @param ex     為未捕獲的異常 ,可以通過e 拿到異常資訊
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        //匯入異常資訊到SD卡中
        try {
            dumpExceptionToSDCard(ex);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //這裡可以上傳異常資訊到伺服器,便於開發人員分析日誌從而解決Bug
        uploadExceptionToServer();
        ex.printStackTrace();
        //如果系統提供了預設的異常處理器,則交給系統去結束程式,否則就由自己結束自己
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler.uncaughtException(thread, ex);
        } else {
            Process.killProcess(Process.myPid());
        }

    }

    /**
     * 將異常資訊寫入SD卡
     *
     * @param e
     */
    private void dumpExceptionToSDCard(Throwable e) throws IOException{
        //如果SD卡不存在或無法使用,則無法將異常資訊寫入SD卡
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (DEBUG) {
                Log.w(TAG, "sdcard unmounted,skip dump exception");
                return;
            }
        }
        File dir = new File(PATH);
        //如果目錄下沒有資料夾,就建立資料夾
        if (!dir.exists()) {
            dir.mkdirs();
        }
        //得到當前年月日時分秒
        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
        //在定義的Crash資料夾下建立檔案
        File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);

        try{
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
            //寫入時間
            pw.println(time);
            //寫入手機資訊
            dumpPhoneInfo(pw);
            pw.println();//換行
            e.printStackTrace(pw);
            pw.close();//關閉輸入流
        } catch (Exception e1) {
           Log.e(TAG,"dump crash info failed");
        }

    }

    /**
     * 獲取手機各項資訊
     * @param pw
     */
    private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
       //得到包管理器
        PackageManager pm = mContext.getPackageManager();
        //得到包物件
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
        //寫入APP版本號
        pw.print("App Version: ");
        pw.print(pi.versionName);
        pw.print("_");
        pw.println(pi.versionCode);
        //寫入 Android 版本號
        pw.print("OS Version: ");
        pw.print(Build.VERSION.RELEASE);
        pw.print("_");
        pw.println(Build.VERSION.SDK_INT);
        //手機制造商
        pw.print("Vendor: ");
        pw.println(Build.MANUFACTURER);
        //手機型號
        pw.print("Model: ");
        pw.println(Build.MODEL);
        //CPU架構
        pw.print("CPU ABI: ");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            pw.println(Build.SUPPORTED_ABIS);
        }else {
            pw.println(Build.CPU_ABI);
        }
    }

    /**
     * 將錯誤資訊上傳至伺服器
     */
    private void uploadExceptionToServer() {

    }
}

大家可以在 uploadExceptionToServer 方法中寫上自己上傳伺服器的邏輯,最後的最後,別忘了在 application 中初始化我們的異常處理器。

 CrashHandler.getInstance().init(this);

部落格到這裡就結束了,點個贊吧。