微信Android ANR原理解析及解决方案

时间:2024-02-16 09:14:43

最近微信安卓版的“两位数字+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到这几个方法调用里面。

 

然后,我们就可以开始进行反编译定位问题了,一般我们有两种方案:

 

  1. 通过动态调试来找到问题。首先,我们需要co.debuggable = 1的设备(root或者模拟器均可),然后先通过baksmali将微信apk反编译成smali文件,然后通过在android studio安装SmaliIDEA调试插件,导入反编译好的smali项目,即可开始调试(具体操作可以自行查阅)。

     

  2. 直接反编译代码,然后人肉定位问题。首先,我们需要先将apk解压,取出dex文件,借助dex2jar将dex文件转换成jar文件,然后借助jd-gui来查看源码,然后人肉定位问题。

     

通过如上的两个方案,可以帮助我们在黑盒的情况下定位到具体问题。

 

 

如何避免ANR

ANR本质是一个性能问题,要求主线程在超时内完成操作,所以想要避免ANR,根本的就是需要杜绝主线程中的耗时操作。 


所以我们可以使用一些异步的方式来进行耗时操作,常用的有AsynkTask、Thread等,然后通过Handler来处理结果,只在主程序进行耗时较少的更新操作。