性能优化的课程:
原理
工具的使用
方法论

ANR:
ANR概念
ANR类型
ANR怎么产生的
ANR如何定位分析
ANR面试相关问题

概念

ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间仍未能得到有效响应或者响应时间过长,都会造成ANR。

在 Android 里,应用程序的响应性是由 ActivityManager 和 WindowManager 系统服务监视的。当它监测到以下情况中的一个时,Android 就会针对特定的应用程序显示 ANR:

场景

  • Service Timeout
  • BroadcastQueue Timeout
  • ContentProvider Timeout
  • InputDispatching Timeout

Timeout时长

对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s;
ContentProvider超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;
InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

注意事项:
Input的超时机制与其他的不同,对于input来说即便某次事件执行时间超过timeout时长,只要用户后续在没有再生成输入事件,则不会触发ANR

超时检测机制

  1. Service超时检测机制:
    超过一定时间没有执行完相应操作来触发移除延时消息,则会触发anr;
  2. BroadcastReceiver超时检测机制:
    有序广播的总执行时间超过 2* receiver个数 * timeout时长,则会触发anr;
    有序广播的某一个receiver执行过程超过 timeout时长,则会触发anr;
  3. 另外:
    对于Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding;
    对于provider,在其进程启动时publish过程可能会出现ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR的对话框

如何避免 ANR?

考虑上面的 ANR 定义,让我们来研究一下为什么它会在 Android 应用程序里发生和如何最佳 构建应用程序来避免 ANR。

  • Android 应用程序通常是运行在一个单独的线程(例如,main)里。这意味着你的应用程序所做的事情如果在主线程里占用了太长的时间的话,就会引发 ANR 对话框,因为你的应用程 序并没有给自己机会来处理输入事件或者 Intent 广播。
  • 因此,运行在主线程里的任何方法都尽可能少做事情。特别是,Activity 应该在它的关键
    生命周期方法(如 onCreate()和 onResume())里尽可能少的去做创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是 Thread.sleep()。替代的方法是,主线程应该为子线程提供一个 Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将 能保证你的主线程保持对输入的响应性并能避免由于 5 秒输入事件的超时引发的 ANR 对话框。这种做法应该在其它显示 UI 的线程里效仿,因为它们都受相同的超时影响。
  • IntentReceiver 执行时间的特殊限制意味着它应该做:在后台里做小的、琐碎的工作如保 存设定或者注册一个Notification。和在主线 程里调用的其它方法一样,应用程序应该避 免在 BroadcastReceiver 里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver 的生命周期短),替代的是,如果响应 Intent 广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。顺便提及一句,你也应该避免在IntentReceiver 里启动一个 Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应 Intent 广播时需要向用户展示什么,你应该使用 Notification Manager 来实现。
  • 增强响应灵敏性
    一般来说,在应用程序里,100 到 200ms 是用户能感知阻滞的时间阈值。因此,这里有一些 额外的技巧来避免ANR,并有助于让你的应用程序看起来有响应性。
    如果你的应用程序为响应用户输入正在后台工作的话,可以显示工作的进度(ProgressBar 和 ProgressDialog 对这种情况来说很有用)。
    特别是游戏,在子线程里做移动的计算。
    如果你的应用程序有一个耗时的初始化过程的话,考虑可以显示一个 SplashScreen 或者快 速显示主画面并异步来填充这些信息。在这两种情况下,你都应该显示正在进行的进度,以免用户认为应用程序被冻结了。

前台与后台ANR(了解下就行)

  • 前台ANR:用户能感知,比如拥有前台可见的activity的进程,或者拥有前台通知的fg-service的进程,此时发生ANR对用户体验影响比较大,需要弹框让用户决定是否退出还是等待
  • 后台ANR:只抓取发生无响应进程的trace,也不会收集CPU信息,并且会在后台直接杀掉该无响应的进程,不会弹框提示用户

