Android發(fā)生ANR后的信息采集過程
發(fā)生ANR后
系統(tǒng)會為我們提供一些信息,便于我們分析問題,如生成trace文件,在log中打印CPU信息等。
這篇文章,我們來看看ANR發(fā)生之后,系統(tǒng)會提供給我們哪些信息,以及這些信息是如何采集和輸出的。
系統(tǒng)提供的信息
系統(tǒng)提供給我們的信息,主要有:
EventLog中會打印"am_anr"的日志MainLog中會打印ANR發(fā)生的進(jìn)程、原因、CPU負(fù)載等信息/data/anr路徑下會生成一個trace文件,打印出主要進(jìn)程的堆棧信息dropbox會保存trace文件和CPU負(fù)載信息data/system/dropbox目錄
采集信息源碼
發(fā)生 ANR 后,不管是哪種類型的 ANR,系統(tǒng)都會調(diào)用到appNotResponding方法中,進(jìn)行信息的采集工作。
這個方法所在的位置,在不同的Android版本有區(qū)別。舊版本是在AMS中,新版本是在ProcessErrorStateRecord類中。
我們以新版本的appNotResponding源碼為例,分步進(jìn)行講解。
appNotResponding傳入?yún)?shù)
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
}其中annotation就是shortMsg,表示ANR發(fā)生的原因。
- 先獲取一次CPU信息
long anrTime = SystemClock.uptimeMillis();
if (isMonitorCpuUsage()) {
mService.updateCpuStatsNow();
}- 設(shè)置annotation
// Store annotation here as instance above will not be hit on all paths.
setAnrAnnotation(annotation);- 判斷是否需要跳過
// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
if (mService.mAtmInternal.isShuttingDown()) {
Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
return;
} else if (isNotResponding()) {
Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
return;
} else if (isCrashing()) {
Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
return;
} else if (mApp.isKilledByAm()) {
Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
return;
} else if (mApp.isKilled()) {
Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
return;
}有幾種情況會跳過:
- 如果正在關(guān)機(jī)中
- 如果已經(jīng)被標(biāo)記為
notResponding,正在處理 ANR 中 - 進(jìn)程正在 crash 處理中
- 進(jìn)程已經(jīng)被 AMS 殺掉
- 進(jìn)程已經(jīng)被殺
- 設(shè)置notResponding的標(biāo)記
setNotResponding(true);
- 打印am_anr的EventLog
EventLog.writeEvent(EventLogTags.AM_ANR, mApp.userId, pid, mApp.processName,
mApp.info.flags, annotation);- 獲取需要dump的所有進(jìn)程id
// 先把當(dāng)前進(jìn)程添加到firstPids
firstPids.add(pid);
// 如果是silentAnr,則不需要dump其他進(jìn)程,silentAnr主要是后臺anr
isSilentAnr = isSilentAnr();
if (!isSilentAnr && !onlyDumpSelf) {
// 將parentPid加入firstPids
int parentPid = pid;
if (parentProcess != null && parentProcess.getPid() > 0) {
parentPid = parentProcess.getPid();
}
if (parentPid != pid) firstPids.add(parentPid);
// MY_PID是system_server的pid,將system_server加入firstPids
if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
final int ppid = parentPid;
// 所有進(jìn)程,按lru的順序排列
mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
if (r != null && r.getThread() != null) {
int myPid = r.getPid();
if (myPid > 0 && myPid != pid && myPid != ppid && myPid != MY_PID) {
if (r.isPersistent()) {
firstPids.add(myPid); // 將persisitent進(jìn)程加入firstPids
} else if (r.mServices.isTreatedLikeActivity()) {
firstPids.add(myPid); // treatedLikeActivity
} else {
lastPids.put(myPid, Boolean.TRUE); // 其他進(jìn)程加入lastPids
}
}
}
});
}
}加入firstPids的進(jìn)程:
- 當(dāng)前進(jìn)程
- parent 進(jìn)程
- system_server 進(jìn)程
- 生成mainlog
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(mApp.processName);
if (activityShortComponentName != null) {
info.append(" (").append(activityShortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parentShortComponentName != null
&& parentShortComponentName.equals(activityShortComponentName)) {
info.append("Parent: ").append(parentShortComponentName).append("\n");
}
if (errorId != null) {
info.append("ErrorId: ").append(errorId.toString()).append("\n");
}
info.append("Frozen: ").append(mApp.mOptRecord.isFrozen()).append("\n");初始化一個StringBuilder對象info,用來記錄輸出到mainLog里的內(nèi)容。
主要包含如下內(nèi)容:
- ANR in xxx
- PID: xxx
- Reason: xxx (shortMsg)
- Parent: xxx (可能沒有)
- ErrorId: xxx (可能沒有)
- Frozen: 是否frozen
- CPU信息
- 收集需要 dump 的native pids
String[] nativeProcs = null;
if (isSilentAnr || onlyDumpSelf) {
for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
// 寫死在watchdog中的native進(jìn)程,主要包含audioserver、cameraserver等。
if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
nativeProcs = new String[] { mApp.processName };
break;
}
}
} else {
nativeProcs = NATIVE_STACKS_OF_INTEREST;
}
int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
ArrayList<Integer> nativePids = null;
if (pids != null) {
nativePids = new ArrayList<>(pids.length);
for (int i : pids) {
nativePids.add(i);
}
}NATIVE_STACKS_OF_INTEREST寫死在WatchDog.java文件,主要包含audioserver、cameraserver等。
- 開始dump trace文件
// 生成ProcessCpuTracker
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePids, tracesFileException, offsets, annotation, criticalEventLog);dump trace文件的具體細(xì)節(jié),以及SignalCatcher線程監(jiān)聽信號,下一篇文章再詳細(xì)講。
- 再次獲取CPU信息,并打印mainLog
if (isMonitorCpuUsage()) {
mService.updateCpuStatsNow();
mService.mAppProfiler.printCurrentCpuState(report, anrTime);
info.append(processCpuTracker.printCurrentLoad());
info.append(report);
}
info.append(processCpuTracker.printCurrentState(anrTime));
// 打印mainLog,tag為ActivityManager
Slog.e(TAG, info.toString());- 保存到 dropbox
mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
parentShortComponentName, parentPr, null, report.toString(), tracesFile,
null, new Float(loadingProgress), incrementalMetrics, errorId);把traces文件、CPU使用率等信息,保存到dropbox,即data/system/dropbox目錄
- 如果是后臺 ANR,直接殺進(jìn)程
if (isSilentAnr() && !mApp.isDebugging()) {
mApp.killLocked("bg anr", ApplicationExitInfo.REASON_ANR, true);
return;
}- 設(shè)置
App的notRespondingReport(到這里,AMS 才能查詢到進(jìn)程是否發(fā)生 ANR)
synchronized (mProcLock) {
// 設(shè)置AppNotRespondingReport
makeAppNotRespondingLSP(activityShortComponentName,
annotation != null ? "ANR " + annotation : "ANR", info.toString());
mDialogController.setAnrController(anrController);
}private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
setNotResponding(true);
if (mService.mAppErrors != null) {
// 設(shè)置NotRespondingReport
mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
activity, shortMsg, longMsg, null);
}
startAppProblemLSP();
mApp.getWindowProcessController().stopFreezingActivities();
}- 喚醒 ANR 彈窗
if (mService.mUiHandler != null) {
// 喚醒AppNotResponding彈窗
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem);
mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);
}到這里,AppNotResponding的流程就講完了。
從AMS獲取App的ErrorState
AMS提供一個public的接口,用于查詢所有進(jìn)程的ErrorState。
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
synchronized (mProcLock) {
// 遍歷所有進(jìn)程
mProcessList.forEachLruProcessesLOSP(false, app -> {
// 獲取進(jìn)程的mErrorState
final ProcessErrorStateRecord errState = app.mErrorState;
final boolean crashing = errState.isCrashing();
final boolean notResponding = errState.isNotResponding();
if ((app.getThread() != null) && (crashing || notResponding)) {
ActivityManager.ProcessErrorStateInfo report = null;
if (crashing) {
// 如果是crashing,需要獲取CrashingReport
report = errState.getCrashingReport();
} else if (notResponding) {
// 如果是notResponding,需要獲取notRespondingReport
report = errState.getNotRespondingReport();
}
if (report != null) {
if (errList[0] == null) {
errList[0] = new ArrayList<>(1);
}
errList[0].add(report);
} else {
Slog.w(TAG, "Missing app error report, app = " + app.processName +
" crashing = " + crashing +
" notResponding = " + notResponding);
}
}
});
}
return errList[0];
}這個方法的作用,主要是找到出現(xiàn) Crash 或 ANR 的進(jìn)程列表??梢酝ㄟ^循環(huán)調(diào)用該方法,判斷進(jìn)程是否發(fā)生 ANR。
不過這個判斷不會很準(zhǔn),因?yàn)橹挥挟?dāng)發(fā)生 ANR 的進(jìn)程的notRespondingReport生成后,才會返回該進(jìn)程。由前面的分析克制,生成notRespondingReport的時機(jī),是在dump trace完成之后,彈出ANR彈窗之前。
以下幾種情況會導(dǎo)致我們無法獲取到進(jìn)程的ErrorState:
- 用戶可能在我們調(diào)用方法之間,殺掉進(jìn)程
- 對于oppo和vivo手機(jī),發(fā)生ANR后會自動殺死進(jìn)程,幾乎沒辦法拿到
總結(jié)
當(dāng) ANR 發(fā)生時,系統(tǒng)會調(diào)用appNotResponding方法,修改進(jìn)程的ErrorState狀態(tài),同時dump豐富的信息。
主要流程如下:
- 將
am_anr信息,輸出到EventLog(這是ANR發(fā)生的起點(diǎn)) - 獲取重要進(jìn)程的trace信息,保存到
/data/anr/traces.txtJava進(jìn)程:當(dāng)前進(jìn)程、parent進(jìn)程、system_server、top 5的進(jìn)程Native進(jìn)程:audioserver、cameraserver等
- 將
ANR reason和CPU使用情況,輸出到MainLog - 將
CPU使用情況及traces文件信息,保存到/data/system/dropbox - 如果是后臺ANR,直接殺進(jìn)程
- 如果是前臺ANR,設(shè)置
notRespondingReport,激活 ANR 彈窗
以上就是Android發(fā)生ANR后的信息采集過程的詳細(xì)內(nèi)容,更多關(guān)于Android ANR信息采集的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android自定義View實(shí)現(xiàn)水波紋擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Android如何通過自定義View實(shí)現(xiàn)水波紋擴(kuò)散效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-08-08
RecyclerView多層級數(shù)據(jù)實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了RecyclerView多層級數(shù)據(jù)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Android ListView中動態(tài)添加RaidoButton的實(shí)例詳解
這篇文章主要介紹了Android ListView中動態(tài)添加RaidoButton的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-08-08
規(guī)避Android開發(fā)中內(nèi)存泄漏陷阱的解決方案
在Android開發(fā)中,內(nèi)存泄漏是一個常見但容易被忽視的問題,它會導(dǎo)致應(yīng)用程序占用過多的內(nèi)存資源,最終影響應(yīng)用的性能和用戶體驗(yàn),本文將深入探討Android常見的內(nèi)存泄漏問題,并提供優(yōu)化指南,需要的朋友可以參考下2024-05-05
Android編程實(shí)現(xiàn)分頁加載ListView功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)分頁加載ListView功能,結(jié)合實(shí)例形式分析了listview分頁加載的原理、實(shí)現(xiàn)技巧與相關(guān)注意事項,需要的朋友可以參考下2017-02-02
詳解如何實(shí)現(xiàn)一個Kotlin函數(shù)類型
這篇文章主要為大家詳細(xì)介紹了如何實(shí)現(xiàn)一個Kotlin函數(shù)類型,文中的實(shí)現(xiàn)方法講解詳細(xì),具有一定的借鑒價值,需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-10-10
Android編程之SQLite數(shù)據(jù)庫操作方法詳解
這篇文章主要介紹了Android編程之SQLite數(shù)據(jù)庫操作方法,簡單介紹了SQLite數(shù)據(jù)庫及Android操作SQLite數(shù)據(jù)庫的步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08

