Android 全域性異常捕獲的完整實踐
阿新 • • 發佈:2019-01-09
前言
在 Android 開發中在所難免的會出現程式 Crash,俗稱崩潰。使用者的隨意性訪問出現測試時未知的 Bug 導致我們的程式 Crash,此時我們是無法直接獲取的錯誤 Log 的。Crash 極大的影響使用者體驗,甚至很可能因此被解除安裝。為了獲取錯誤的 Log 資訊,就強烈需要捕獲全域性的異常資訊,當程式出現Crash 資訊,記錄 Log,上傳到伺服器,以便開發分析、修復 Bug。
原理
當程式中出現異常,會自動檢測該異常是否被 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