面试:ANR相关---原因、源码、监控

Android性能优化杂谈-如何监控和解决ANR问题?_Ryane_Lee的博客-CSDN博客

Android ANR 监测方案解析 · TesterHome

ANR问题总结分析 - 掘金 

四大组件的onCreate/onReceive方法中Thread.sleep(2000),会产生几个ANR?

先回答一下上面会产生几个ANR(Application Not Responding),答案是两个,分别是在BroadcastReceiver和Service中产生的。

哪些场景会造成ANR?

  • Service Timeout:比如前台服务在20s内未执行完成

    • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s
    • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成

    对于广播队列有两个: foreground队列和background队列:

    • 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s
    • 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s
  • ContentProvider Timeout:内容提供者,在publish过超时10s

    ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。

  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

下面我们开始实践一下,分别在四大组件和Application(顺便测试)的onCreate/onReceive方法中进行Thread.sleep(21_000)。

首先是Activity中的onCreate:并没有出现ANR,从上面总结的那些场景,也并没有这个场景。有些人可能理解在主线程做了耗时操作会引起ANR,ANR的意思是应用没有响应,但是耗时操作实际上并不一定会导致没有响应。

在Service中的onCreate方法,默认创建的启动Activity创建的是前台服务(20s),所以会造成ANR。

ContentProvider在publish过超时10s,”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。而通过查看ContentProvider源码和启动流程,我们可以看到onCreate方法在publish之前执行,所以在onCreate方法中并不会造成ANR。

BroadcastReceiver,对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s,后台广播的耗时是60s。所以这里会产生ANR。注意发送广播的时候要添加intent的包名。

顺便做一下Application的测试,也不会发生ANR,Android中的ANR并没有这个场景。

一、ANR相关

ANR 的触发条件

ANR 的本质是一个性能问题,即主线程中的耗时操作造成主线程堵塞,导致应用失去响应能力。常见的超时时限:

Service 与 Bradcast 只会打印 trace 信息,不会提示用户 ANR 弹窗,大部分可感知的 ANR 都是由于 InputEvent。

出现ANR的一般有以下几种类型:

  • KeyDispatchTimeOut :最常见的一种类型,原因是View的按键事件或者触摸事件在特定的时间(5秒)内无法得到响应。
  • BroadcaseTimeOut:原因是BroadcastReceiver的onReceiver()函数运行在主线程中,在特定的时间(10秒)内无法完成处理。
  • ServiceTimeOut:比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20秒)内无法完成处理。
  • ContentProviderTimeout:ContentProvider在10S内无法完成处理。

ANR原因

从应用的角度上来讲,它的原因可以归结为以下几种:

  • 应用在主线程上非常缓慢地执行涉及 I/O 的操作。
  • 应用在主线程上进行长时间的计算。
  • 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
  • 主线程处于阻塞状态,等待子线程的长时间耗时操作完成。
  • 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。

如何解决问题

无论我们通过线上还是开发中发现了ANR问题,我们都需要思考怎么去解决。

1、修改主线程上耗时的代码

通过TraceView或者CPU Profiler可以找到应用中主线程忙碌时间超过5s的位置,然后把此处代码移到子线程操作。如一些网络操作、耗时计算等。

2、锁的竞争导致堵塞

如果主线程参与锁的竞争,有可能会导致主线程持续等待锁而造成堵塞的问题,从而引发ANR。所以最好还是避免主线程出现竞争锁的情况。

3、死锁

如果某资源被另一个线程所持有,而该线程又在等待主线程的资源,就会陷入死锁情况导致ANR。

4、广播接收器执行速度慢

如在广播接收器的onReceive()执行了长时间的耗时操作,就会可能导致ANR,所以应该把耗时操作放到子线程操作。

开发中检测ANR

StrictMode(严格模式)
开发中由于个人原因,多多少少都会可能写出造成ANR的代码,要想在开发中及时发现问题,可以使用StrictMode有助于您在开发应用时发现主线程上的意外 I/O 操作。

严苛模式是一个避免你不小心把网络或者IO操作放在UI线程操作的开发工具,从而实现避免ANR。使用严苛模式,系统检测出主线程违例的情况会做出相应的反应,如日志打印,屏幕闪烁(需要在开发者选项里面打开启用严格模式)等。

启用后台 ANR 对话框
只有在设备的开发者选项中启用了显示所有 ANR 时,Android 才会针对花费过长时间处理广播消息的应用显示 ANR 对话框。我们可以通过打开这个选项,在开发中及早发现相关问题。

