Android應(yīng)用的異常捕獲

做為程序員,最不愿意看到的就是自己寫的程序崩潰,特別是遇到?jīng)]有錯誤信息的崩潰的時候,往往程序員自己也就隨之一起崩潰了。如何捕獲Android程序產(chǎn)生異常時的崩潰信息,本文提供了隨手可行的方法,希望能夠帶來一些啟發(fā),同時解決一些生產(chǎn)上的難題。

背景

在應(yīng)用從開發(fā)慢慢過渡到線上部署的時候,開發(fā)者就逐漸切換到依靠程序日志/運行狀態(tài)來進(jìn)行問題的排查和解決。通常一些第三方廠商會提供這些服務(wù),譬如騰訊的bugly,fabric(以前叫crashlytics),Umeng等。 前兩者的異常收集和處理較好,然而有一個通用的問題就是需要將symbol文件上傳到廠商服務(wù)器,顯然對于注重應(yīng)用安全和IP保護(hù)的開發(fā)者,這個行為是值得著重權(quán)衡的。而Umeng則注重于運營數(shù)據(jù),對于異常捕獲沒有特別大的優(yōu)勢。所以本文的關(guān)注點就是如何在Android項目內(nèi)部實現(xiàn)自己異常捕獲和分析模塊。

主要會包括以下知識點(劃重點)

1. 如何捕獲Java異常

2. 如何捕獲Native異常及分析

3. 如何提示用戶發(fā)送錯誤信息

1. 如何捕獲Java 異常

這個問題乍一聽感覺沒有任何難度,這不是用以下的終極處理Crash大法就解決了嗎?

且不說這樣做的對錯,只是并不能很好的解決我們的問題,問題就在于我們對于會發(fā)生異常的地方?jīng)]有辦法完全預(yù)知,對于CheckedException 編譯器會提示我們處理,然而對于RuntimeException,總是難以預(yù)防。NPE(Null Pointer Exception)常年占據(jù)Java Eeception的Top 1 不是沒有道理。

所以我們的期望是什么,就算我們沒有指定Catch Exception,在程序發(fā)生異常的時候,也能通知到我們。幸運的是,正好就有這樣的方法。


看一下這個方法的說明,一下就明白了

至此,一切都清楚了,我們要做的就是設(shè)置一個自定義的UncaughtExceptionHandler,并且設(shè)置為Thread的Default值,這樣在異常發(fā)生時,就可以進(jìn)入到我們的處理流程當(dāng)中,簡要代碼如下:


2. 如何捕獲Native異常

與Java層的異常不同,Native層的異常捕獲要稍微復(fù)雜一些。難點主要在于當(dāng)Native 層發(fā)生異常時,JVM在收到異常信號后會直接Shutdown,所以我們的Thread.UncaughtExceptionHandler 不會被執(zhí)行。所以我們必須捕獲Linux的異常信號,并執(zhí)行我們自己的信號處理函數(shù)。例如如下展示:


在此基礎(chǔ)上,我們還必須自己來實現(xiàn)Crash Dump生成和解析,著實不太友好。所以在這里我們引用了一個三方庫,來幫我們處理。

Google-Breakpad項目地址


下面我們就來看一下如何集成Breakpad來完成我們需要的異常捕獲。

從上面的Breakpad的結(jié)構(gòu)圖可以看出,我們需要的是兩塊東西,一是Client模塊,這將被集成到App中,用作Crash的信號捕捉和dump文件生成;另外一個就是Process模塊,當(dāng)收集到用戶dump文件時,結(jié)合symbol進(jìn)行crash棧分析。

2.1 Client模塊集成

通過depot_tools 下載好整個Breakpad源碼之后,我們就可以進(jìn)行client module的編譯。通常來說有兩種編譯的方式,一是集成到項目中,利用NDK-Build 來進(jìn)行整個源碼的編譯和集成;第二個就是使用NDK toolchain來進(jìn)行單獨編譯,然后將編譯出來的靜態(tài)鏈接庫導(dǎo)入到項目中。兩者沒有很大的區(qū)別,主要區(qū)別在于前者Breakpad的src代碼會被集成到項目里,而后者則是單獨管理。之前的項目采用的是第二種方法,導(dǎo)入靜態(tài)鏈接庫。這樣的好處就是項目中jni文件夾里沒有過多跟項目業(yè)務(wù)無關(guān)的代碼,然而有一個問題沒法避免,需要用到的頭文件也需要加到項目中去,這樣也需要一個手動的維護(hù)。再加上Android Studio 2.2 以后對NDK的支持,所以本文就采用集成源碼到項目中來進(jìn)行一個展示。

