天天看點

android 崩潰日志捕獲,安卓Java崩潰的捕獲和日志記錄

Android的兩種崩潰

Android 崩潰分為 Java 崩潰和 Native崩潰兩種。

Java崩潰的知識點

android 崩潰日志捕獲,安卓Java崩潰的捕獲和日志記錄

Java崩潰.png

Java崩潰的原因

簡單來說,Java崩潰就是在Java代碼中,出現了未被捕獲的異常,導緻應用程式異常退出。

Java異常的歸類

Java的異常可分為分為可查的異常(checkedexceptions)和不可查的異常(unchecked exceptions)

常見的異常可歸類為如下圖:

android 崩潰日志捕獲,安卓Java崩潰的捕獲和日志記錄

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位址: