做為程序員,最不愿意看到的就是自己寫的程序崩潰,特別是遇到?jīng)]有錯(cuò)誤信息的崩潰的時(shí)候滑潘,往往程序員自己也就隨之一起崩潰了粟耻。如何捕獲Android程序產(chǎn)生異常時(shí)的崩潰信息查近,本文提供了隨手可行的方法,希望能夠帶來一些啟發(fā)挤忙,同時(shí)解決一些生產(chǎn)上的難題嗦嗡。
背景
在應(yīng)用從開發(fā)慢慢過渡到線上部署的時(shí)候,開發(fā)者就逐漸切換到依靠程序日志/運(yùn)行狀態(tài)來進(jìn)行問題的排查和解決饭玲。通常一些第三方廠商會(huì)提供這些服務(wù)侥祭,譬如騰訊的bugly,fabric(以前叫crashlytics)茄厘,Umeng等矮冬。 前兩者的異常收集和處理較好,然而有一個(gè)通用的問題就是需要將symbol文件上傳到廠商服務(wù)器次哈,顯然對于注重應(yīng)用安全和IP保護(hù)的開發(fā)者胎署,這個(gè)行為是值得著重權(quán)衡的。而Umeng則注重于運(yùn)營數(shù)據(jù)窑滞,對于異常捕獲沒有特別大的優(yōu)勢琼牧。所以本文的關(guān)注點(diǎn)就是如何在Android項(xiàng)目內(nèi)部實(shí)現(xiàn)自己異常捕獲和分析模塊恢筝。
主要會(huì)包括以下知識點(diǎn)(劃重點(diǎn))
1. 如何捕獲Java異常
2. 如何捕獲Native異常及分析
3. 如何提示用戶發(fā)送錯(cuò)誤信息
1. 如何捕獲Java 異常
這個(gè)問題乍一聽感覺沒有任何難度,這不是用以下的終極處理Crash大法就解決了嗎?
且不說這樣做的對錯(cuò)巨坊,只是并不能很好的解決我們的問題撬槽,問題就在于我們對于會(huì)發(fā)生異常的地方?jīng)]有辦法完全預(yù)知,對于CheckedException 編譯器會(huì)提示我們處理趾撵,然而對于RuntimeException侄柔,總是難以預(yù)防。NPE(Null Pointer Exception)常年占據(jù)Java Eeception的Top 1 不是沒有道理占调。
所以我們的期望是什么暂题,就算我們沒有指定Catch Exception,在程序發(fā)生異常的時(shí)候究珊,也能通知到我們薪者。幸運(yùn)的是,正好就有這樣的方法剿涮。
看一下這個(gè)方法的說明啸胧,一下就明白了
至此,一切都清楚了幔虏,我們要做的就是設(shè)置一個(gè)自定義的UncaughtExceptionHandler纺念,并且設(shè)置為Thread的Default值,這樣在異常發(fā)生時(shí)想括,就可以進(jìn)入到我們的處理流程當(dāng)中陷谱,簡要代碼如下:
2. 如何捕獲Native異常
與Java層的異常不同,Native層的異常捕獲要稍微復(fù)雜一些瑟蜈。難點(diǎn)主要在于當(dāng)Native 層發(fā)生異常時(shí)烟逊,JVM在收到異常信號后會(huì)直接Shutdown,所以我們的Thread.UncaughtExceptionHandler 不會(huì)被執(zhí)行铺根。所以我們必須捕獲Linux的異常信號宪躯,并執(zhí)行我們自己的信號處理函數(shù)。例如如下展示:
在此基礎(chǔ)上位迂,我們還必須自己來實(shí)現(xiàn)Crash Dump生成和解析访雪,著實(shí)不太友好。所以在這里我們引用了一個(gè)三方庫掂林,來幫我們處理臣缀。
Google-Breakpad項(xiàng)目地址
下面我們就來看一下如何集成Breakpad來完成我們需要的異常捕獲。
從上面的Breakpad的結(jié)構(gòu)圖可以看出泻帮,我們需要的是兩塊東西精置,一是Client模塊,這將被集成到App中锣杂,用作Crash的信號捕捉和dump文件生成脂倦;另外一個(gè)就是Process模塊番宁,當(dāng)收集到用戶dump文件時(shí),結(jié)合symbol進(jìn)行crash棧分析赖阻。
2.1 Client模塊集成
通過depot_tools 下載好整個(gè)Breakpad源碼之后蝶押,我們就可以進(jìn)行client module的編譯。通常來說有兩種編譯的方式政供,一是集成到項(xiàng)目中播聪,利用NDK-Build 來進(jìn)行整個(gè)源碼的編譯和集成朽基;第二個(gè)就是使用NDK toolchain來進(jìn)行單獨(dú)編譯布隔,然后將編譯出來的靜態(tài)鏈接庫導(dǎo)入到項(xiàng)目中。兩者沒有很大的區(qū)別稼虎,主要區(qū)別在于前者Breakpad的src代碼會(huì)被集成到項(xiàng)目里衅檀,而后者則是單獨(dú)管理。之前的項(xiàng)目采用的是第二種方法霎俩,導(dǎo)入靜態(tài)鏈接庫哀军。這樣的好處就是項(xiàng)目中jni文件夾里沒有過多跟項(xiàng)目業(yè)務(wù)無關(guān)的代碼,然而有一個(gè)問題沒法避免打却,需要用到的頭文件也需要加到項(xiàng)目中去杉适,這樣也需要一個(gè)手動(dòng)的維護(hù)。再加上Android Studio 2.2 以后對NDK的支持柳击,所以本文就采用集成源碼到項(xiàng)目中來進(jìn)行一個(gè)展示猿推。
?首先是創(chuàng)建jni文件夾:右鍵項(xiàng)目 -> new -> folder -> jni folder
?接著是拷貝breakpad/src 到j(luò)ni folder下,并刪除一些Client不需要的folder捌肴,包括build蹬叭,processor,testing状知,tools等秽五,清理過后的目錄結(jié)構(gòu)應(yīng)該跟以下類似:
?接著是配置Gradle:
這里的Android.mk 如果項(xiàng)目之前沒有的話,那么可以直接拷貝Breakpad 下src\android\sample_app\jni下面的3個(gè)文件到項(xiàng)目中的jni目錄下饥悴,然后根據(jù)之前拷貝的Breakpad目錄結(jié)構(gòu)做如下改動(dòng):
可以看到坦喘,在test_breakpad.cpp中,我們定義了一個(gè)jni 方法setHandler()西设,主要干了幾件事起宽,設(shè)置Dmp文件的生成位置,注冊自己的DumpCallback济榨,并且緊跟著模擬了一個(gè)Crash坯沪。
?最后我們選擇MainActivity 或者Application 中去加載我們的Library并調(diào)用jni方法。
別忘記加上寫SDCard的權(quán)限!
至此擒滑,我們的測試App 就已經(jīng)完成腐晾,運(yùn)行起來之后就可以在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 的依賴包都已安裝。成功編譯之后巨柒,我們需要用到兩個(gè)文件樱拴,一是minidump_stackwalk(src/processor/minidump_stackwalk),另一個(gè)是dump_sys(src/tools/linux/dump_sysm/dump_syms)洋满。我們將這兩個(gè)文件拷貝到工作目錄備用晶乔。
同時(shí),我們將$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é)果:
這樣,一個(gè)清晰的Crash Stack Trace就出現(xiàn)了柠硕。
3.如何提示用戶發(fā)送錯(cuò)誤信息
我們在前面已經(jīng)成功的捕獲了Java異常和Native異常工禾,那么接下來要做的就是順理成章的處理,只要提示用戶將我們需要的Java棧和Native Dmp文件發(fā)送給我們仅叫,我們就可以進(jìn)行問題排查和修復(fù)了帜篇。
由于篇幅原因,這里就給出一個(gè)思路诫咱,我們可以在Java Excpetion 和 Native DumpCallback的時(shí)候笙隙,新起一個(gè)進(jìn)程,在這個(gè)進(jìn)程里面進(jìn)行日志上傳坎缭,甚至可以彈出一個(gè)Activity告知用戶竟痰,提升用戶體驗(yàn)。如何在Native DumpCallback新開進(jìn)程并彈出Activity掏呼,這又是一個(gè)較大的話題坏快,我們以后有機(jī)會(huì)再一起探討。:-D
總結(jié)
當(dāng)應(yīng)用上線之后憎夷,脫離了Logcat提供的便利莽鸿,開發(fā)者應(yīng)該如何獲取錯(cuò)誤信息進(jìn)行異常修復(fù),本文提供了一些隨手可行的思路,包含了Java層和Native層不同的處理辦法祥得,通過實(shí)例代碼步步深入兔沃,希望帶給大家一起啟發(fā),同時(shí)解決生產(chǎn)上的一些實(shí)際問題级及。
本文作者:夏欣(點(diǎn)融黑幫)乒疏,就職于點(diǎn)融大前端,Android程序員一枚饮焦。