一、ANR日志生成過程
以Input ANR為例來分析下anr日志的生成過程:

input觸發(fā)anr之后會通過InputManagerService執(zhí)行notifyANR,最終交由ActivityManagerService來處理,ActivityManagerService執(zhí)行appNotResponding是ANR處理的核心位置,通過AppErrors,最終在ActivityManagerService分別干了三件事:
- 寫trace到/data/anr/traces.txt。
- 寫trace和cpu usage信息到/data/system/dropbox/,然后發(fā)送響應廣播。
- 發(fā)送ANR廣播,執(zhí)行ANR彈窗。
詳細四大組件+Input觸發(fā)ANR流程參考之前文章:Android ANR(二)-觸發(fā)原理
二、ANR日志收集方式
2.1 低版本:FileObserver+ProcessErrorStateInfo
ProcessErrorStateInfo 獲取cause reason
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> processErrorList = am.getProcessesInErrorState();
if (processErrorList != null) {
for (ActivityManager.ProcessErrorStateInfo errorStateInfo : processErrorList) {
if (errorStateInfo.pid == pid && errorStateInfo.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
//這兩部分構成 cause reason
Log.d("xcrashtest", "shortMsg: " + errorStateInfo.shortMsg);
Log.d("xcrashtest", "longMsg: " + errorStateInfo.longMsg);
return true;
}
}
這是AMS對外暴露的api,從AMS的mLruProcesses中過濾出crash和anr異常的進程,返回對應的錯誤信息,詳細邏輯如下:
ActivityManagerService.java
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
enforceNotIsolatedCaller("getProcessesInErrorState");
// assume our apps are happy - lazy create the list
List<ActivityManager.ProcessErrorStateInfo> errList = null;
final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
int userId = UserHandle.getUserId(Binder.getCallingUid());
synchronized (this) {
// iterate across all processes
for (int i=mLruProcesses.size()-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!allUsers && app.userId != userId) {
continue;
}
if ((app.thread != null) && (app.crashing || app.notResponding)) {
// This one's in trouble, so we'll generate a report for it
// crashes are higher priority (in case there's a crash *and* an anr)
ActivityManager.ProcessErrorStateInfo report = null;
if (app.crashing) {
report = app.crashingReport;
} else if (app.notResponding) {
report = app.notRespondingReport;
}
if (report != null) {
if (errList == null) {
errList = new ArrayList<ActivityManager.ProcessErrorStateInfo>(1);
}
errList.add(report);
} else {
Slog.w(TAG, "Missing app error report, app = " + app.processName +
" crashing = " + app.crashing +
" notResponding = " + app.notResponding);
}
}
}
}
return errList;
}
這里如果是anr,report = app.notRespondingReport,notRespondingReport初始化的地方在AppErrors.appNotResponding中調(diào)用的makeAppNotRespondingLocked。
FileObserver來監(jiān)聽/data/anr/目錄下對應的trace文件,來讀取相關的trace信息。
FileObserver的使用:
fileObserver = new FileObserver("/data/anr/", CLOSE_WRITE) {
public void onEvent(int event, String path) {
//監(jiān)聽回調(diào)處理anr
}
}
};
fileObserver.startWatching();//啟動監(jiān)聽
fileObserver.stopWatching();//停止監(jiān)聽
FileObserver的startWatching是交給內(nèi)部的ObserverThread來處理的,最終執(zhí)行的startWatching是個native方法:
static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
{
int res = -1;
#if defined(__linux__)
if (fd >= 0)
{
const char* path = env->GetStringUTFChars(pathString, NULL);
//fd :inotify_init的返回值
//path:要監(jiān)控的文件路徑
//mask:監(jiān)聽文件的哪些事件
//res: 表示對那個文件的監(jiān)視
res = inotify_add_watch(fd, path, mask);
env->ReleaseStringUTFChars(pathString, path);
}
#endif
return res;
}
inotify是文件系統(tǒng)變化通知機制,在監(jiān)聽到文件系統(tǒng)變化后,會向相應的應用程序發(fā)送事件。
該方案因為高版本文件權限的問題,目前只支持<=21的版本。
SELinux(或SEAndroid)將app劃分為主要三種類型(根據(jù)user不同,也有其他的domain類型):
- untrusted_app:第三方app,沒有android平臺簽名,沒有system權限
- platform_app:有android平臺簽名,沒有system權限
- system_app:有android平臺簽名和system權限
2.2 高版本:native 注冊 SIGNAL_QUIT 信號,ANR發(fā)生時接收回調(diào)去收集ANR信息
高版本能使用的原因是捕獲SIGNAL_QUIT只能基于ART。
接收回調(diào)之后,art dump出trace信息,具體anr日志抓取可以參考xcrash的源碼: xc_trace.c xc_trace_dumper線程。
對ANR 日志的獲取主流方案就是如上兩種,Xcrash 和Bugly都是使用的這兩種方式。
2.3 ANR-WatchDog
ANR-WatchDog是仿Android WatchDog機制起個單獨線程向主線程發(fā)送一個變量+1操作,自我休眠自定義ANR的閾值,休眠過后判斷變量是否+1完成,如果未完成則告警。有個明顯問題是,發(fā)送+1 消息的時機可能錯過ANR現(xiàn)場,從而抓不到現(xiàn)場堆棧信息。
這方案只是提供了一個新思路,并不能穩(wěn)定獲取到anr線程堆棧信息。