我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android之ANR分析

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

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android之ANR分析

目录


一、ANR是什么?

ANR,Application Not Responding,字面意思就是应用无响应,稍加解释就是用户的一些操作无法从应用中获取反馈。在实际的应用中应当去避免这种现象,虽然他暂时不会造成应用崩溃,但是却极大的损坏了用户体验。
手机APP出现引用后一般系统会出现一个提示弹窗 在这里插入图片描述
车机系统根据策略会直接闪退。

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

二、ANR触发原因和场景

ANR类型

  1. Service Timeout: 比如前台服务在20s(后台服务200s)内未执行完成
  2. BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  3. InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件

ANR原因

分析上述ANR场景发现ANR出现的原因一般都是超时,具体原因有两点:

  1. 当前事件没有机会得到处理,如UI线程正在响应另一个事件,当前事件由于某种原因被阻塞了。
  2. 当前事件正在被处理,但是由于耗时太长没有能够及时完成。

ANR常见的场景

  1. 主线程频繁进行IO操作,比如读写文件或者数据库
  2. 硬件操作如进行调用照相机或者录音等操作
  3. 多线程操作的死锁,导致主线程等待超时
  4. 主线程操作调用join()方法、sleep()方法或者wait()方法
  5. 耗时动画/耗资源行为导致CPU负载过重
  6. system server中发生WatchDog ANR
  7. service binder的数量达到上限
  8. SP 存入大数据量的文件
  9. onDraw里面绘制复杂的图形

三、ANR触发时系统做了啥

  1. 弹出一个提示弹窗,如上图

  2. 将ANR信息输出到traces.txt文件中

    traces.txt文件是一个ANR记录文件,用于开发人员调试,目录位于/data/anr中,无需root权限即可通过pull命令获取,下面的命令可以将traces.txt文件拷贝到当前目录下。
    车机系统需要先root,生成的是一个txt文件,类似这种 anr_2023-07-12-08-57-29-529。

  3. 将ANR信息输出到Logcat中

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

四、看源码

  1. 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的所有进程。
  1. 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;}
  1. 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();    }}

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

  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 {        dump_backtrace_to_file(pid, fd);    }    close(fd);}
  1. 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

流程总结

  1. 前台服务,超时为SERVICE_TIMEOUT = 20s
  2. 后台服务,超时为SERVICE_BACKGROUND_TIMEOUT = 200s
  3. 前台广播,超时为BROADCAST_FG_TIMEOUT = 10s
  4. 后台广播,超时为BROADCAST_BG_TIMEOUT = 60s
  5. 应用页面相应为5s
  6. Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding方法来处理超时,记录日志、弹出ANR的dialog等操作。

避免措施

  1. 主线程不要进行耗时操作,耗时操作放在子线程进行处理,如网络请求,复杂的IO操作
  2. 避免在BroadcastReceiver里做耗时的操作。如果要进行复杂的耗时操作,可以在onReceive()方法中启动一个Service来处理
  3. 在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

Android之ANR分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android中ANR的示例分析

这篇文章将为大家详细讲解有关Android中ANR的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Android ANR(Application Not Responding)的分析ANR (Ap
2023-05-30

通过Android trace文件分析死锁ANR实例过程

对于从事Android开发的人来说,遇到ANR(Application Not Responding)是比较常见的问题。一般情况下,如果有ANR发生,系统都会在/data/anr/目录下生成trace文件,通过分析trace文件,可以定位产
2022-06-06

深入学习Android ANR 的原理分析及解决办法

目录一、ANR说明和原因1.1 简介1.2 原因1.3 避免二、ANR分析办法2.1 ANR重现2.2 ANR分析办法一:Log2.3 ANR分析办法二:traces.txt2.4 ANR分析办法三:Java线程调用分析2.5 ANR分析办
2022-06-07

怎么使用Android ANR分析trace文件的产生流程

这篇文章主要介绍“怎么使用Android ANR分析trace文件的产生流程”,在日常操作中,相信很多人在怎么使用Android ANR分析trace文件的产生流程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
2023-07-02

Android Animation之TranslateAnimation的示例分析

这篇文章主要为大家展示了“Android Animation之TranslateAnimation的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Android Animation之Tr
2023-05-30

Android核心分析之一分析方法论探讨之设计意图

为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序员的内心的渴望,渴望理解这种复杂性。我研究的对象是作为手机开发平台的Android软件系统部分,而不是Dalvik虚拟机本身。作为一个从其他平台装接过来的程序员,要
2022-06-06

Android入门之RelativeLayout、FrameLayout用法分析

本文讲述的是Android中RelativeLayout、FrameLayout的用法。具体如下: RelativeLayout是一个按照相对位置排列的布局,跟AbsoluteLayout这个绝对坐标布局是个相反的理解。在RelativeL
2022-06-06

Android控件之TextView的分析探究

在android中,文本控件主要包括TextView控件和EditView控件,本节先对TextView控件的用法进行详细介绍。 TextView类继承自View类,TextView控件的功能是向用户显示文本的内容,但不允许编辑
2022-06-06

Android之网络通信案例分析

由于一个项目的需要,我研究了一下android的网络通信方式,大体和java平台的很相似! android平台也提供了很多的API供开发者使用,请按示例图: 首先,介绍一下通过http包工具进行通信,分get和post两种方式,两者的区
2022-06-06

Android广播事件流程与广播ANR原理深入刨析

这篇文章主要介绍了Android广播事件流程与广播ANR原理,ANR应用程序未响应,当主线程被阻塞时,就会弹出如下弹窗,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可任意参考一下
2022-11-13

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录