目前大多數(shù)app都是使用三方庫(例如友盟)實現(xiàn)崩潰日志收集酵幕, 但不一定了解是如何實現(xiàn)的扰藕。 今天工作不忙, 剛好有時間思考一下這個問題芳撒。
我們知道Android進程在閃退或崩潰時实胸, logcat里會輸出一片紅色的崩潰日志他嫡, 包括Shutting down vm和堆棧信息。 PS: Android基礎(chǔ)知識點:app和linux進程是什么關(guān)系庐完? 答:每個android進程可以理解為是一個linux進程钢属。
下面說說我理解的做日志采集的套路:
1、 自定義Application類门躯, 一般在這個類里初始化三方庫淆党。
<pre>
<application
android:name=".TheApplication"
android:icon="${icon}"
android:label="${str}"
android:largeHeap="true"
tools:replace="android:label"></pre>
2、 保存默認異常處理Handler的引用(Java虛虛擬機的異常日志都會執(zhí)行回調(diào)函數(shù)uncaughtException)讶凉, 注意handler是運行在主線程里染乌! (PS: 為什么不是子線程? 我的理解是子線程默認沒有l(wèi)ooper懂讯, 而創(chuàng)建android進程時AndroidNative.java里會創(chuàng)建一個looper荷憋。)
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
3、 將uncaughtException函數(shù)的參數(shù)轉(zhuǎn)換為字符串褐望, 即異常日志堆棧信息勒庄。
4、 為了更好的分析問題瘫里,除了異常堆棧外实蔽,可能還需要app版本號、用戶信息(例如用戶名或手機號)谨读、手機信息(如機型局装、版本等)、當前進程的線程信息等等劳殖, 將這些信息保存到數(shù)據(jù)庫表里(可以用原生的sqlite或三方庫如Realm铐尚、LitePal、OrmLite哆姻、GreenDao等等)宣增, 寫到數(shù)據(jù)庫的目的在于能夠多條批量上傳,更重要的是避免進程崩潰后日志丟失或者不知道是否已上傳的狀態(tài)填具。 PS:當然還可以在捕獲到異常時保存其它你關(guān)心的數(shù)據(jù)!
5匆骗、 上傳日志可以單獨啟動個遠程服務(wù)(運行在:remote進程劳景,目的是避免占用UI進程資源), 使用觀察者模式監(jiān)聽數(shù)據(jù)庫變化或監(jiān)聽當前時間和上次上傳日志的時間間隔碉就, 當日志記錄條數(shù)超出閾值或者超出間隔周期時盟广, 將日志打包成gzip或其它壓縮格式并傳送到服務(wù)器, 服務(wù)器存儲結(jié)果并返回成功時瓮钥, 客戶端刪除對應(yīng)的日志記錄筋量。
PS: HTTP交互是在子線程執(zhí)行的烹吵, 可以借助三方庫例如OkHttp實現(xiàn)。
思路示例代碼(省略了存數(shù)據(jù)和打包上傳的代碼):
<pre>
public class TheApplication extends Application {
Thread.UncaughtExceptionHandler mDefaultHandler;
@Override
public void onCreate() {
super.onCreate();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //第一步桨武,獲取默認handler
//替換handler, 這是在主線程里執(zhí)行
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
e.printStackTrace(printWriter);
Throwable cause = e.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String result = writer.toString(); //這就是異常日志堆棧信息
/**
第三步肋拔, 存儲app版本號、用戶信息(例如uid或手機號)呀酸、手機信息(例如機型凉蜂、版本號)、
當前進程的線程信息和result(即異常堆棧到一個數(shù)據(jù)庫表里(狀態(tài)位表示未上傳性誉,已上傳時刪除該記錄)
*/
/**
* 第四步窿吩, 上傳異常日志的服務(wù)器。 進程里應(yīng)該有個服務(wù)错览,監(jiān)聽著數(shù)據(jù)庫異常日志表(觀察者模式)或啟動服務(wù)
* 時判斷記錄條數(shù)是否達到閾值纫雁; 超過閾值時, 將多條記錄打包壓縮成gzip或其它格式上傳到服務(wù)器倾哺, 服務(wù)器
* 存儲數(shù)據(jù)后返回成功轧邪, 客戶端刪除本地日志表的對應(yīng)記錄。
*/
mDefaultHandler.uncaughtException(t, e);
// Java的默認異常處理悼粮。 如果是NullPointerException闲勺, 注釋掉這行app會無響應(yīng),因為"AndroidRuntime: Shutting down VM"
}
});</pre>