Android 全域性異常捕獲之CrashHandler
阿新 • • 發佈:2019-02-12
一個App上線或者投入到生產環境的時候崩潰了,還不知道是什麼原因,這肯定是開發者的痛...所以肯定要加入全域性異常捕獲,如果專案較大的話,可以考慮加入第三方諸如友盟的崩潰統計外掛,以達到異常捕獲的效果!
Crash,可以理解為崩潰、垮臺,通常來講就是App執行期間發生了不可預料的錯誤,雖然在經歷釋出之前,測試人員進行了大量的測試,但是並不能保證App的正常執行,總會或多或少有一些BUG的。
Java的Thread中有一個UncaughtExceptionHandler介面,該介面的作用主要是為了當Thread 因未捕獲的異常而突然終止時,呼叫處理程式。我們可以通過setDefaultUncaughtExceptionHandler方法,來改變異常預設處理程式。
實現程式碼如下:
package com.yuyh.utils; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; /** * UncaughtException處理類,當程式發生Uncaught異常的時候,有該類來接管程式,並記錄傳送錯誤報告. * <p> * Created by yuyuhang on 15/12/7. */ public class CrashHandler implements UncaughtExceptionHandler { //系統預設的UncaughtException處理類 private Thread.UncaughtExceptionHandler mDefaultHandler; //CrashHandler例項 private static CrashHandler INSTANCE; //程式的Context物件 private Context mContext; //用來儲存裝置資訊和異常資訊 private Map<String, String> infos = new HashMap<String, String>(); private CrashHandler() { } /** * 獲取CrashHandler例項 ,單例模式 */ public static CrashHandler getInstance() { if (INSTANCE == null) INSTANCE = new CrashHandler(); 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(3000); } catch (InterruptedException e) { LogUtils.e(e.toString()); } //退出程式 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } } /** * 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成. * * @param ex * @return true:如果處理了該異常資訊;否則返回false. */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } //收集裝置引數資訊 collectDeviceInfo(mContext); //儲存日誌檔案 saveCrashInfo2File(ex); return true; } /** * 收集裝置引數資訊 * * @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) { LogUtils.e("CrashHandleran.NameNotFoundException---> 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) { LogUtils.e("CrashHandler.NameNotFoundException---> an error occured when collect crash info", e); } } } /** * 儲存錯誤資訊到檔案中 * * @param ex * @return 返回檔名稱, 便於將檔案傳送到伺服器 */ private String saveCrashInfo2File(Throwable ex) { StringBuffer sb = new StringBuffer(); sb.append("---------------------sta--------------------------"); for (Map.Entry<String, String> entry : infos.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key + "=" + value + "\n"); } 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); sb.append("--------------------end---------------------------"); LogUtils.e(sb.toString()); return null; } }
然後我們在Application中,對CrashHandler進行初始化。
import android.app.Application; import android.content.Context; import com.yuyh.utils.CrashHandler; /** * Created by yuyuhang on 15/12/7. */ public class App extends Application { public static Context mContext; @Override public void onCreate() { super.onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(getApplicationContext()); mContext = this; } }
這樣的話,當程式程式碼中並未捕獲異常,但發生了異常的時候,就會交由CrashHandler進行處理,異常資訊可以儲存到日誌檔案中。日誌檔案記錄請參考:Android 日誌列印工具類 可顯示列印所在的方法和行號
這樣子,就能把異常資訊及其異常發生所在的位置,儲存在日誌檔案中。