觸發(fā)ANR條件
- Service Timeout:前臺服務(wù)20s內(nèi)未完成執(zhí)行分尸,后臺服務(wù)超過200s
- BroadcastQueue Timeout:前臺廣播在10s內(nèi)未完成,后臺廣播超過60s未完成
- ContentProvider Timeout:內(nèi)容提供者,在publish過超時10s
- InputDispatching Timeout: 輸入事件分發(fā)超時5s锦聊,包括按鍵和觸摸事件
對應(yīng)最后執(zhí)行ANR記錄的方法是:
- ActiveServices.serviceTimeout()
- BroadcastQueue.AppNotResponding.run().appNotResponding()
- AMS.appNotRespondingViaProvider()
- AMS.inputDispatchingTimedOut()
理解為什么會出發(fā)ANR,必須明白四大組件的啟動流程箩绍。這個我們以后分析孔庭。
分析ANR觸發(fā)之后記錄問題
可以將anr目錄下的文件放到電腦上進(jìn)行查看
adb pull data/anr .
屬性系統(tǒng)可以通過
adb shell getprop dalvik.vm.stack-trace-file
這種方式查找對應(yīng)的屬性值
當(dāng)觸發(fā)ANR之后會調(diào)用AppErrors.appNotResponding()方法
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
...
//記錄ANR時間
long anrTime = SystemClock.uptimeMillis();
//更新CPU狀態(tài)
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow();
}
//特定場景下忽略ANR
synchronized (mService) {
if (mService.mShuttingDown) {
Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
return;
} else if (app.notResponding) {
Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
return;
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
}
}
//為了防止多次對相同app的anr執(zhí)行重復(fù)代碼,在此處標(biāo)注記錄材蛛,屬于上面的特定情況種的一種
app.notResponding = true;
//記錄ANR信息到Event Log中
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
//添加當(dāng)前app到firstpids列表中
firstPids.add(app.pid);
//如果可能添加父進(jìn)程到firstpids列表種
int parentPid = app.pid;
...
// 將ANR信息存在info變量中圆到,后續(xù)打印到LOGCAT怎抛,這部分的信息會以ActivityManager為Tag打印出來,包含了ANR的進(jìn)程芽淡,出現(xiàn)原因以及當(dāng)時的CPU狀態(tài)抽诉,這些對分析ANR是非常重要的信息
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
//將ANR信息輸出到traces文件,分為兩種吐绵,一種帶native層信息迹淌,一種不帶
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true;
String[] nativeProcs = NATIVE_STACKS_OF_INTEREST;
// don't dump native PIDs for background ANRs
File tracesFile = null;
if (isSilentANR) {
//這里返回了一個文件,這里的文件路徑是:`/data/anr/traces.txt`
//查找方法:adb shell getprop dalvik.vm.stack-trace-file
tracesFile = mService.dumpStackTraces(true, firstPids, null, lastPids,
null);
} else {
tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
nativeProcs);
}
//再次更新CPU信息己单,并且輸出到SystemLog中
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
info.append(processCpuTracker.printCurrentState(anrTime));
Slog.e(TAG, info.toString());
//上面的信息已經(jīng)對應(yīng)的ANR信息寫入/data/anr/traces.txt中
//給底層發(fā)送信號Process.SIGNAL_QUIT=3
if (tracesFile == null) {
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
//將traces文件 和 CPU使用率信息保存到dropbox唉窃,即data/system/dropbox目錄
//命名:system_server/system_app/data_app + type+...比如下面
//data_app_anr@1501989621992.txt.gz
//data_app_crash@1501989671926.txt
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
synchronized (mService) {
mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
//如果是后臺ANR則直接殺掉結(jié)束
if (isSilentANR) {
app.kill("bg anr", true);
return;
}
//設(shè)置app的not響應(yīng)狀態(tài),并查找errorReportReceiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : "ANR",
info.toString());
//彈出ANR對話框
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
//向ui線程發(fā)送纹笼,內(nèi)容為SHOW_NOT_RESPONDING_MSG的消息
mService.mUiHandler.sendMessage(msg);
}
}
我們來小節(jié)一下上面發(fā)生了什么:
- 立刻更新了CPU的信息
/** 2721 cpu (total|1|6),(user|1|6),(system|1|6),(iowait|1|6),(irq|1|6),(softirq|1|6) */ public static final int CPU = 2721; 給event_log中寫入值
- 忽略一些anr
- 在event_log中打印am_anr的信息纹份,這個是anr立刻發(fā)生的記錄
- 將ANR信息存在info變量中,后續(xù)打印到LOGCAT廷痘,這部分的信息會以ActivityManager為Tag打印出來蔓涧,包含了ANR的進(jìn)程,出現(xiàn)原因以及當(dāng)時的CPU狀態(tài)笋额,這些對分析ANR是非常重要的信息
- 將ANR信息輸出到data/anr/traces文件
- 沒有輸出到traces文件的時候元暴,給底層發(fā)送一個rocess.SIGNAL_QUIT=3信號
- 將traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目錄
- 如果是后臺ANR則直接殺掉結(jié)束
- 彈出ANR對話框
細(xì)節(jié)
怎么樣就將信息保存到了/data/anr/traces.txt了
1.AMS.dumpStackTraces
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
//tracesPath = "data/anr/traces.txt"
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
}
File tracesFile = new File(tracesPath);
try {
if (clearTraces && tracesFile.exists()) tracesFile.delete();
tracesFile.createNewFile();
FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
} catch (IOException e) {
Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
return null;
}
//[2]
dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
return tracesFile;
}
2.
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs){
FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
@Override
public synchronized void onEvent(int event, String path) { notify(); }
};
try {
observer.startWatching();
// 獲取發(fā)生ANR進(jìn)程的pid,然后遍歷這些進(jìn)程給進(jìn)程發(fā)送Process.SIGNAL_QUIT=3的信號
if (firstPids != null) {
try {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
synchronized (observer) {
final long sime = SystemClock.elapsedRealtime();
Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
observer.wait(1000); // Wait for write-close, give up after 1 sec
}
}
} catch (InterruptedException e) {
Slog.wtf(TAG, e);
}
}
// 接下來收集本地pids的堆棧
if (nativeProcs != null) {
int[] pids = Process.getPidsForCommands(nativeProcs);
if (pids != null) {
for (int pid : pids) {
final long sime = SystemClock.elapsedRealtime();
Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);//[3]輸出native進(jìn)程的trace并且限制超時時間
}
}
}
if (processCpuTracker != null) {
processCpuTracker.init();
System.gc();
processCpuTracker.update();
try {
synchronized (processCpuTracker) {
processCpuTracker.wait(500); // measure over 1/2 second.
}
} catch (InterruptedException e) {
}
processCpuTracker.update();
//從lastPids中選取CPU使用率 top 5的進(jìn)程兄猩,輸出這些進(jìn)程的stacks
final int N = processCpuTracker.countWorkingStats();
int numProcs = 0;
for (int i=0; i<N && numProcs<5; i++) {
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
try {
synchronized (observer) {
final long stime = SystemClock.elapsedRealtime();
Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
observer.wait(1000); // Wait for write-close, give up after 1 sec
}
} catch (InterruptedException e) {
Slog.wtf(TAG, e);
}
} else if (DEBUG_ANR) {
Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
+ stats.pid);
}
}
}
} finally {
observer.stopWatching();
}
}
小結(jié):
-
收集發(fā)生anr進(jìn)程的調(diào)用棧
- 發(fā)生anr的進(jìn)程
- anr進(jìn)程的父進(jìn)程(anr進(jìn)程是由于AMS生成茉盏,AMS在system_server進(jìn)程中,system_server進(jìn)程是anr的父進(jìn)程)
- mLruProcesses中所有的persistent進(jìn)程
-
收集Native進(jìn)程的調(diào)用棧
"/system/bin/audioserver"
"/system/bin/cameraserver"
"/system/bin/drmserver"
"/system/bin/mediadrmserver"
"/system/bin/mediaserver"
"/system/bin/sdcard"
"/system/bin/surfaceflinger"
-
"media.codec"
// system/bin/mediacodec -
"media.extractor"
// system/bin/mediaextractor -
"com.android.bluetooth"
// Bluetooth service
-
收集lastPids進(jìn)程的stacks
- 收集前五名
注意收集信息等待的時間
3.Debug.dumpNativeBacktraceToFileTimeout()
static void android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
jint pid, jstring fileName, jint timeoutSecs)
{
if (fileName == NULL) {
jniThrowNullPointerException(env, "file == null");
return;
}
const jchar* str = env->GetStringCritical(fileName, 0);
String8 fileName8;
if (str) {
fileName8 = String8(reinterpret_cast<const char16_t*>(str),
env->GetStringLength(fileName));
env->ReleaseStringCritical(fileName, str);
}
//打開文件(data/anr/traces.txt)
int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
if (fd < 0) {
fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
return;
}
dump_backtrace_to_file_timeout(pid, fd, timeoutSecs);//[4]
close(fd);
}
4.dump_backtrace_to_file_timeout()
int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
//發(fā)送dump請求得到sock_fd
int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
if (sock_fd < 0) {
return -1;
}
int result = 0;
char buffer[1024];
ssize_t n;
int flag = 0;
//從sock_fd中讀取信息寫入data/anr/traces.txt中
while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
flag = 1;
if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
result = -1;
break;
}
}
close(sock_fd);
...
return result;
}
主要是通過給底層發(fā)送DEBUGGER_ACTION_DUMP_BACKTRACE
來請求dump的sock_fd句柄枢冤,底層調(diào)用dump_backtraces()來獲取信息鸠姨,從而寫入data/anr/traces.txt文件中
總結(jié)
當(dāng)發(fā)生anr的時候,距離ANR最近的時間是am_anr這個日志的時間淹真,然后會打印各種信息有底層dump的讶迁,有進(jìn)程的調(diào)用棧信息等等。最后將trances.txt寫入data/system/dropbox目錄下核蘸,并且重命名巍糯,規(guī)則見上文。
補充
其中Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
發(fā)出退出進(jìn)程信號