Potato icon indicating copy to clipboard operation
Potato copied to clipboard

Android ANR Summary

Open yunshuipiao opened this issue 5 years ago • 0 comments

Android ANR Summary

[TOC]

什么情况下会出现ANR问题

ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。

具体来说,ANR会在以下几种情况中出现:

  • 输入事件(按键和触摸事件)5s内没被处理
  • BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s).
  • service 前台20s后台200s未完成启动
  • ContentProvider的publish在10s内没进行完

ANR机制的实现原理

  1. 在进行相关操作调用hander.sendMessageAtTime()发送一个ANR的消息,延时时间为ANR发生的时间(如前台Service是当前时间20s之后)。

  2. 进行相关的操作

  3. 操作结束后向remove掉该条message。如果相关的操作在规定时间没有执行完成,该条message将被handler取出并执行,就发生了ANR。

如何分析ANR问题

从前文可以明确,ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:

  1. 主线程在做一些耗时的工作
  2. 主线程被其他线程锁住
  3. cpu被其他进程占用,该进程没被分配到足够的cpu资源。

判断一个ANR属于哪种情况便是分析ANR问题的关键。那么拿到一个anr的日志,应该如何分析呢?进行服务器上报,分析线上用户的 ANR 问题。

在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了 /data/anr/traces.txt中,此外,ANR前后该进程打印出的log也有一定价值。一般来说可以按一下思路来分析:

  1. 从log中找到ANR反生的信息:可以从log中搜索“ANR in”或“am_anr”,会找到ANR发生的log,该行会包含了ANR的时间、进程、是何种ANR等信息,如果是BroadcastReceiver的ANR可以怀疑BroadCastReceiver.onRecieve()的问题,如果的Service或Provider就怀疑是否其onCreate()的问题。
  2. 在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:
    1. 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。
    2. 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑应用内一些代码不合理消耗掉了CPU资源,如出现了死循环或者后台有许多线程执行任务等等原因,这就要结合trace和ANR前后的log进一步分析了。
    3. 如果CPU总用量不高,该进程和其他进程的占用过高,这有一定概率是由于某些主线程的操作就是耗时过长,或者是由于主进程被锁造成的。
  3. 进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题最有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:
    1. 主线程是running或者native而对应的栈对应了我们应用中的函数,则很有可能就是执行该函数时候发生了超时。
    2. 主线程被block:非常明显的线程被锁,这时候可以看是被哪个线程锁了,可以考虑优化代码。如果是死锁问题,就更需要及时解决了。
    3. 由于抓trace的时刻很有可能耗时操作已经执行完了(ANR -> 耗时操作执行完毕 ->系统抓trace),这时候的trace就没有什么用了,主线程的stack就是这样的:

如何降低ANR的概率

  1. 主线程读取数据:在Android中主线程去读取数据是非常不好的,Android是不允许主线程从网络读数据的,但系统允许主线程从数据库或者其他地方获取数据,但这种操作ANR风险很大,也会造成掉帧等,影响用户体验
    1. 避免在主线程query provider,首先这会比较耗时,另外这个操作provider那一方的进程如果挂掉了或者正在启动,我们应用的query就会很长时间不会返回,我们应该在其他线程中执行数据库query、provider的query等获取数据的操作。
    2. sharePreference的调用:首先sharePreference的commit()方法是同步的,apply()方法一般是异步执行的。所以在主线程不要用其commit(),用apply()替换。其次sharePreference的写是全量写而非增量写,所以尽量都修改完同一apply,避免改一点apply一次(apply()方法在Activity stop的时候主线程会等待写入完成,提交多次就很容易卡)。并且存储文本也不宜过大,这样会很慢。另外,如果写入的是json或者xml,由于需要加和删转义符号,速度会比较慢。
  2. 不要在broadcastReciever的onRecieve()方法中干活,这一点很容易被忽略,尤其应用在后台的时候。为避免这种情况,一种解决方案是直接开的异步线程执行,但此时应用可能在后台,系统优先级较低,进程很容易被系统杀死,所以可以选择开个IntentService去执行相应操作,即使是后台Service也会提高进程优先级,降低被杀可能性。
  3. 各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲,应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR。
  4. 尽量避免主线程的被锁的情况,在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的ANR风险,对于这种情况有时也可以用异步线程来执行相应的逻辑。另外, 我们要避免死锁的发生(主线程被死锁基本就等于要发生ANR了)。

yunshuipiao avatar Jun 01 '19 03:06 yunshuipiao