ANR分析

  1. 前台ANR发生后,系统会马上去抓取现场的信息,用于调试分析,收集的信息如下:
    (1). 将am_anr信息输出到EventLog,也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息
    (2).收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件:
    当前发生ANR的进程,system_server进程以及所有persistent进程;
    audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程;
    CPU使用率排名前5的进程;
    (3).将发生ANR的reason以及CPU使用情况信息输出到main log
    (4).将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录
    (5).对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉

  2. 分析步骤
    (1). 定位发生ANR时间点
    (2). 查看trace信息
    (3). 分析是否有耗时的message,binder调用,锁的竞争,CPU资源的抢占
    (4). 结合具体的业务场景的上下文来分析

如何避免ANR发生

  1. 主线程尽量只做UI相关的操作,避免耗时操作,比如过度复杂的UI绘制,网络操作,文件IO操作;
  2. 避免主线程跟工作线程发生锁的竞争,减少系统耗时binder的调用,谨慎使用sharePreference,注意主线程执行provider query操作;

总之,尽可能减少主线程的负载,让其空闲待命,以期可随时响应用户的操作。

trace.txt文件解读(重点要看的)

  1. 人为的收集traces.txt的命令
    traces.txt文件不一定要等到系统产生ANR时才可以生成,也可以通过adb命令手动生成:
    adb shell kill -3 888 //可指定进程pid
    执行完该命令后traces信息的结果保存到文件/data/anr/traces.txt
  2. trace文件解读
----- pid 888 at 2016-11-11 22:22:22 -----
Cmd line: system_server
ABI: arm
Build type: optimized
Zygote loaded classes=4113 post zygote classes=3239
Intern table: 57550 strong; 9315 weak
JNI: CheckJNI is off; globals=2418 (plus 115 weak)
Libraries: /system/lib/libandroid.so /system/lib/libandroid_servers.so /system/lib/libaudioeffect_jni.so /system/lib/libcompiler_rt.so /system/lib/libjavacrypto.so /system/lib/libjnigraphics.so /system/lib/libmedia_jni.so /system/lib/librs_jni.so /system/lib/libsechook.so /system/lib/libshell_jni.so /system/lib/libsoundpool.so /system/lib/libwebviewchromium_loader.so /system/lib/libwifi-service.so /vendor/lib/libalarmservice_jni.so /vendor/lib/liblocationservice.so libjavacore.so (16)
//已分配堆内存大小40MB,其中29M已用,总分配207772个对象
Heap: 27% free, 29MB/40MB; 307772 objects
... //省略GC相关信息//当前进程总99个线程
DALVIK THREADS (99):
//主线程调用栈
"main" prio=5 tid=1 Native| group="main" sCount=1 dsCount=0 obj=0x75bd9fb0 self=0x5573d4f770| sysTid=12078 nice=-2 cgrp=default sched=0/0 handle=0x7fa75fafe8| state=S schedstat=( 5907843636 827600677 5112 ) utm=453 stm=137 core=0 HZ=100| stack=0x7fd64ef000-0x7fd64f1000 stackSize=8MB| held mutexes=//内核栈kernel: __switch_to+0x70/0x7ckernel: SyS_epoll_wait+0x2a0/0x324kernel: SyS_epoll_pwait+0xa4/0x120kernel: cpu_switch_to+0x48/0x4cnative: #00 pc 0000000000069be4 /system/lib64/libc.so (__epoll_pwait+8)native: #01 pc 000000000001cca4 /system/lib64/libc.so (epoll_pwait+32)native: #02 pc 000000000001ad74 /system/lib64/libutils.so (_ZN7android6Looper9pollInnerEi+144)native: #03 pc 000000000001b154 /system/lib64/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+80)native: #04 pc 00000000000d4bc0 /system/lib64/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+48)native: #05 pc 000000000000082c /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+144)at android.os.MessageQueue.nativePollOnce(Native method)at android.os.MessageQueue.next(MessageQueue.java:323)at android.os.Looper.loop(Looper.java:135)at com.android.server.SystemServer.run(SystemServer.java:290)at com.android.server.SystemServer.main(SystemServer.java:175)at java.lang.reflect.Method.invoke!(Native method)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)"Binder_1" prio=5 tid=8 Native| group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750| sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450| state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100| stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB| held mutexes=kernel: __switch_to+0x70/0x7ckernel: binder_thread_read+0xd78/0xeb0kernel: binder_ioctl_write_read+0x178/0x24ckernel: binder_ioctl+0x2b0/0x5e0kernel: do_vfs_ioctl+0x4a4/0x578kernel: SyS_ioctl+0x5c/0x88kernel: cpu_switch_to+0x48/0x4cnative: #00 pc 0000000000069cd0 /system/lib64/libc.so (__ioctl+4)native: #01 pc 0000000000073cf4 /system/lib64/libc.so (ioctl+100)native: #02 pc 000000000002d6e8 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+164)native: #03 pc 000000000002df3c /system/lib64/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+24)native: #04 pc 000000000002e114 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+124)native: #05 pc 0000000000036c38 /system/lib64/libbinder.so (???)native: #06 pc 000000000001579c /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+208)native: #07 pc 0000000000090598 /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+96)native: #08 pc 0000000000014fec /system/lib64/libutils.so (???)native: #09 pc 0000000000067754 /system/lib64/libc.so (_ZL15__pthread_startPv+52)native: #10 pc 000000000001c644 /system/lib64/libc.so (__start_thread+16)(no managed stack frames)
... //此处省略剩余的N个线程.
  1. trace参数解读
