Android和Ios的crash reporter(崩潰報告採集與上傳)

Crash Report,這在大型軟件開發領域是很常見的功能,就是可以當程序崩潰退出後,可以將崩潰時的信息,最好是攜帶dmp文件發送給服務器,這樣開發人員既能夠得到分發出去的客戶端的崩潰率統計,也能夠針對出現的錯誤進行及時的糾正,以前在PC的端遊時代,這是很常見的作法,最近進行了在手游上的關於crash report的相關研究,而且爲項目編寫了一個相對完善的CrashReport模塊。java

       這個模塊的來源於手遊項目正式上線,可是不少玩家反饋閃退,可是咱們只能聽到反饋閃退,卻不能找到緣由,只能憑腦殼去猜,是否是內存不夠,機器配置太差,而後去儘量優化性能,因而老大開始喊咱們須要一個Crash Report, 因而就花2個星期完善了一個能夠正式使用的Crash Repoter,項目基於Unity3D,在Android和Ios上作crash report 對我仍是第一次,因此仍是抱有了極大的興趣。linux

1 Android 平臺。

其實CrashReport也不該該是隻有Crash了才Report,各類錯誤和潛在會致使Crash的問題也應該report上去。對於基於Unity3D的Android應用來講,自底到上能夠分爲三層:C++,Java和C#。 android是基於linux的系統,最底下的各類so a庫就是C++的部分,android系統自己的相關邏輯則是java,U3D則使用了C#開發邏輯,因此咱們採集問題也要從這三塊分別着手。

C#

c#的錯誤很好處理,這層U3D徹底封裝好了,C#層會出現warning error和exception,在android下這幾種狀況都不會致使crash,都會被UNITY3d接住,可是咱們須要知道並報告給服務器,U3D有接口Application.RegisterLogCallback(),可讓C#層發生上面的問題時被我知道,咱們只要寫這個callback,而後在裏面給服務器就好了。

Java

java層的錯誤就是各類java exception,對於java,若是對於咱們catch了的exception,不會致使crash,會按照咱們的catch行爲執行,對於那些咱們沒有catch的exception,是會crash的,還會在adblog上打印出來,咱們須要獲知這些exception,咱們能夠採用java中的接口Thread.setDefaultUncaughtExceptionHandler來從新設置這個對未catch的exeption的處理,在咱們本身的handler中基本作的事情就是首先把這個exceptio報告給服務器,而後並不讓程序退出,讓程序儘量活下去。

C++

C++中出現的問題一般就是很嚴重的了,這裏也分兩種,一種是普通的一些異常,這取決於你是否catch了,若是沒有catch,默認就是abort的,也就是crash了,還有一些好比對內存的非法訪問,就直接在linux中產生了一個結束信號,把進程結束了,也是crash。對於C++咱們前後嘗試了兩種方案,第一種就是採用捕獲linux的信號量,程序異常退出老是有信號的,可使用linux 下的sigaction來設置對這些信號的捕獲處理,好比咱們捕獲了SIGILL SIGABRT SIGFPE SIGSEGV SIGPIPE SIGBUS SIGSTKFLT,這樣對於異常的程序退出咱們是知道的,能夠在下次進入遊戲時告知服務器,可是這樣作有一個明顯的問題就是咱們只是知道程序crash了,可是沒有trace back,不知道在哪掛了,咱們想要dump文件。因而後來採起的方法就是使用了google的breakpad框架,關於google breakpad,這是它的主頁,https://chromium.googlesource.com/breakpad/breakpad/,關於他的基本原理,你們能夠去看他的wiki和文檔,很長,基原本說它是一個平臺無關的C++的crash reporter,能夠在crash後,生成dmp文件,而後利用它的一些工具獲取堆棧的符號信息。
google breakpad在android的簡單集成方法以下:
1.從http://google-breakpad.googlecode.com/svn/trunk拿到源碼
2.創建你本身的jni工程
3.將google breakpad的android 和src兩個文件夾放到你的工程裏
4.配置你的Application.mk,裏面要加入
APP_STL := stlport_static
APP_CPPFLAGS := -std=gnu++11 -D__STDC_LIMIT_MACROS 
5.配置你的Android.mk,裏面要加入如下的src文件
    google_breakpad/src/client/linux/crash_generation/crash_generation_client.cc \
    google_breakpad/src/client/linux/handler/exception_handler.cc \
    google_breakpad/src/client/linux/handler/minidump_descriptor.cc \
    google_breakpad/src/client/linux/log/log.cc \
    google_breakpad/src/client/linux/dump_writer_common/thread_info.cc \
 google_breakpad/src/client/linux/dump_writer_common/ucontext_reader.cc \
google_breakpad/src/client/linux/microdump_writer/microdump_writer.cc \
    google_breakpad/src/client/linux/minidump_writer/linux_dumper.cc \
    google_breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
    google_breakpad/src/client/linux/minidump_writer/minidump_writer.cc \
    google_breakpad/src/client/minidump_file_writer.cc \
    google_breakpad/src/common/android/breakpad_getcontext.S \
    google_breakpad/src/common/convert_UTF.c \
    google_breakpad/src/common/md5.cc \
    google_breakpad/src/common/string_conversion.cc \
    google_breakpad/src/common/linux/elfutils.cc \
    google_breakpad/src/common/linux/file_id.cc \
    google_breakpad/src/common/linux/guid_creator.cc \
    google_breakpad/src/common/linux/linux_libc_support.cc \
    google_breakpad/src/common/linux/memory_mapped_file.cc \
   google_breakpad/src/common/linux/safe_readlink.cc \

