返回顶部
首页 > 资讯 > 移动开发 >浅谈AndroidANR的信息收集过程
  • 433
分享到

浅谈AndroidANR的信息收集过程

2024-04-02 19:04:59 433人浏览 八月长安
摘要

目录一. ANR场景二. appNotResponding处理流程三. 总结一. ANR场景 无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotRespondi

一. ANR场景

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

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

  • Service Timeout:比如前台服务在20s内未执行完成;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

二. appNotResponding处理流程

1. 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信息,这些进程包含:

  • firstPids队列:第一个是ANR进程,第二个是system_server,剩余是所有persistent进程;
  • Native队列:是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程;
  • lastPids队列: 是指mLruProcesses中的不属于firstPids的所有进程。

2. 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文件内容是全新的方式,而非追加。

3. 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();
    }
}

该方法的主要功能,依次输出:

1.收集firstPids进程的stacks;

第一个是发生ANR进程;

第二个是system_server;

mLruProcesses中所有的persistent进程;

2.收集Native进程的stacks;(dumpNativeBacktraceToFile)

依次是mediaserver,sdcard,surfaceflinger进程;

3.收集lastPids进程的stacks;;

依次输出CPU使用率top 5的进程;

Tips: firstPids列表中的进程, 两个进程之间会休眠200ms, 可见persistent进程越多,则时间越长. top 5进程的traces过程中, 同样是间隔200ms, 另外进程使用情况的收集也是比较耗时.

4. 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);
}

5. 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)

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;

整个过程中进程Trace的输出是最为核心的环节,Java和Native进程采用不同的策略,如下:

进程类型 trace命令 描述
Java kill -3 [pid] 不适用于Native进程
Native debuggerd -b [pid] 也适用于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 ANR的信息收集过程的详细内容,更多关于Android ANR 的资料请关注编程网其它相关文章!

--结束END--

本文标题: 浅谈AndroidANR的信息收集过程

