第4章:常见ANR案例复现 - 权限对话框超时
权限申请还能搞出ANR?很多人不信。但它确实是线上ANR的“隐形杀手”。你想想看,系统弹个窗口需要和App进行binder通信。一旦通信卡住,5秒,就是ANR。
这一章,我就带你亲手复现这三个经典场景:高频弹窗、连续拒绝、对话框泄漏。
4.1 高频权限弹窗
我个人习惯把这种问题叫做“Toast式弹窗”。什么意思?就是代码里循环调用了requestPermission。
我在项目中遇到过某个第三方SDK,在初始化时同时请求了通讯录、存储、定位三个权限。好巧不巧,这三个权限弹窗瞬间弹出,直接把SystemUI的binder线程池给打满了。
复现代码其实很简单:
// 模拟高频权限请求
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往返。如果这个过程发生在主线程,卡顿是必然的。
曾经有个项目,因为连续拒绝权限导致InputDispatcher超时。日志里全是“Permission dialog suppressed”的warning。说白了,就是系统弹窗被App的代码逻辑给“玩坏了”。
4.3 对话框未及时关闭
这是最隐蔽的一种情况。
当用户点击“允许”或“拒绝”后,对话框需要关闭。但如果App的Activity因为某些原因(比如正在执行耗时操作)没有及时处理这个消息,对话框就会一直挂着。
我记得有个案例,是因为在权限回调中执行了数据库写入操作,导致主线程卡死。对话框等不到dismiss指令,最终触发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,你一眼就能看出问题出在哪。