還要加入LOCAL_STATIC_LIBRARIES += breakpad_client
以及include google_breakpad/android/google_breakpad/Android.mk
6.在你的crashreport模塊初始化中(固然一般也能夠在JNI_OnLoad中)初始化google breakpad,
 google_breakpad::MinidumpDescriptor descriptor(path);  
   handler = new google_breakpad::ExceptionHandler(descriptor,  NULL,   NULL,    NULL,   true,    -1);  
這裏的path是你手機上存放dmp文件的文件夾,crash發生後,它會在這個文件夾內生成以UUID命名的dmp文件,固然前提你要保證這個文件夾真實存在。
7.最後編出你的so庫,給程序使用。
這樣咱們就經過google breakpad實現了C++層的dump文件生成,當發生後,咱們把dump文件傳給服務器就好了。
最後關於這個dmp文件的解讀,這裏你們能夠參考這份文檔:https://www.chromium.org/developers/decoding-crash-dumps。這個解析要在linux環境下作,沒有win的工具,基本就是兩步驟,第一步是用工具將dmp的二進制文件轉成能夠看懂的文本格式,能夠看到出錯的地址,可是若是要想詳細知道這些地址所表明的符號,還須要用裏面的一個工具以及帶有符號版本的so庫才能知道,unity本身的庫應該沒有這種so庫,可是也能大體看出問題處在哪了。

其餘

彷佛到這裏咱們可以堵到android上全部可能崩潰的地方,然而並非。至少有兩種狀況是還不行的,一是watch dog超時,二是內存資源不足。
watch dog超時:當你的主線程超過一段時間沒有相應,android系統會將你的程序退出,內存資源不足:當android系統認爲這個程序使用的內存太高時,會選擇將這個退出,以釋放內存。
這兩種狀況都是android系統的管理器按照必定的策略調度的,雖然玩家看到閃退了,可是這兩種狀況邏輯上都不屬於異常退出,和你本身退出android進程是同樣的,只不過是系統幫你退出了,因此用google breakpad或者信號都不能知道,由於這實際上是正常退出,可是對於咱們程序設計來講,這是異常。因此從捕獲異常退出來講沒有辦法(固然也許真的有,我不知道,那歡迎你們批評指正),因此對這兩種狀況咱們退而求其次使用當感知到有潛在的退出危險時報告給服務器警告的策略。
對於watch dog 超時,咱們在java層開一個新的線程,不斷的去探測主線程,當較長時間發現主線程沒回應,咱們給一個警告給服務器,並帶上如今的內存狀況,告知服務器這臺機器主線程卡住好久了,極可能一會就被系統退出了,可是也可能運氣好一會又好了。這種探測的方法咱們能夠正好用unity的在native層的UnitySendMessage機制,由於這個就是異步的,咱們用另外一個線程不斷的給主線程的Unity用這個發送心跳包,unity收到後回覆,好久沒回復就是主線程卡住了(緣由多了,好比某個邏輯特別特別耗時。。。)
對於內存過高,咱們會在程序裏按期檢測一下內存,當發現使用的內存明顯高過咱們的設計時發給服務器,好比說咱們認爲PSS內存超過600M,固然若是有機器每到這個就崩了,那也不是咱們的目標機型。在android系統下動態獲得系統的總內和當前可用內存能夠用activityManager.getMemoryInfo(),獲取當前的進程的使用內存的接口能夠用activityManager.getProcessMemoryInfo(new int[]{Process.myPid()})
經過這兩種策略咱們能夠預防式的獲得這兩種狀況的一個crash統計。

IOS

    IOS只有C++和C#兩層,對於C#來講,和Android是如出一轍的,不用多說。android

    對於C++這層,咱們固然仍是能夠繼續使用google breakpad,由於它是跨平臺的,可是其實Unity(至少從4.6開始)爲ios已經提供了一個crash report模塊,他須要咱們將工程生成好後,將Crashreport.h裏面的ENABLE_CUSTOM_CRASH_REPORTER設置爲1,或者你能夠直接在unity安裝路徑下找到這個文件直接改。這樣在c++ crash後,unity會爲咱們生存crash文件,等下次啓動後,能夠經過crashreport這個模塊訪問這些dmp文件,這些dmp文件都是ios上的標準dmp文件,可使用ios的開發工具symbolicatecrash來查看。unity內置的crash report其實也是採用了第三方的庫plcrashreport來實現的,這個庫在ios上的應用不少。ios

  另外對於ios,其實也提供了一個函數NSSetUncaughtExceptionHandler ,用來當那些未捕捉的異常發生時,進入這個處理,能夠攔截一些東西,可是一些好比內存訪問錯誤直接就退出了,不會進到這裏,另外進到這裏以後程序仍是會退出,只是讓咱們能夠記錄一下,不過有了unity自帶的crashreport 這個也就沒啥用了。c++

  還有在unity對ios的c#運行中,在player setting裏面有個對代碼的運行優化,選擇slow but safe 仍是fast but no exception,若是選擇前者,全部c#的執行錯誤都會像腳本同樣被catch住,會報c#的exception,若是後者就不會,就直接不catch了,會形成程序退出,可是運行效率是高的,咱們一般選擇slow but safe。這是mono架構的特性。c#


經過對ios 和android的崩潰的採集和報告,有助於瞭解咱們程序在用戶手中的穩定性和及時改進。服務器

相關文章
相關標籤/搜索