【Android】制造一个ANR并进行简单分析

发布于:2025-09-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

常见ANR的场景简述

  1. 输入事件超时(Input dispatching timed out)
    描述:这是用户最能直接感知的ANR,当用户点击屏幕、滑动列表、按下按键等输入事件发生后,如果应用的主线程在5秒内没有处理完这个事件,就会触发ANR。
    根本原因:主线程正在执行耗时操作。例如网络请求,大量文件读写操作,复杂计算,主线程等待锁,主线程的 IPC 调用(Binder调用)被阻塞,对方处理缓慢等。

  2. 服务生命周期超时(Timeout executing service)
    描述:当一个 Service 在其生命周期函数(如 onCreate(), onStartCommand(), onBind(), onUnbind(), onRebind())中执行了太多工作,超过了20秒,就会触发ANR。
    根本原因:将耗时逻辑放在了Service的生命周期方法中。

  3. 广播接收器超时(Timeout of broadcast Broadcast Record)
    描述:当一个 BroadcastReceiver 在其 onReceive() 方法中执行了太多工作,超过了10秒(前台广播)或60秒(后台广播,如 ACTION_BOOT_COMPLETED),就会触发ANR。
    重要特点:onReceive() 方法本身就是在主线程执行的!
    根本原因:在 onReceive() 中执行了耗时操作。例如在接收到网络变化、电量变化的广播后,直接在其中进行网络请求或大量逻辑处理。

  4. 内容提供者超时(Timeout publishing content providers)
    描述:当一个 ContentProvider 在执行查询(query)、插入(insert)、更新(update)、删除(delete)等操作时耗时过长,也会触发ANR。虽然官方没有明确给出超时时间,但它同样运行在调用者的线程(如果调用者是主线程,那就在主线程运行)。
    根本原因:在ContentProvider的方法中执行了低效的数据库操作或耗时逻辑,并且调用方是主线程。

  5. 其他可能导致或加剧ANR的场景
    CPU 被抢占:
    应用进程或系统处于高负载状态。如果CPU被其他进程(或你应用内的其他线程)占满,主线程即使只做很少的工作,也可能因为抢不到CPU时间片而无法及时响应。
    常见于:应用在执行大量后台任务、手机正在发热降频、多个应用在后台激烈竞争资源。
    主线程的 IPC 调用(Binder Call):
    主线程通过Binder机制调用系统服务或其他进程的方法(如 AccountManager.blockingGetAuthToken(), ContentResolver.query()),如果对方处理缓慢,主线程就会被阻塞等待。
    内存不足与频繁 GC:
    当系统内存严重不足时,垃圾回收(GC)会变得非常频繁。而 GC 执行时会暂停所有线程(包括主线程)。如果主线程因为频繁的GC而无法得到执行,也可能间接导致ANR。
    厂商定制系统(ROM)的后台限制:
    一些国内厂商的Android系统为了省电,会非常激进地限制后台应用的活动(如冻结进程、限制网络、限制唤醒),这可能会导致你的Service或广播接收器被延迟执行,从而在恢复时发生ANR。

制造一个ANR

新建一个项目MyApplication,并且新建一个MainActivity,设置一个按钮的点击事件:

    private fun setClickListener() {
        actionButton?.setOnClickListener {
            Thread.sleep(10 * 1000)
            Toast.makeText(this, "点击了按钮", Toast.LENGTH_LONG).show()
        }
    }

在点击事件中,使主线程sleep10s(理论上大于5s就行)。
最后,连续点击按钮两次以触发ANR。

ANR日志的获取

anr触发后立即在AS终端执行以下命令获取日志:

adb bugreport

执行后,所有的日志文件都会生成到项目根目录:
在这里插入图片描述
解压后文件结构如下(不同Android版本可能不同):
在这里插入图片描述
anr目录下的为trace文件,包含ANR发生时的一些线程信息,以及函数调用栈。
下面的文件是Locat日志信息,包含ANR原因、进程信息以及CPU的使用情况。

ANR分析:

首先,查看Locat日志,定位ANR类型。一般通过搜索“ANR in”来过滤日志,找到上次ANR发生时间点的日志:
在这里插入图片描述
根据报错Input dispatching timed out,我们知道是由于输入事件没有得到及时处理,导致出现了ANR。且CPU目前使用情况正常,也没有io操作等待。

接下来,查看trace文件,看一下具体的函数调用栈:
在这里插入图片描述
根据调用栈,我们可以看到问题就发生在我们的onClick方法中,且当前主线程处于sleep的状态。
最后,结合具体代码逻辑,得出结论:
用户点击按钮时,主线程处于sleep状态,导致点击事件没有得到及时的处理,最终导致了ANR。


网站公告

今日签到

点亮在社区的每一天
去签到