TraceView
TraceView 是 Android SDK 中内置的一个工具,它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈,便于我们分析。(注意:Android Studio3.2之后已经弃用)

CPU Profiler
Android Studio3.2之后,CPU Profiler替代了TraceView,我们可以通过在通过记录应用交互过程获取相关方法执行顺序和耗时图,从而分析哪些方法耗时过长导致ANR。

线上ANR数据收集

在我们日常开发中经常遇到的ANR问题都是线上用户使用时发现的ANR问题,如果是我们开发中就已经发现,那是非常好解决的,只需要聚焦于新增代码块即可,但是如果是针对线上版本,那么我们就需要对线上数据进行监控,很多公司都会自主研发APM系统,或者是借用第三方的,抑或使用Google官方的,都是可以统计到ANR的数据。其实有时候发现站在巨人的肩膀上去做一些事情,也许效率会更高,以下是一些常用的ANR数据收集工具:

国外

  1. Google vitals:Google Play自带的性能统计工具。
  2. Firebase Crashlytics:Google Cloud Platform为应用开发者们提供的实时性能分析系统。
  3. ACRA:在Goolge Play上有2.68%实用率的ACRA库。

国内

  1. Bugly:腾讯Bugly项目组推出移动应用崩溃监控分析平台,提供崩溃、脚本错误、ANR(Android)/卡顿(iOS)问题等监控分析服务。
  2. xCrash:爱奇艺开源的一个性能监控的SDK。
  3. Umeng:国内移动统计分析服务平台,提供统计分析、更新、分享、推送等服务,其中,错误分析也是在统计分析的基础上添加。
  4. Testin:国内云测平台,服务升级后,提供云测,APM服务(包括性能监控,错误上报等),众测等服务。

Android 对 ANR 的监控机制

卡顿、ANR、死锁,线上如何监控? - 掘金

Android应用ANR源码分析--1.ANR触发机制了解 - 掘金

卡顿监控 - ANR 底层机制源码分析 - 掘金

  • 第一类:组件调度 以Service启动为例

Android 应用程序是通过消息来驱动的,Android 某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关。Android 的 ANR 监测方案也是一样,大部分就是利用了 Android 的消息机制。ANR触发流程其实可以比喻成埋炸弹拆炸弹的过程,

埋炸弹:

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //1、这里会发送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        //2、通知AMS创建服务
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } 
    ...
}

注释1的bumpServiceExecutingLocked内部调用scheduleServiceTimeoutLocked

    void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        ...
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        // 发送deley消息,前台服务是20s,后台服务是10s
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

注释2通知AMS启动服务之前,注释1处发送Handler延时消息,埋下炸弹,如果10s内(前台服务是20s)没人来拆炸弹,炸弹就会爆炸,即ActiveServices#serviceTimeout方法会被调用。该方法的主要工作发送 delay 消息 (SERVICE_TIMEOUT_MSG)。炸弹已埋下, 我们并不希望炸弹被引爆, 那么就需要在炸弹爆炸之前拆除炸弹。

拆炸弹:

