第4章:常见ANR案例复现 - 权限对话框超时

权限申请还能搞出ANR?很多人不信。但它确实是线上ANR的“隐形杀手”。你想想看,系统弹个窗口需要和App进行binder通信。一旦通信卡住,5秒,就是ANR。

这一章,我就带你亲手复现这三个经典场景:高频弹窗、连续拒绝、对话框泄漏。

权限请求与ANR触发流程 App 发起请求 PMS 处理请求 SystemUI 弹窗 用户拒绝/忽略 对话框泄漏 Binder调用超时 ANR (5s) 核心指标:App -> Framework -> 用户,整条链路需在5秒内走完。 任何节点阻塞或泄漏都直接导致ANR。

4.1 高频权限弹窗

我个人习惯把这种问题叫做“Toast式弹窗”。什么意思?就是代码里循环调用了requestPermission。

我在项目中遇到过某个第三方SDK,在初始化时同时请求了通讯录、存储、定位三个权限。好巧不巧,这三个权限弹窗瞬间弹出,直接把SystemUI的binder线程池给打满了。

避坑指南: 千万不要在onResume或onStart中连续发起权限请求。

复现代码其实很简单:


// 模拟高频权限请求
for (int i = 0; i < 10; i++) {
    ActivityCompat.requestPermissions(
        this, 
        new String[]{Manifest.permission.CAMERA}, 
        REQUEST_CODE_CAMERA + i
    );
}

这段代码跑在Android 9以下机型上,ANR概率极高。为什么?因为弹窗是串行处理的,多个弹窗排队,后面的弹窗在等待过程中,如果主线程被其他任务占用,超时立刻就来了。

4.2 连续授权拒绝

用户连续拒绝权限,系统会标记为“REJECTED”。但如果App没有处理这个状态,就会陷入“申请 -> 拒绝 -> 申请 -> 拒绝”的死循环。

你想想看,每一次申请弹窗都意味着一次完整的binder事务。连续拒绝10次,就是10次binder往返。如果这个过程发生在主线程,卡顿是必然的。

检查方法: 查看onRequestPermissionsResult回调中的grantResults数组。如果全是DENIED,就不要再次申请了,引导用户去设置页。

曾经有个项目,因为连续拒绝权限导致InputDispatcher超时。日志里全是“Permission dialog suppressed”的warning。说白了,就是系统弹窗被App的代码逻辑给“玩坏了”。

4.3 对话框未及时关闭

这是最隐蔽的一种情况。

当用户点击“允许”或“拒绝”后,对话框需要关闭。但如果App的Activity因为某些原因(比如正在执行耗时操作)没有及时处理这个消息,对话框就会一直挂着。

我记得有个案例,是因为在权限回调中执行了数据库写入操作,导致主线程卡死。对话框等不到dismiss指令,最终触发ANR。

ANR关键日志特征:
“Window freeze timeout”
“Waiting for permission callback for package [xxx]”

4.4 三种场景的ANR表现对比

场景 ANR类型 超时时间 日志关键字
高频弹窗 InputDispatching Timeout 5s Input dispatch timed out
连续拒绝 Binder Transaction Failed 5s Transaction stuck / Suppressed dialog
对话框泄漏 Broadcast Timeout / Service Timeout 10s / 20s Window freeze timeout

遇到这些问题,我建议你首先抓取/data/anr/traces.txt。重点关注“Watched for”和“Permission”相关的线程堆栈。基本上,权限导致的ANR堆栈都会指向PermissionManagerService或者SystemUI的Dialog。

这一章的内容不多,但很实用。把这些场景记住了,遇到权限ANR,你一眼就能看出问题出在哪。