"Binder_1" prio=5 tid=8 Native| group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750| sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450| state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100| stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB| held mutexes=
  • 第0行:

    • 线程名: Binder_1(如有daemon则代表守护线程)
    • prio: 线程优先级
    • tid: 线程内部id
    • 线程状态: NATIVE
  • 第1行:
    • group: 线程所属的线程组
    • sCount: 线程挂起次数
    • dsCount: 用于调试的线程挂起次数
    • obj: 当前线程关联的java线程对象
    • self: 当前线程地址
  • 第2行:
    • sysTid:线程真正意义上的tid
    • nice: 调度有优先级
    • cgrp: 进程所属的进程调度组
    • sched: 调度策略
    • handle: 函数处理地址
  • 第3行:
    • state: 线程状态
    • schedstat: CPU调度时间统计
    • utm/stm: 用户态/内核态的CPU时间(单位是jiffies)
    • core: 该线程的最后运行所在核
    • HZ: 时钟频率
  • 第4行:
    • stack:线程栈的地址区间
    • stackSize:栈的大小
  • 第5行:
    • mutex: 所持有mutex类型,有独占锁exclusive和共享锁shared两类
  • schedstat含义说明:
    • nice值越小则优先级越高。此处nice=-2, 可见优先级还是比较高的;

    • schedstat括号中的3个数字依次是Running、Runable、Switch,紧接着的是utm和stm

      • Running时间:CPU运行的时间,单位ns
      • Runable时间:RQ队列的等待时间,单位ns
      • Switch次数:CPU调度切换次数
      • utm: 该线程在用户态所执行的时间,单位是jiffies,jiffies定义为sysconf(_SC_CLK_TCK),默认等于10ms
      • stm: 该线程在内核态所执行的时间,单位是jiffies,默认等于10ms
  • 可见,该线程Running=186667489018ns,也约等于186667ms。在CPU运行时间包括用户态(utm)和内核态(stm)。 utm + stm = (12112 + 6554) ×10 ms = 186666ms。
  • 结论:utm + stm = schedstat第一个参数值。

更多anr问题可以参考:
ART 虚拟机之 Trace 原理

