无论是四大组件或者进程等只要发生 ANR,最终都会调用 AMS.appNotResponding() 方法,下面从这个方法说起。
以下场景都会触发调用 AMS.appNotResponding 方法:
Service Timeout:比如前台服务在20s内未执行完成;broadcastQueue Timeout:比如前台广播在10s内未执行完成inputdispatching Timeout:输入事件分发超时5s,包括按键和触摸事件。frameworks/base/core/java/androID/os/DeBUG.javaframeworks/base/core/jni/androID_os_DeBUG.cppsystem/core/libcutils/deBUGger.c
二. appNotResponding处理流程2.1 AMS.appNotRespondingfinal voID appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... updatecpuStatsNow(); //第一次 更新cpu统计信息 synchronized (this) { //PowerManager.reboot() 会阻塞很长时间,因此忽略关机时的ANR if (mShuttingDown) { return; } else if (app.notResponding) { return; } else if (app.crashing) { return; } //记录ANR到EventLog EventLog.writeEvent(EventLogTags.AM_ANR, app.userID, app.pID, app.processname, app.info.flags, annotation); // 将当前进程添加到firstPIDs firstPIDs.add(app.pID); int parentPID = app.pID; //将system_server进程添加到firstPIDs if (MY_PID != app.pID && MY_PID != parentPID) firstPIDs.add(MY_PID); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); if (r != null && r.thread != null) { int pID = r.pID; if (pID > 0 && pID != app.pID && pID != parentPID && pID != MY_PID) { if (r.persistent) { firstPIDs.add(pID); //将persistent进程添加到firstPIDs } else { lastPIDs.put(pID, Boolean.TRUE); //其他进程添加到lastPIDs } } } } } // 记录ANR输出到main log 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"); } //创建cpu tracker对象 final ProcesscpuTracker processcpuTracker = new ProcesscpuTracker(true); //输出traces信息【见小节2】 file tracesfile = dumpStackTraces(true, firstPIDs, processcpuTracker, lastPIDs, NATIVE_STACKS_OF_INTEREST); updatecpuStatsNow(); //第二次更新cpu统计信息 //记录当前各个进程的cpu使用情况 synchronized (mProcesscpuTracker) { cpuInfo = mProcesscpuTracker.printCurrentState(anrTime); } //记录当前cpu负载情况 info.append(processcpuTracker.printCurrentLoad()); info.append(cpuInfo); //记录从anr时间开始的cpu使用情况 info.append(processcpuTracker.printCurrentState(anrTime)); //输出当前ANR的reason,以及cpu使用率、负载信息 Slog.e(TAG, info.toString()); //将traces文件 和 cpu使用率信息保存到dropBox,即data/system/dropBox目录 addErrorToDropBox("anr", app, app.processname, activity, parent, annotation, cpuInfo, tracesfile, null); synchronized (this) { ... //后台ANR的情况, 则直接杀掉 if (!showBackground && !app.isInterestingToUserLocked() && app.pID != MY_PID) { app.kill("bg anr", true); return; } //设置app的ANR状态,病查询错误报告receiver makeAppNotRespondingLocked(app, activity != null ? activity.shortComponentname : null, annotation != null ? "ANR " + annotation : "ANR", info.toString()); //重命名trace文件 String tracesPath = SystemPropertIEs.get("dalvik.vm.stack-trace-file", null); if (tracesPath != null && tracesPath.length() != 0) { //traceRenamefile = "/data/anr/traces.txt" file traceRenamefile = new file(tracesPath); String newTracesPath; int lpos = tracesPath.lastIndexOf ("."); if (-1 != lpos) // 新的traces文件= /data/anr/traces_进程名_当前日期.txt newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processname + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos); else newTracesPath = tracesPath + "_" + app.processname; traceRenamefile.renameTo(new file(newTracesPath)); } //d出ANR对话框 Message msg = Message.obtain(); HashMap<String, Object> map = new HashMap<String, Object>(); msg.what = SHOW_NOT_RESPONDING_MSG; msg.obj = map; msg.arg1 = aboveSystem ? 1 : 0; map.put("app", app); if (activity != null) { map.put("activity", activity); } //向ui线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息 mUiHandler.sendMessage(msg); } }
当发生ANR时, 会按顺序依次执行:
输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;
收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时; 【见小节2】
输出当前各个进程的cpu使用情况以及cpu负载情况;
将traces文件和 cpu使用情况信息保存到dropBox,即data/system/dropBox目录
根据进程类型,来决定直接后台杀掉,还是d框告知用户.
ANR输出重要进程的traces信息,这些进程包含:
firstPIDs队列:第一个是ANR进程,第二个是system_server,剩余是所有persistent进程;
Native队列:是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程;
lastPIDs队列: 是指mLruProcesses中的不属于firstPIDs的所有进程。
public static file dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPIDs, ProcesscpuTracker processcpuTracker, SparseArray<Boolean> lastPIDs, String[] nativeProcs) { //默认为 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 { //当clearTraces,则删除已存在的traces文件 if (clearTraces && tracesfile.exists()) tracesfile.delete(); //创建traces文件 tracesfile.createNewfile(); fileUtils.setPermissions(tracesfile.getPath(), 0666, -1, -1); } catch (IOException e) { return null; } //输出trace内容【见小节3】 dumpStackTraces(tracesPath, firstPIDs, processcpuTracker, lastPIDs, nativeProcs); return tracesfile;}
这里会保证data/anr/traces.txt文件内容是全新的方式,而非追加。
3. AMS.dumpStackTracesprivate 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(); //首先,获取最重要进程的stacks if (firstPIDs != null) { try { int num = firstPIDs.size(); for (int i = 0; i < num; i++) { synchronized (observer) { //向目标进程发送signal来输出traces Process.sendSignal(firstPIDs.get(i), Process.SIGNAL_QUIT); observer.wait(200); //等待直到写关闭,或者200ms超时 } } } catch (InterruptedException e) { Slog.wtf(TAG, e); } } //下一步,获取native进程的stacks if (nativeProcs != null) { int[] pIDs = Process.getPIDsForCommands(nativeProcs); if (pIDs != null) { for (int pID : pIDs) { //输出native进程的trace【见小节4】 DeBUG.dumpNativeBacktracetofile(pID, tracesPath); } } } if (processcpuTracker != null) { processcpuTracker.init(); System.gc(); processcpuTracker.update(); synchronized (processcpuTracker) { processcpuTracker.wait(500); //等待500ms } //测量cpu使用情况 processcpuTracker.update(); //从lastPIDs中选取cpu使用率 top 5的进程,输出这些进程的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++; synchronized (observer) { Process.sendSignal(stats.pID, Process.SIGNAL_QUIT); observer.wait(200); } } } } } finally { observer.stopWatching(); }}
该方法的主要功能,依次输出:
收集firstPIDs进程的stacks;
第一个是发生ANR进程;
第二个是system_server;
mLruProcesses中所有的persistent进程;
收集Native进程的stacks;(dumpNativeBacktracetofile)
依次是mediaserver,sdcard,surfaceflinger进程;
收集lastPIDs进程的stacks;;
依次输出cpu使用率top 5的进程;
Tips: firstPIDs列表中的进程, 两个进程之间会休眠200ms, 可见persistent进程越多,则时间越长. top 5进程的traces过程中, 同样是间隔200ms, 另外进程使用情况的收集也是比较耗时.
DeBUG.dumpNativeBacktracetofile(pID, tracesPath)经过JNI调用如下方法:
static voID androID_os_DeBUG_dumpNativeBacktracetofile(jnienv* env, jobject clazz, jint pID, Jstring filename) { ... 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, 0666); /* -rw-rw-rw- */ ... if (lseek(fd, 0, SEEK_END) < 0) { fprintf(stderr, "lseek: %s\n", strerror(errno)); } else { //【见小节5】 dump_backtrace_to_file(pID, fd); } close(fd);}
5. dump_backtrace_to_fileint dump_backtrace_to_file(pID_t tID, int fd) { return dump_backtrace_to_file_timeout(tID, fd, 0);}int dump_backtrace_to_file_timeout(pID_t tID, int fd, int timeout_secs) { //通过socket向服务端发送dump backtrace的请求 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; //阻塞等待,从sock_fd中读取到服务端发送过来的数据,并写入buffer while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) { //再将buffer数据输出到traces.txt文件 if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) { result = -1; break; } } close(sock_fd); return result;}
可见,这个过程主要是通过向deBUGgerd守护进程发送命令DEBUGGER_ACTION_DUMP_BACKTRACE, deBUGgerd收到该命令,在子进程中调用 dump_backtrace()来输出backtrace,更多内容见Native进程之Trace原理。
三. 总结触发ANR时系统会输出关键信息:(这个较耗时,可能会有10s)
将am_anr信息,输出到EventLog.(ANR开始起点看EventLog)
获取重要进程trace信息,保存到/data/anr/traces.txt;(会先删除老的文件)
Java进程的traces;
Native进程的traces;
ANR reason以及cpu使用情况信息,输出到main log;
再将cpu使用情况和进程trace文件信息,再保存到/data/system/dropBox;
整个过程中进程Trace的输出是最为核心的环节,Java和Native进程采用不同的策略,如下:
进程类型 trace命令 文章 描述
Java kill -3 [pID] 解读Java进程的Trace文件 不适用于Native进程
Native deBUGgerd -b [pID] Native进程之Trace原理 也适用于Java进程
说明:kill -3命令需要虚拟机的支持,所以无法输出Native进程traces.而deBUGgerd -b [pID]也可用于Java进程,但信息量远没有kill -3多。 总之,ANR信息最为重要的是dropBox信息,比如system_server_anr。
重要节点:
进程名:cat /proc/[pID]/cmdline
线程名:cat /proc/[tID]/comm
Kernel栈:cat /proc/[tID]/stack
Native栈: 解析 /proc/[pID]/maps
以上是内存溢出为你收集整理的Android稳定性系列3 ANR的信息收集过程全部内容,希望文章能够帮你解决Android稳定性系列3 ANR的信息收集过程所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)