本文链接: https://lsjlt.com/news/158291.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 浅谈AndroidANR的信息收集过程
    目录一. ANR场景二. appNotResponding处理流程三. 总结一. ANR场景 无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotRespondi...
    99+
    2024-04-02
  • Android ANR的信息收集过程是什么
    本篇内容介绍了“Android ANR的信息收集过程是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一. ANR场景无论是四大...
    99+
    2023-06-21
  • 通过Python收集汇聚MySQL 表信息
    一.需求 统计收集各个实例上table的信息,主要是表的记录数及大小。 收集的范围是cmdb中所有的数据库实例。 二.公共基础文件说明 1.配置文件 配置文为db_servers_conf.ini,假设cmdb的DBServer为119...
    99+
    2017-02-22
    通过Python收集汇聚MySQL 表信息
  • 渗透测试信息收集查询真实IP过程
    查找真实IP 如果目标服务器不存在CDN可以直接通过 http://tools.jb51.net/aideddesign/ipcha 获取目标的一些IP及域名信息。 目标服...
    99+
    2024-04-02
  • 怎样理解trace信息的收集
    本篇文章为大家展示了怎样理解trace信息的收集,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 每逢与遇到SQL相关性能,我们总是需要收集10046的,来查看和诊...
    99+
    2024-04-02
  • 通过Python收集汇聚MySQL 表信息的实例详解
    目录一.需求二.公共基础文件说明1.配置文件2.定义声明db连接3.定义声明访问db的操作三.主要代码3.1 创建保存数据的脚本3.2 收集的功能脚本一.需求 统计收集各个实例上ta...
    99+
    2024-04-02
  • Oracle的自动统计信息不收集直方图的信息
    在oracle9i中,默认的统计信息收集是不收集直方图信息的,也就是说默认的MOTHOD_OPT模式为FOR ALL COLUMNS SIZE 1 在10g开始,dbms_stats包中默认的ME...
    99+
    2024-04-02
  • 浅谈DNS域名解析的过程
    用户在浏览器输入www.baidu.com时,DNS域名解析大致分为以下几个过程: 浏览器客户端检查自身有没有该域名的缓存: 如果浏览器有命中,直接返回该域名对应的IP地址,解析结束; ...
    99+
    2023-09-17
    服务器 网络 前端
  • 浅谈uniapp姓名的发布过程
    随着移动互联网的不断发展,越来越多的开发者开始致力于开发移动应用程序。而在移动应用程序的开发中,选择合适的框架就显得尤为重要。相信已经有不少人听说过uniapp这个跨平台开发框架,今天我们就来谈谈uniapp的发布过程。一、uniapp的基...
    99+
    2023-05-14
  • 浅谈Spring Boot 整合ActiveMQ的过程
    RabbitMQ是比较常用的AMQP实现,这篇文章是一个简单的Spring boot整合RabbitMQ的教程。安装ActiveMQ服务器,(也可以不安装,如果不安装,会使用内存mq)构建Spring boot项目,增加依赖项,只需要添加这...
    99+
    2023-05-30
  • Android 如何收集已发布程序的崩溃信息
    我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃。不过理想是丰满的,现实是骨感的。没有一个程序员能保证自己写的程序绝对不会出现异常崩溃。...
    99+
    2022-06-06
    程序 Android
  • 数据库中怎么找出未收集统计信息以及统计信息过期的表
    小编给大家分享一下数据库中怎么找出未收集统计信息以及统计信息过期的表,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!下面这个查询可以找到从未收集过统计信息或者统计信息过期的表。EXEC&nb...
    99+
    2024-04-02
  • 浅谈java面向对象的数组化信息处理
    虽然非常简单的东西,但对于一些自学的新手和前期理解的不够深的萌新来说,应该会有很大的帮助,有助于理解。初学面向对象的时候,我想许多同学对此很纳闷。简单的问题复杂化,多此一举诸之云云。那么往下看:package cn.bdqn.test3;i...
    99+
    2023-05-31
    java 面向对象 数组
  • oracle统计信息收集的方法是什么
    Oracle数据库中对表的统计信息可以通过以下方法进行收集: 自动统计信息收集:Oracle数据库会自动收集表的统计信息,以便优...
    99+
    2024-04-09
    oracle
  • Oracle 12c数据库优化器统计信息收集的最佳实践(三)|何时不需要收集统计信息
    原文链接 http://www.oracle.com/technetwork/database/bi-datawarehousing/twp-bp-for-stats-gather-12...
    99+
    2024-04-02
  • Web应用程序信息收集工具wig有什么用
    这篇文章主要为大家展示了“Web应用程序信息收集工具wig有什么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Web应用程序信息收集工具wig有什么用”这篇文章吧。Web应用程序信息收集工具w...
    99+
    2023-06-04
  • Oracle里收集与查看统计信息的方法
    Oracle数据库里的统计信息是这样的一组数据:它存储在数据字典里,且从多个维度描述了Oracle数据库里对象的详细信息。CBO会利用这些统计信息来计算目标SQL各种可能的、不同的执行路径的成本,并从中选择...
    99+
    2024-04-02
  • win7收集错误信息重启的解决方案
    当我们使用电脑时,我们无法避免一些问题。例如,最近朋友反馈win7收集错误信息重启。事实上,win7收集错误信息重启的解决方案非常简单。今天,小编将教你如何解决win7收集错误信息的重启。让我们一起学习吧!重启计算机,看看它是否能进入系统。...
    99+
    2023-07-10
  • win10收集错误信息重启的解决方案
    当我们使用计算机时,我们无法避免一些问题。例如,最近朋友反馈win10收集错误信息重启。事实上,win10收集错误信息重启的解决方案非常简单。今天,小编将教你如何解决win10收集错误信息重启的问题。让我们一起学习!重新启动计算机,看看它是...
    99+
    2023-07-12
  • 通过Python收集MySQL MHA 部署及运行状态信息的功能实现
    一. 背景介绍  当集团的MySQL数据库实例数达到2000+、MHA集群规模数百个时,对MHA的及时、高效管理是DBA必须面对的一个挑战。MHA 集群 节点信息 和 运行状态 是管理的基础。本篇幅主要介绍如何通过Python实现收集MH...
    99+
    2016-03-07
    通过Python收集MySQL MHA 部署及运行状态信息的功能实现
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作