Android的兩種崩潰
Android 崩潰分為 Java 崩潰和 Native崩潰兩種。
Java崩潰的知識點
Java崩潰.png
Java崩潰的原因
簡單來說,Java崩潰就是在Java代碼中,出現了未被捕獲的異常,導緻應用程式異常退出。
Java異常的歸類
Java的異常可分為分為可查的異常(checkedexceptions)和不可查的異常(unchecked exceptions)
常見的異常可歸類為如下圖:
Throwable.png
其中Error和RuntimeException是unchecked exceptions,編譯器預設無法通過對其處理。其餘checkedexceptions,需要我們在代碼中try-catch。
崩潰的捕捉
UncaughtExceptionHandler
先來看一下這個接口的作用
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
大緻意思就是如果線程發生未處理的異常,會調用UncaughtExceptionHandler的uncaugthException方法去處理異常,如果該線程沒有設定UncaughtExceptionHandler,則會去調用ThreadGroup的UncaughtExceptionHandler,若還是沒有,則最終getDefaultUncaughtExceptionHandler來處理異常。
系統預設的UncaughtExceptionHandler
日常當我們應用崩潰時,會有一個預設的系統彈窗,告知我們應用崩潰,那系統的崩潰是如何定義的呢?源碼如下,注釋已經比較完整。
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}
該接口實作在RuntimeInit類中,并在Runtime初始化時寫入設定成我們預設的異常處理類
RuntimeInit.class
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
......
}
自定義崩潰捕捉
到這裡思路已經很清晰了,我們要做的就是自己實作一個對崩潰處理的UncaughtExceptionHandler,那麼我們應該設定在哪,初始化的時機在何時。我們先來看看系統初始化用到的方法,即Thread.setDefaultUncaughtExceptionHandler()。
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
這個defaultUncaughtHandler是Thread類中一個靜态的成員,是以,按道理,我們為任意一個線程設定異常處理,所有的線程都應該能共用這個異常處理器。為了在ui線程中添加異常處理Handler,我推薦大家在Application中添加而不是在Activity中添加。Application辨別着整個應用,在Android聲明周期中是第一個啟動的,早于任何的Activity、Service等。
有了以上的知識,我們就可以自己來實作一個崩潰捕捉和處理的lib啦
其實實作方法網上都大同小異,主要是對異常捕獲後的處理機制不一緻。一般會通過儲存崩潰日志并上報這種方案去解決。這裡先基礎實作崩潰日志檔案的存儲。
一個崩潰日志應該包括的基本資訊有:
崩潰原因和棧記錄
日期和APP版本資訊
機型資訊
是以我們定義基礎的異常處理器如下:
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
//将崩潰資訊記錄到檔案
dumpToFile(thread, throwable);
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
//如果系統仍有設定預設的處理器,則調用系統預設的
if (mDefaultUncaughtExceptionHandler != null) {
mDefaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
} else {
//結束程序并退出
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
記錄檔案具體如下:
private void dumpToFile(Thread thread, Throwable ex) throws IOException {
File file = null;
PrintWriter printWriter = null;
String crashTime = dataFormat.format(new Date(System.currentTimeMillis()));
String dirPath = Utils.getCrashLogPath(mContext);
File dir = new File(dirPath);
if (!dir.exists()) {
boolean ok = dir.mkdirs();
if (!ok) {
return;
}
}
//Log檔案的名字
String fileName = "Crash" + "_" + crashTime + FILE_NAME_SUFFIX;
file = new File(dir, fileName);
if (!file.exists()) {
boolean createNewFileOk = file.createNewFile();
if (!createNewFileOk) {
return;
}
}
try {
//開始寫日志
printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//崩潰時間
printWriter.println(crashTime);
//導出APP資訊
dumpAppInfo(printWriter);
//導出手機資訊
dumpPhoneInfo(printWriter);
//導出異常的調用棧資訊
ex.printStackTrace(printWriter);
Log.e(TAG, "崩潰日志輸入完成");
} catch (Exception e) {
Log.e(TAG, "導出資訊失敗");
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
好了,Java崩潰捕獲大緻就這樣。
Demo位址: