1. 程式人生 > >Android 全域性異常捕獲的完整實踐

Android 全域性異常捕獲的完整實踐

前言

在 Android 開發中在所難免的會出現程式 Crash,俗稱崩潰。使用者的隨意性訪問出現測試時未知的 Bug 導致我們的程式 Crash,此時我們是無法直接獲取的錯誤 Log 的。Crash 極大的影響使用者體驗,甚至很可能因此被解除安裝。為了獲取錯誤的 Log 資訊,就強烈需要捕獲全域性的異常資訊,當程式出現Crash 資訊,記錄 Log,上傳到伺服器,以便開發分析、修復 Bug。

Crash

原理

當程式中出現異常,會自動檢測該異常是否被 try catch 捕獲,如果沒有捕獲,則向上傳遞,直到被系統捕獲、處理。系統預設有一個實現了 Thread.UncaughtExceptionHandler 介面的物件,並在初始化註冊。專門用來捕獲異常。我們要做的就是實現自己的 UncaughtExceptionHandler ,並通過 set 方法,替換掉系統預設的。

實現

定義一個類,實現 Thread.UncaughtExceptionHandler,提供初始化方法,並在 Application 的 onCreate 初始化。由於異常捕獲全域性只需要一個物件,所以最好用單例。

public class CrashHandler implements 
                Thread.UncaughtExceptionHandler{

    private static Object lock = new Object();

    private CrashHandler(){
        // Empty Constractor
} private static CrashHandler mCrashHandler; private Context mContext; public static CrashHandler getInstance(){ synchronized (lock) { if (mCrashHandler == null) { synchronized (lock) { if (mCrashHandler == null) { mCrashHandler = new
CrashHandler(); } } } return mCrashHandler; } } /* 初始化 */ public void init(Context context){ this.mContext = context; Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, final Throwable ex) { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); ToastUtils.show("程式發生了點小意外,即將關閉... "+ ex.getMessage()); Looper.loop(); SystemClock.sleep(3000); // 將Activity的棧清空 AppManager.removeAll(); // 退出程式 Process.killProcess(Process.myPid()); // 關閉虛擬機器,徹底釋放記憶體空間 System.exit(0); } }).start(); } }

注意: 學 JavaSE 時,知道 Java 的異常處理是另開執行緒的,所以不能在子執行緒直接更新UI,需要加上 Loop。

BaseApplication.java

public class BaseApplication extends Application{

    @Override
    public void onCreate() {
        super.onCreate();

        // ...

        // 註冊全域性異常處理
        CrashHandler.getInstance().init(this);
    }
}

優化

以上是基本的實現,既然異常捕獲了,就要將異常和裝置資訊上傳到伺服器。排除沒有必要捕獲的異常。

public class CrashHandler implements 
                Thread.UncaughtExceptionHandler{

    private static Object lock = new Object();

    private CrashHandler(){
        // Empty Constractor
    }

    private static CrashHandler mCrashHandler;
    private Context mContext;
    private UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    public static CrashHandler getInstance(){
        synchronized (lock) {
            if (mCrashHandler == null) {
                synchronized (lock) {
                    if (mCrashHandler == null) {
                        mCrashHandler = new CrashHandler();
                    }
                }
            }

            return mCrashHandler;
        }
    }

    /* 初始化 */
    public void init(Context context){
        this.mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void uncaughtException(Thread thread, final Throwable ex) {
        if (isHandler(ex)){
            handlerException(ex);
        } else {
            defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
        }
    }

    private boolean isHandler(Throwable ex){
        // 排序不需要捕獲的情況
        if (ex == null){
            return false;
        }

        // ...

        return true;

    }

    private void handlerException(final Throwable ex) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();

                ToastUtils.show("程式發生了點小意外,即將關閉... "+
                        ex.getMessage());
                Looper.loop();

                // 上傳至伺服器
                connectionServer();

                // 將Activity的棧清空
                AppManager.removeAll();
                // 退出程式
                Process.killProcess(Process.myPid());
                // 關閉虛擬機器,徹底釋放記憶體空間
                System.exit(0);

            }

        }).start();
    }
}

裝置資訊

可在 android.os.Build 類找

Build.BOARD // 主機板
Build.BRAND // android系統定製商
Build.CPU_ABI // cpu指令集
Build.DEVICE // 裝置引數
Build.DISPLAY // 顯示屏引數
Build.FINGERPRINT // 硬體名稱
Build.HOST
Build.ID // 修訂版本列表
Build.MANUFACTURER // 硬體製造商
Build.MODEL // 版本
Build.PRODUCT // 手機制造商
Build.TAGS // 描述build的標籤
Build.TIME
Build.TYPE // builder型別
Build.USER