?首先是創(chuàng)建jni文件夾:右鍵項目 -> new -> folder -> jni folder

?接著是拷貝breakpad/src 到j(luò)ni folder下,并刪除一些Client不需要的folder,包括build,processor,testing,tools等,清理過后的目錄結(jié)構(gòu)應(yīng)該跟以下類似:


?接著是配置Gradle:


這里的Android.mk 如果項目之前沒有的話,那么可以直接拷貝Breakpad 下src\android\sample_app\jni下面的3個文件到項目中的jni目錄下,然后根據(jù)之前拷貝的Breakpad目錄結(jié)構(gòu)做如下改動:





可以看到,在test_breakpad.cpp中,我們定義了一個jni 方法setHandler(),主要干了幾件事,設(shè)置Dmp文件的生成位置,注冊自己的DumpCallback,并且緊跟著模擬了一個Crash。

?最后我們選擇MainActivity 或者Application 中去加載我們的Library并調(diào)用jni方法。


別忘記加上寫SDCard的權(quán)限!

至此,我們的測試App 就已經(jīng)完成,運行起來之后就可以在Logcat里面看到類似如下的輸出:

Dump path: %s]

/storage/emulated/0/Android/data/com.example.capturecrash/

files/c1d1d3cb-7030-46a2-009b4598-26d2de2d.dmp

這里的dmp文件就包含了我們之后分析Crash的信息了。

2.2 Dmp文件分析

從Dmp文件中提取有效信息,我們就需要重新回到Breakpad目錄,進(jìn)行Processor的編譯。

請確保Linux或者M(jìn)ac 的依賴包都已安裝。成功編譯之后,我們需要用到兩個文件,一是minidump_stackwalk(src/processor/minidump_stackwalk),另一個是dump_sys(src/tools/linux/dump_sysm/dump_syms)。我們將這兩個文件拷貝到工作目錄備用。

同時,我們將$AndroidProject/build/intermediates/ndkBuild/debug/obj/local/armeabi/libtest_google_breakpad.so 和上一步生成的dmp 文件也拷貝到工作目錄。

接下來我們就開始進(jìn)行dmp文件的分析。

?生成Symbol文件

我們首先采用dump_syms生成了libtest_google_breakpad.so的symbol,然后讀取了symbol文件的第一行,需要的信息是這一串十六進(jìn)制的version code。接下來我們將Symbol文件按照固定路徑進(jìn)行放置:

至此,Symbol文件就準(zhǔn)備完畢。

?分析Symbol

然后我們打開result.txt,就可以發(fā)現(xiàn)有如下類似結(jié)果:

這樣,一個清晰的Crash Stack Trace就出現(xiàn)了。

3.如何提示用戶發(fā)送錯誤信息

我們在前面已經(jīng)成功的捕獲了Java異常和Native異常,那么接下來要做的就是順理成章的處理,只要提示用戶將我們需要的Java棧和Native Dmp文件發(fā)送給我們,我們就可以進(jìn)行問題排查和修復(fù)了。

由于篇幅原因,這里就給出一個思路,我們可以在Java Excpetion 和 Native DumpCallback的時候,新起一個進(jìn)程,在這個進(jìn)程里面進(jìn)行日志上傳,甚至可以彈出一個Activity告知用戶,提升用戶體驗。如何在Native DumpCallback新開進(jìn)程并彈出Activity,這又是一個較大的話題,我們以后有機(jī)會再一起探討。:-D

總結(jié)

當(dāng)應(yīng)用上線之后,脫離了Logcat提供的便利,開發(fā)者應(yīng)該如何獲取錯誤信息進(jìn)行異常修復(fù),本文提供了一些隨手可行的思路,包含了Java層和Native層不同的處理辦法,通過實例代碼步步深入,希望帶給大家一起啟發(fā),同時解決生產(chǎn)上的一些實際問題。

本文作者:夏欣(點融黑幫),就職于點融大前端,Android程序員一枚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容