Android之ANR分析
目录
一、ANR是什么?
ANR,Application Not Responding,字面意思就是应用无响应,稍加解释就是用户的一些操作无法从应用中获取反馈。在实际的应用中应当去避免这种现象,虽然他暂时不会造成应用崩溃,但是却极大的损坏了用户体验。
手机APP出现引用后一般系统会出现一个提示弹窗
车机系统根据策略会直接闪退。
无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法。
二、ANR触发原因和场景
ANR类型
- Service Timeout: 比如前台服务在20s(后台服务200s)内未执行完成
- BroadcastQueue Timeout:比如前台广播在10s内未执行完成
- InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件
ANR原因
分析上述ANR场景发现ANR出现的原因一般都是超时,具体原因有两点:
- 当前事件没有机会得到处理,如UI线程正在响应另一个事件,当前事件由于某种原因被阻塞了。
- 当前事件正在被处理,但是由于耗时太长没有能够及时完成。
ANR常见的场景
- 主线程频繁进行IO操作,比如读写文件或者数据库
- 硬件操作如进行调用照相机或者录音等操作
- 多线程操作的死锁,导致主线程等待超时
- 主线程操作调用join()方法、sleep()方法或者wait()方法
- 耗时动画/耗资源行为导致CPU负载过重
- system server中发生WatchDog ANR
- service binder的数量达到上限
- SP 存入大数据量的文件
- onDraw里面绘制复杂的图形
三、ANR触发时系统做了啥
-
弹出一个提示弹窗,如上图
-
将ANR信息输出到traces.txt文件中
traces.txt文件是一个ANR记录文件,用于开发人员调试,目录位于/data/anr中,无需root权限即可通过pull命令获取,下面的命令可以将traces.txt文件拷贝到当前目录下。
车机系统需要先root,生成的是一个txt文件,类似这种 anr_2023-07-12-08-57-29-529。 -
将ANR信息输出到Logcat中
-
将traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目录
四、看源码
- AMS.appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... //第一次 更新cpu统计信息 updateCpuStatsNow(); 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) { //将persistent进程添加到firstPids firstPids.add(pid); } else { //其他进程添加到lastPids lastPids.put(pid, Boolean.TRUE); } } } } } // 记录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信息 File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids, NATIVE_STACKS_OF_INTEREST); //第二次更新cpu统计信息 updateCpuStatsNow(); //记录当前各个进程的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)); } //弹出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); } }
小结
1. 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息2. 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时3. 输出当前各个进程的CPU使用情况以及CPU负载情况4. 将traces文件和 CPU使用情况信息保存到dropbox,即data/system/dropbox目录5. 根据进程类型,来决定直接后台杀掉,还是弹框告知用户
ANR输出重要进程的traces信息,这些进程包含:
1. firstPids队列:第一个是ANR进程,第二个是system_server,剩余是所有persistent进程 2. Native队列:是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程 3. lastPids队列: 是指mLruProcesses中的不属于firstPids的所有进程。
- AMS.dumpStackTraces
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内容 dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs); return tracesFile;}
- AMS.dumpStackTraces
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(); //首先,获取最重要进程的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 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, 另外进程使用情况的收集也是比较耗时.
-
dumpNativeBacktraceToFile
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); ... if (lseek(fd, 0, SEEK_END) < 0) { fprintf(stderr, "lseek: %s\n", strerror(errno)); } else { dump_backtrace_to_file(pid, fd); } close(fd);}
- dump_backtrace_to_file
int 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;}
重要节点
- 进程名:cat /proc/[pid]/cmdline
- 线程名:cat /proc/[tid]/comm
- Kernel栈:cat /proc/[tid]/stack
- Native栈: 解析 /proc/[pid]/maps
总结
触发ANR时系统会输出关键信息:(这个较耗时,可能会有10s)
1.将am_anr信息,输出到EventLog.(ANR开始起点看EventLog)2.获取重要进程trace信息,保存到/data/anr/traces.txt;(会先删除老的文件)Java进程的traces;Native进程的traces;3.ANR reason以及CPU使用情况信息,输出到main log;4.再将CPU使用情况和进程trace文件信息,再保存到/data/system/dropbox
流程总结
- 前台服务,超时为SERVICE_TIMEOUT = 20s
- 后台服务,超时为SERVICE_BACKGROUND_TIMEOUT = 200s
- 前台广播,超时为BROADCAST_FG_TIMEOUT = 10s
- 后台广播,超时为BROADCAST_BG_TIMEOUT = 60s
- 应用页面相应为5s
- Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding方法来处理超时,记录日志、弹出ANR的dialog等操作。
避免措施
- 主线程不要进行耗时操作,耗时操作放在子线程进行处理,如网络请求,复杂的IO操作
- 避免在BroadcastReceiver里做耗时的操作。如果要进行复杂的耗时操作,可以在onReceive()方法中启动一个Service来处理
- 在Activity和Service的生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。建议使用Handler+Message的方式做一些耗时的创建操作
五、参考链接
- https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650243788&idx=1&sn=e4eef0294d4961d5aef3b90869a9ac55&chksm=886373a3bf14fab5abe7075cc16705505c244ad645bc1956ab466292e79b4298610a117c4a06&scene=27
- http://www.dedeyun.com/it/m/100570.html
- https://blog.csdn.net/zx54633089/article/details/115320309#t6
来源地址:https://blog.csdn.net/aylr2015/article/details/131682479
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341