微信Android ANR原理解析及解决方案
最近微信安卓版的“两位数字+15个中文字符句号”BUG把ANR带回了大家的视野。
前情介绍-微信bug事件
在微信上给安卓手机用户发送:
“11。。。。。。。。。。。。。。。”
(两位数字+15个中文字符句号)接收到这样的信息以后,部分安卓手机在发送或收到这条消息时微信会无响应,如下图。
本文将从如下几个方面给大家介绍一下Android ANR相关的知识:
ANR的原理
如何定位分析ANR(以微信为例)
如何解决&避免ANR
ANR的原理
什么是ANR
ANR全称Application Not Responding,意为程序未响应。Android基于消息处理机制系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应就会造成ANR。发生ANR时的表现一般是弹出一个提示框,告知程序无响应,让用户选择等待还是强制退出。时的表现一般是弹出一个提示框,告知程序无响应,让用户选择等待还是强制退出。
什么场景会发生ANR
一般情况下,如下场景会导致ANR:
Service Timeout:比如前台服务在20s内未执行完成
BroadcastReceiver Timeout:比如前台广播在10s内未执行完成
ContentProvider Timeout:内容提供者,在publish过超时10s
InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件
系统如何发现ANR
Android基于消息处理机制在系统层实现了一套发现ANR的机制,其其核心原理是消息调度和超时处理。
下面以Service为例简单介绍下ANR的检测流程。
Service的ANR机制
Service的ANR检测流程可以简单归纳为首先埋一个超时消息,然后操作在超时内完成之后移除超时消息,否则消息被系统收到触发ANR。
Service的ANR消息埋点
Service的启动的大致过程如下图:
在Service进程启动之后并attach到system_server进程时会进行ANR埋点,代码如下:
该方法的主要工作就是发送delay消息(SERVICE_TIMEOUT_MSG),所以,如果我们不能在操作完成时remove该消息,则消息会被发送到MainHandler从而触发ANR。
Service的ANR消息移除
根据ANR对监测原理,既然埋下了超时消息,那必然也有对应对移除逻辑,对于Service,在Service启动完成时会对消息进行移除,代码如下:
可以看出该方法的主要工作就是在service启动完成时则移除服务超时消息。
触发ANR
当然,如果没能及时remove消息,则会触发ANR,通过上述代码,我们可以发现在system_server进程中有一个Handler线程, 名叫”ActivityManager”,而ANR对消息也是向该Handler线程发送。该Handler对ANR对处理逻辑如下:
总结
通过如上分析,可以大致总结初Android对ANR对监测机制主要基于消息机制,通过预发送超时消息并及时移除来实现超时检测对功能。
ANR的信息收集
通过如上的ANR触发逻辑可以看到ANR触发时最终都会调用AMS.AppNotResponding()方法,下面从这个方法说起。
当发生ANR时, 会按顺序依次执行:
-
第一次更新CPU的统计信息。这是发生ANR时,第一次CPU使用信息的采样
-
输出ANR Reason信息到EventLog, 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息
-
填充firstPids和lastPids数组
-
收集并输出重要进程列表中的各个线程的traces信息
-
第二次更新CPU统计信息,跟第一次一样,两次采样的数据分别对应ANR发生前后的CPU使用情况
-
将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录
-
根据进程类型,来决定直接后台杀掉,还是弹框告知用户
可以看出,除了前端提示之外,ANR发生的时候会写各种类型的日志来方便后续排查问题,主要的日志包括如下:
-
event log,主要包括ANR的发生时间,类型等基本信息
-
main log,主要包括ANR发生时的详细信息,如CPU的使用情况等
-
dropbox日志,通过dumpsys dropbox找到,主要为ANR发生时的详细信息,trace、函数调用、CPU信息等
-
trace日志,最常用的ANR分析文件,主要包括CPU信息和各进程的函数调用栈信息
如何定位和分析ANR的问题
通过对ANR的原理了解,我们可以从多个角度对ANR进行问题分析,如通过分析trace日志,dropbox日志等。
通过logcat分析
简单的情景,我们可以直接在logcat中检索ANR in关键词即可找到对应的ANR原因、堆栈信息和CPU使用情况,格式大致如下:
通过日志能看到ANR的原因(Input dispatching timed out),发生时的CPU负载等信息。
分析traces文件
实际情况中,这些信息相对还是偏少的,所以我们就需要通过分析trace文件或者dropbox文件来获取详细信息来定位问题。下面以微信为例:
首先,我们拿到trace文件,一般情况下,trace文件在如下目录:
但是实际中,部分机型会对trace有rename的过程,所以trace文件的存储可能如下:
如图,trace文件的格式为traces_[package]_[time].txt
将trace文件pull到本地,打开后可以看到如下的内容:
有源码场景下问题定位
一般情况下,如果是自有的App,我们直接通过分析堆栈信息,即可定位到具体的代码行然后可以直接通过源码调试解决问题。
无源码场景下问题定位
如果App是黑盒,比如微信这样,那我们就需要借助一些反编译的手段来进行定位。
以微信的这个ANR为例,通过分析多个ANR的trace文件后,发现每次的堆栈其实都有细微的不一样,上面的这个堆栈信息里面显示的错误可能是由于正则匹配,但是还有如下的情况:
通过这个堆栈信息可以看出是由于获取textWidth导致的问题,所以可以通过找到相同的父调用链来分析问题:
所以我们基本可以定位到问题是在celltextview到这几个方法调用里面。
然后,我们就可以开始进行反编译定位问题了,一般我们有两种方案:
-
通过动态调试来找到问题。首先,我们需要co.debuggable = 1的设备(root或者模拟器均可),然后先通过baksmali将微信apk反编译成smali文件,然后通过在android studio安装SmaliIDEA调试插件,导入反编译好的smali项目,即可开始调试(具体操作可以自行查阅)。
-
直接反编译代码,然后人肉定位问题。首先,我们需要先将apk解压,取出dex文件,借助dex2jar将dex文件转换成jar文件,然后借助jd-gui来查看源码,然后人肉定位问题。
通过如上的两个方案,可以帮助我们在黑盒的情况下定位到具体问题。
如何避免ANR
ANR本质是一个性能问题,要求主线程在超时内完成操作,所以想要避免ANR,根本的就是需要杜绝主线程中的耗时操作。
所以我们可以使用一些异步的方式来进行耗时操作,常用的有AsynkTask、Thread等,然后通过Handler来处理结果,只在主程序进行耗时较少的更新操作。