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

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

       這個模塊的來源於手遊項目正式上線,但是很多玩家反饋閃退,但是我們只能聽到反饋閃退,卻不能找到原因,只能憑腦袋去猜,是不是內存不夠,機器配置太差,然後去儘可能優化性能,於是老大開始喊我們需要一個Crash Report, 於是就花2個星期完善了一個可以正式使用的Crash Repoter,項目基於Unity3D,在Android和Ios上做crash report 對我還是第一次,所以還是抱有了極大的興趣。

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是一模一樣的,不用多說。

    對於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,其實也提供了一個函數NSSetUncaughtExceptionHandler ,用來當那些未捕捉的異常發生時,進入這個處理,可以攔截一些東西,但是一些比如內存訪問錯誤直接就退出了,不會進到這裏,另外進到這裏之後程序還是會退出,只是讓我們可以記錄一下,不過有了unity自帶的crashreport 這個也就沒啥用了。

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


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章