Android ANR监控与分析

转载自:http://www.10tiao.com/html/203/201609/2649752287/1.html

ANR(Application Not Responding),系统检测到APP长时间没有反应,ANR虽然不是异常但会严重影响用户体验,所以上报解决ANR是非常必要的。


ANR的触发条件



触发ANR的必要条件是主线程阻塞。



分为以下三类:


  1. 主线程在5s内没有处理完输入事件;

  2. Service阻塞20s;

  3. 前台广播阻塞10s或后台广播阻塞60s。


实际使用中ANR通常是由第一类触发条件触发的。




ANR执行流程





了解ANR执行流程有利于我们制定ANR监控策略和获取ANR的相关信息,ANR的执行步骤如下:


  1. 系统捕获到ANR发生;

  2. Process 依次向本进程及其他正在运行的进程发送Linux信号量3;

  3. 进程接收到Linux信号量,并向 /data/anr/traces.txt 中写入进程信息;

  4. Log日志打印ANR信息;

  5. 进程进入ANR状态(此时可以获取到进程ANR信息);

  6. 弹出ANR提示框;

  7. 提示框消失,进程回归正常状态。


由于向 /data/anr/traces.txt 文件中写入信息耗时较长,从Input ANR触发到弹出ANR提示框一般在10s左右(不同rom时间不同)。




ANR监控机制





当APP发生ANR时,并不会像发生java异常时有回调接口,因此我们需要主动监控ANR何时发生并获取ANR相关信息。




1 FileObserver监控机制





监控方法:通过 FileOberver 监控 data/anr 文件夹下文件的变化,来确定ANR的发生。  


获取信息:主线程堆栈信息+ANR信息。  


优点:基于Linux底层通知机制,不额外消耗性能。 

 

缺点:监控不到Android5.0及以上版本的大部分机型。


ANR发生时多个进程会依次向 data/anr/traces.txt 文件中写入信息,因此 onEvent 方法会被多次回调,所以第一次回调 onEvent 方法后不再执行方法内部的逻辑。


FileObserver监控流程:




由于刚监控到ANR发生时进程并没有进入ANR状态,而是先向 /data/anr/traces.txt 文件中写入进程信息,此时并不会立即获取到进程的ANR信息,需要循环等待进程进入ANR状态。


ANR信息获取流程:



获取主线程堆栈信息


Thread mainThread = Looper.getMainLooper().getThread();


StackTraceElement[] mainStackTrace = mainThread.getStackTrace();




2 WatchDog监控机制





监控方法:在子线程中每隔5s向主线程发送信息来判断主线程是否阻塞,如果阻塞则认为发生ANR。 

 

获取信息:主线程堆栈信息+ANR信息。  


优点:可以监控到任意机型的ANR,不会因为系统的某些改变而失效。  


缺点:会产生额外消耗(影响不大)。


设置一个本地变量和全局变量,开启监控时两个变量相等,并让主线程执行全局变量+1操作 ,5秒后判断全局变量是否还和本地变量相等,若相等说明主线程阻塞了5s。继续让主线程执行 全局变量+1操作 ,由于上个5s主线程处于阻塞状态,进程可能发生ANR因此接下来5s不断地获取ANR信息,5s后未获取到继续判断 全局变量是否完成+1 操作。(不断地获取ANR信息是为了确定进程是否真正发生了ANR)


WatchDog监控流程:




WatchDog监控周期:


设置监控间隔为5s,则监控周期为 5s~10s。


如下图,绿色代表主线程空闲,红色代表主线程阻塞。在0s时向主线程发送信息,此时主线程没有阻塞,执行全局变量+1,在5s时全局变量已经+1WatchDog不认为主线阻塞,并继续向主线程发送信息,9s时主线程空闲执行全局变量+1,10s时全局变量完成+1,WatchDog不认为主线程阻塞。





现象:监控间隔为5s的WatchDog并没有发现主线程持续了8s的阻塞。  


结论:WatchDog监控到主线程阻塞的条件是,在一个监控间隔内主线程持续阻塞。 

 

监控间隔5s,主线程阻塞8s,WatchDog监控概率 (8-5)/5 * 100% = 60%,主线程阻塞10s及以上WatchDog监控概率为100%。   


由于主线程的阻塞通常持续到ANR提示框消失后,这个过程大约是10秒左右,所以监控间隔设置为5s。




3 混合监控





利用以上两种监控机制的优势最终使用如下方案实现ANR的全机型监控:


FileObserver + WatchDog


  1. Android 5.0 以下版本使用 FileObserver; 

  2. Android 5.0 及以上版本判断是否可以监控到 data\anr 文件夹的事件  

        a.可以监控到,使用 FileObserver;  

        b.监控不到,使用 WatchDog。




    日志分析    





通过ANR信息和主线程堆栈信息分析ANR




1 ANR信息







  1. 发生ANR的进程及组件;  

  2. ANR的类型(5.0以上带有等待队列的长度和队列头等待的时长);  

  3. CPU信息(可以判断是否是由于性能问题导致的ANR)  

         

CPU平均负载 Load (1分钟,5分钟,15分钟) 某一时刻正在使用和等待使用CPU的进程平均数量;  


ANR之前、之后CPU平均使用率  占用CPU时间 / 总时间 * 100%;


user:用户态运行时间  

kernel:内核态运行时间  

iowait:等待I/O操作的时间  

irq:CPU硬中断的时间 

softirq:CPU软中断的时间    

faults:minor(次要页错误)

major(主要页错误)    


内核在读取数据时会先查找CPU的cache和内存,如果找不到发出MPF信息,磁盘数据被加载到内存后,内核再次读取时会发出一个MnPF信息。可通过major次数来推断那个进程在进行磁盘I/O操作。


  • 如果CPU平均使用率接近100%则说明ANR可能是由CPU资源紧张导致的;

  • 如果某一进程CPU使用率很大则可能是因为该进程过多占用CPU资源从而导致我们进程发生ANR。




2 主线程堆栈信息




主线程堆栈信息最底层是进程启动时调用的函数,其中 ActivityThread.main 是Android程序的入口,最顶层是主线程正在执行的函数。


  • 我们可根据主线程堆栈信息由下向上找到我们的程序代码,查看在代码中是否进行了耗时等操作。

  • 如果在堆栈信息中未发现程序的代码,可以从 ActivityThread.main 方法开始逐级查看顶层堆栈信息调用的方法,通过分析Framework层源码以确定主线程现在的执行状态,并在我们代码中找到可能导致此状态的原因。




如何避免ANR





1 主线程




  1. 避免在主线程中进行网络,数据库和大量运算等耗时操作;  

  2. 主线程要避免使用Thread.wait() 或者 Thread.sleep()等待子线程,使用AsynTask Handler Bolts等框架,子线程完成时通知主线程;  

  3. 使用IntentService,IntentServcie是在子线程中执行的;  

  4. 降低子线程的优先级,主线程可以拥有更多的机会使用CPU资源。




2 停顿感





人们感知的时间是100ms到200ms  


  • 如果应用程序正在后台执行耗时工作,可以使用ProgressBar或者ProgressDialog来提示用户工作进度;   

  • 如果应用程序的初始化过程比较耗时,可以在初始化时显示一个Splash Activity,也可以先显示主界面然后再异步的加载初始化数据。




    QACR平台    





QACR平台支持监控Android客户端的 java异常,native异常,ANR卡顿。并及时将异常上报到QACR异常统计平台,平台会对异常进行管理和多维度统计。QACR为开发者提供release、beta两套环境,便于开发者对线上包和开发测试包的异常分别进行管理和统计。


你可能感兴趣的:(Android,性能优化)