-> ActivityThread#handleCreateService

    private void handleCreateService(CreateServiceData data) {
        try {
           ...
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
             //1、service onCreate调用
            service.onCreate();
            mServices.put(data.token, service);
            try {
            	//2、拆炸弹在这里
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    
    }

注释1,ServiceonCreate方法被调用,
注释2,调用AMS的serviceDoneExecuting方法,最终会调用到ActiveServices. serviceDoneExecutingLocked

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
              boolean finishing) {

...
	//移除delay消息
	mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
            
 }

可以看到,onCreate方法调用完之后,就会移除delay消息,炸弹被拆除。

引爆炸弹:

假设Service的onCreate执行超过10s,那么炸弹就会引爆,也就是ActiveServices#serviceTimeout方法会被调用,所有ANR,最终都会调用AppErrorsappNotResponding方法

    void serviceTimeout(ProcessRecord proc) {
   
    ...
    if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    ...
    }

主要流程如下:
1、写入event log
2、写入 main log
3、生成tracesFile
4、输出ANR logcat(控制台可以看到)
5、如果没有获取到tracesFile,会发一个SIGNAL_QUIT信号,这里看注释是会触发收集线程堆栈信息流程,写入traceFile
6、输出到drapbox
7、后台ANR,直接杀进程
8、错误报告
9、弹出ANR dialog,会调用 AppErrors#handleShowAnrUi方法。

ANR触发流程小结

ANR触发流程,可以比喻为埋炸弹和拆炸弹的过程,
以启动Service为例,Service的onCreate方法调用之前会使用Handler发送延时10s的消息,Service 的onCreate方法执行完,会把这个延时消息移除掉。
假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,也就是触发ANR,会收集cpu、堆栈等信息,弹ANR Dialog。

service、broadcast、provider 的ANR原理都是埋定时炸弹和拆炸弹原理,

  • 第二类:触摸事件 事件派发

InputEvent 的 ANR 与上图有些许不同,是在 Native 监控,但同样会堵塞主线程的消息队列,后面会讲到一部分监测场景。

Input ANR 时间区间便是指当前这次的事件 dispatch 过程中执行 findFocusedWindowTargetsLocked() 方法到下一次执行 resetANRTimeoutsLocked() 的时间区间。看源码有 5 个时机会 reset。都位于InputDispatcher.cpp 文件

二、应用 ANR 检测方案

目前流行的 ANR 检测方案有开源的 BlockCanary 、ANR-WatchDog、SafeLooper, 还有根据谷歌原生系统接口监测的方案:FileObserver。

BlockCanary

原理巧妙的利用了 Android 原生 Looper.loop 中的一个 log 打印逻辑。在 Message 消息分发前后,大部分的性能卡顿问题都是在这里发生的,监控这两个逻辑之间的时间差就可以得到当前主线程的卡顿状态,如果超时则获取 trace 信息并上报。

  • 优点
    灵活配置可监控常见 APP 应用性能也可作为一部分场景的 ANR 监测,并且可以准确定位 ANR 和耗时调用栈。

  • 缺点
    但 BlockCanary 应用在 ANR 监控上有几个比较严重的问题
    1、 谷歌已经明确标注 This must be in a local variable, in case a UI event sets the logger 这个 looger 对象是可以被更改的,已经有开发者遇到在使用 WebView 时 logger 被 set 为 Null 导致 BlockCanary 失效,只能让 BlockCanary 在 WebView 初始化之后调用 start。
    2、 如果 dispatchMessage 执行的非常久是无法触发 BlockCanary 的逻辑。
    3、 谷歌在 Looper 中还有一个标注

这里的 queue.next 是可能 block 的,场景就是文章开始提到的 InputEvent。此处 block 同样会触发 ANR,但 BlockCanary 同样无法适用的。一个例子可以验证下:

在 Activity 中重写 dispatchTouchEvent 和 dispatchKeyEvent,模拟耗时操作,弹出 ANR 告警,但 BlockCanary 没有任何反应,查看调用栈。

会看到 InputEvent 在 queue.next 中 block,不会继续执行 dispatchMessage,而是从 native 回调给 InputEventReceiver.dispatchInputEvent 处理分发,所以 BlockCanary 也就无法监控到这类 ANR。
4、 无法监控 CPU 资源紧张造成系统卡顿,无法响应的 ANR。

ANR-WatchDog

ANR-WatchDog 是参考 Android WatchDog 机制(com.android.server.WatchDog.java)起个单独线程向主线程发送一个变量 +1 操作,自我休眠自定义 ANR 的阈值,休眠过后判断变量是否 +1 完成,如果未完成则告警。

  • 优点
    1、 兼容性好,各个机型版本通用
    2、 无需修改 APP 逻辑代码,非侵入式
    3、 逻辑简单,性能影响不大

  • 缺点
    无法保证能捕捉所有 ANR,对阈值的设置直接影响捕获概率。如图:


    如果对线程的堵塞大于 10s 则设置监控阈值 5s 能捕获所有 ANR,堵塞时间在 5s~10s,则可能出现无法捕获场景。

FileObserver

有 ANR 的流程就可以知道/data/anr 文件夹的变化代表着 ANR 的发生,AMS 在 dumpStackTrace 方法中给了我们一些提示。当 ANR 发生的时候,我们是可以通过监听该文件的写入情况来判断是否发生了 ANR,看起来这是一个不错的时机。需要注意的是,所有应用发生 ANR 的时候都会进行回调,因此需要做一些过滤与判断,如包名、进程号等。ANR 生成的 trace 如图:

  • 优点
    1、基于原生接口调用,时机和内容准确。
    2、无性能问题实现简单

  • 缺点
    最大的困难是兼容性问题,这个方案受限于 Android 系统的 SELinux 机制,5.0 以后基本已经使低权限应用无法监听到 trace 文件了,但是可以在开发内测阶段通过 root 手机修改 app 对应的 te 文件提权进行监控。Android 7.1.1 版本的测试截图

    可以看到很多应用都尝试监听 ANR 文件,但是都被权限拒绝,属于不受信任应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值