http://bbs.9ria.com/thread-200126-1-1.html
通過崩潰捕獲和收集,可以收集到已發佈應用(遊戲)的異常,以便開發人員發現和修改bug,對於提高軟件質量有着極大的幫助。本文介紹了iOS和android平臺下崩潰捕獲和收集的原理及步驟,不過如果是個人開發應用或者沒有特殊限制的話,就不用往下看了,直接把友盟sdk(一個統計分析sdk)加入到工程中就萬事大吉了,其中的錯誤日誌功能完全能夠滿足需求,而且不需要額外準備接收服務器。 但是如果你對其原理更感興趣,或者像我一樣必須要兼容公司現有的bug收集系統,那麼下面的東西就值得一看了。
要實現崩潰捕獲和收集的困難主要有這麼幾個:
1、如何捕獲崩潰(比如c++常見的野指針錯誤或是內存讀寫越界,當發生這些情況時程序不是異常退出了嗎,我們如何捕獲它呢)
2、如何獲取堆棧信息(告訴我們崩潰是哪個函數,甚至是第幾行發生的,這樣我們纔可能重現並修改問題)
3、將錯誤日誌上傳到指定服務器(這個最好辦)
我們先進行一個簡單的綜述。會引發崩潰的代碼本質上就兩類,一個是c++語言層面的錯誤,比如野指針,除零,內存訪問異常等等;另一類是未捕獲異常(Uncaught Exception),iOS下面最常見的就是objective-c的NSException(通過@throw拋出,比如,NSArray訪問元素越界),android下面就是java拋出的異常了。這些異常如果沒有在最上層try住,那麼程序就崩潰了。 無論是iOS還是android系統,其底層都是unix或者是類unix系統,對於第一類語言層面的錯誤,可以通過信號機制來捕獲(signal或者是sigaction,不要跟qt的信號插槽弄混了),即任何系統錯誤都會拋出一個錯誤信號,我們可以通過設定一個回調函數,然後在回調函數裏面打印併發送錯誤日誌。
一、iOS平臺的崩潰捕獲和收集
1、設置開啓崩潰捕獲
[cpp]view plaincopyprint?
在遊戲的最開始調用InitCrashReport()函數來開啓崩潰捕獲。 註釋1處對應上文所說的第一類崩潰,註釋2處對應objective-c(或者說是UIKit Framework)拋出但是沒有被處理的異常。
2、打印堆棧信息
[cpp]view plaincopyprint?
幸好,蘋果的iOS系統支持backtrace,通過這個函數可以直接打印出程序崩潰的調用堆棧。優點是,什麼符號函數表都不需要,也不需要保存發佈出去的對應版本,直接查看崩潰堆棧。缺點是,不能打印出具體哪一行崩潰,很多問題知道了是哪個函數崩的,但是還是查不出是因爲什麼崩的
3、日誌上傳,這個需要看實際需求,比如我們公司就是把崩潰信息http post到一個php服務器。這裏就不多做聲明瞭。
4、技巧---崩潰後程序保持運行狀態而不退出
[cpp]view plaincopyprint?
在崩潰處理函數上傳完日誌信息後,調用上述代碼,可以重新構建程序主循環。這樣,程序即便崩潰了,依然可以正常運行(當然,這個時候是處於不穩定狀態,但是由於手持遊戲和應用大多是短期操作,不會有掛機這種說法,所以穩定與否就無關緊要了)。玩家甚至感受不到崩潰。
這裏要在說明一個感念,那就是“可重入(reentrant)”。簡單來說,當我們的崩潰回調函數是可重入的時候,那麼再次發生崩潰的時候,依然可以正常運行這個新的函數;但是如果是不可重入的,則無法運行(這個時候就徹底死了)。要實現上面描述的效果,並且還要保證回調函數是可重入的幾乎不可能。所以,我測試的結果是,objective-c的異常觸發多少次都可以正常運行。但是如果多次觸發錯誤信號,那麼程序就會卡死。 所以要慎重決定是否要應用這個技巧。
二、android崩潰捕獲和收集
1、android開啓崩潰捕獲
首先是java代碼的崩潰捕獲,這個可以仿照最下面的完整代碼寫一個UncaughtExceptionHandler,然後在所有的Activity的onCreate函數最開始調用
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this));
這樣,當發生崩潰的時候,就會自動調用UncaughtExceptionHandler的public void uncaughtException(Thread thread, Throwable exception)函數,其中的exception包含堆棧信息,我們可以在這個函數裏面打印我們需要的信息,並且上傳錯誤日誌
然後是重中之重,jni的c++代碼如何進行崩潰捕獲。
[cpp]view plaincopyprint?
2、打印堆棧
java語法可以直接通過exception獲取到堆棧信息,但是jni代碼不支持backtrace,那麼我們如何獲取堆棧信息呢? 這裏有個我想嘗試的新方法,就是使用google breakpad,貌似它現在完整的跨平臺了(支持windows, mac, linux, iOS和android等),它自己實現了一套minidump,在android上面限制會小很多。 但是這個庫有些大,估計要加到我們的工程中不是一件非常容易的事,所以我們還是使用了簡潔的“傳統”方案。 思路是,當發生崩潰的時候,在回調函數裏面調用一個我們在Activity寫好的靜態函數。在這個函數裏面通過執行命令獲取logcat的輸出信息(輸出信息裏面包含了jni的崩潰地址),然後上傳這個崩潰信息。 當我們獲取到崩潰信息後,可以通過arm-linux-androideabi-addr2line(具體可能不是這個名字,在android ndk裏面搜索*addr2line,找到實際的程序)解析崩潰信息。
jni的崩潰回調函數如下:
[cpp]view plaincopyprint?
java對應的函數實現如下:
[java]view plaincopyprint?
我們開啓了一個新的activity,因爲當jni發生崩潰的時候,原始的activity可能已經結束掉了。 這個新的activity實現如下:
[java]view plaincopyprint?
最主要的地方是doInBackground函數,這個函數通過logcat獲取了崩潰信息。 不要忘記在AndroidManifest.xml添加讀取LOG的權限
[html]view plaincopyprint?
3、獲取到錯誤日誌後,就可以寫到sd卡(同樣不要忘記添加權限),或者是上傳。 代碼很容易google到,不多說了。 最後再說下如何解析這個錯誤日誌。
我們在獲取到的錯誤日誌中,可以截取到如下信息:
[plain]view plaincopyprint?
[plain]view plaincopyprint?
這個就是我們崩潰函數的地址, libhelloworld.so就是崩潰的動態庫。我們要使用addr2line對這個動態庫進行解析(注意要是obj/local目錄下的那個比較大的,含有符號文件的動態庫,不是Libs目錄下比較小的,同時發佈版本時,這個動態庫也要保存好,之後查log都要有對應的動態庫)。命令如下:
arm-linux-androideabi-addr2line.exe -e 動態庫名稱 崩潰地址
例如:
[plain]view plaincopyprint?
得到的結果就是哪個cpp文件第幾行崩潰。 如果動態庫信息不對,返回的就是 ?:0
原文來自:http://www.cnblogs.com/lancidie/archive/2013/04/13/3019349.html