什么是ANR,如何避免ANR相关推荐

  1. android anr 文件路径,android出现ANR 如何导出anr文件

    1.老版Android系统 anr的导出 在项目目录下或app下运行命令: adb pull data/anr/traces.txt 1 测试了华为p7(Android 5.1.1)和荣耀v10(An ...

  2. ANR原理篇 - ANR信息收集过程

    系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录 前言 一.ANR日志信息收集过程 ...

  3. 浅谈ANR及log分析ANR

    一:什么是ANR ANR:Application Not Responding,即应用无响应 二:ANR的类型 ANR一般有三种类型: 1:KeyDispatchTimeout(5 seconds) ...

  4. 避免出现anr的方法_ANR原因及解决方法

    一.什么是ANR ANR: Application not responding (应用无响应),系统会向用户显示一个对话框,用户可以选择"等待"而让程序继续运行,也可以选择&qu ...

  5. android token机制_对Android 中的 ANR 进行详解

    前言 关于ANR,以前只知道Activity.BroadCastReceiver.Service三种组件的ANR时限.一般采用哪些方式避免ANR.以及通过data/anr/traces.txt去分析A ...

  6. Android进阶知识:ANR的定位与解决

    1.前言 ANR对于Android开发者来说一定不会陌生,从刚开始学习Android时的一不注意就ANR,到后来知道主线程不能进行耗时操作注意到这点后,程序出现ANR的情况就大大减少了,甚至于消失了. ...

  7. Android开发之ANR原因分析

    第一步拿到anr日志文件(低版本手机获取ANR日志方法) bogon:wyze-doorbell-wireless-android-14 xiayiye5$ adb shell 进入手机目录 HS89 ...

  8. Android ANR

    ANRs ("Application Not Responding"),意思是"应用没有响应". 1)什么引发了ANR? 在Android里,应用程序的响应性是 ...

  9. Android App 优化之 ANR 详解

    为了便于阅读, 应邀将Android App性能优化系列, 转移到掘金原创上来. 掘金的新出的"收藏集"功能可以用来做系列文集了. 今天先来聊聊ANR. 1, 你碰到ANR了吗 在 ...

  10. Android App优化之ANR详解

    引言 背景:Android App优化, 要怎么做? Android App优化之性能分析工具 Android App优化之提升你的App启动速度之理论基础 Android App优化之提升你的App ...

最新文章

  1. 图灵书单——程序员的算法
  2. NYOJ 336 子序列
  3. 对爬虫爬取到的数据进行存储
  4. 使用Spring Security 5进行密码编码器迁移
  5. Spring JpaRepository示例(内存中)
  6. Python之数据分析(坐标刻度定位器、散点图、柱状图、颜色区域填充)
  7. 如何MATLAB中将一个向量或者矩阵强行转换为列向量
  8. 在Chrome浏览器中保存的密码有多安全?
  9. Java全套学习资料
  10. 访问网上邻居-修改账号密码
  11. 关于卸载office的问题:office无法卸载的办法(附office安装和注册表查看)
  12. excel模板报表转PDF下载
  13. AcWing 1934. 贝茜放慢脚步
  14. 个人github地址 https://github.com/Gerry1218
  15. 常规的几个API接口(也包括比价)
  16. 代码随想录第十天 LeetCode 232、225(栈,队列)
  17. mysql 创建连接报错_创建数据库连接报错 · Issue #IRK5Z · 一米一粟信息科技/kooteam - Gitee.com...
  18. 弄懂这56个Python使用技巧,秒变Python大神!
  19. C语言-编写程序,找出2~100以内的所有质数(素数)。
  20. 初入社会要避免入的坑-租房篇

热门文章

  1. [裴礼文数学分析中的典型问题与方法习题参考解答]4.5.1
  2. 一种xml解析二次处理的方法(根据剔除规则仅保留部分二级节点)
  3. vue Component inside <Transition> renders non-element root node that cannot be animated.
  4. 饥荒开服(含各种踩雷)
  5. 不礼让行人怎么抓拍的_不礼让行人百分百抓拍吗?不礼让行人如何申诉成功
  6. flex 随机数产生方法
  7. 微信账户如何解除对第三方应用的授权
  8. 怎么删除淘宝足迹 淘宝足迹多久才能形成
  9. Python爬虫入门教程 62-100 30岁了,想找点文献提高自己,还被反爬了,Python搞起,反爬第2篇
  10. 国产手机已经用上了 120W 快充技术,苹果还在用20W的原因一