文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android ANR的信息收集过程是什么

2023-06-21 20:37

关注

本篇内容介绍了“Android ANR的信息收集过程是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

一. ANR场景

无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法,下面从这个方法说起。

以下场景都会触发调用AMS.appNotResponding方法:

二. appNotResponding处理流程

AMS.appNotResponding

final 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));        }                        //弹出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时, 会按顺序依次执行:

  1. 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;

  2. 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时; 【见小节2】

  3. 输出当前各个进程的CPU使用情况以及CPU负载情况;

  4. 将traces文件和 CPU使用情况信息保存到dropbox,即data/system/dropbox目录

  5. 根据进程类型,来决定直接后台杀掉,还是弹框告知用户.

ANR输出重要进程的traces信息,这些进程包含:

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内容【见小节3】    dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);    return tracesFile;}

这里会保证data/anr/traces.txt文件内容是全新的方式,而非追加。

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【见小节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, 另外进程使用情况的收集也是比较耗时.

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 {        //【见小节5】        dump_backtrace_to_file(pid, fd);    }    close(fd);}

dump_backtrace_to_file

[-> debugger.c]

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;}

可见,这个过程主要是通过向debuggerd守护进程发送命令DEBUGGER_ACTION_DUMP_BACKTRACE, debuggerd收到该命令,在子进程中调用 dump_backtrace()来输出backtrace。

三. 总结

触发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命令描述
Javakill -3 [pid]不适用于Native进程
Nativedebuggerd -b [pid]也适用于Java进程

说明:kill -3命令需要虚拟机的支持,所以无法输出Native进程traces.而debuggerd -b [pid]也可用于Java进程,但信息量远没有kill -3多。 总之,ANR信息最为重要的是dropbox信息,比如system_server_anr。

重要节点:

“Android ANR的信息收集过程是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