22、SurfaceFlinger死锁与ANR:Transaction超时、BufferQueue死锁、dump分析技巧
SurfaceFlinger 死锁,这词儿听着就让人头疼。我在一线摸爬滚打这些年,没少跟它打交道。说白了,它就是 Android 图形系统的“心脏骤停”。一旦挂了,整个屏幕就像被按了暂停键,点哪儿都没反应,紧接着就是 ANR 弹窗。
今天咱们就把它扒开来看。我会结合我踩过的坑,聊聊 Transaction 超时、BufferQueue 死锁,还有怎么用 dump 快速定位问题。
Transaction 超时:最常见的“假死”
先说说 Transaction 超时。这玩意儿我遇到过太多次了。应用层调个 setVisibility 或者 setAlpha,底层就会发起一个 Transaction。正常情况下,SurfaceFlinger 会很快处理完。
但有时候,它就像卡住了一样。为什么?
我个人的习惯是,先看 logcat 里有没有这个关键字:
W/SurfaceFlinger( 1234): Timed out waiting for previous transaction to complete
看到这个,基本就实锤了。Transaction 超时意味着 SurfaceFlinger 的主线程被某个耗时操作阻塞了。它没法处理新的请求,应用层那边等不到回应,自然就 ANR 了。
核心原因:SurfaceFlinger 主线程被阻塞,无法处理新的 Transaction。
我记得有一次,一个第三方应用疯狂创建和销毁 Surface。每次操作都发一个 Transaction,而且频率极高。SurfaceFlinger 处理不过来,队列越积越长,最终超时。嗯,这里要注意,不是所有超时都是 SurfaceFlinger 的锅,应用层“刷屏”也是常见诱因。
BufferQueue 死锁:真正的“死结”
Transaction 超时还算温和,至少能恢复。BufferQueue 死锁就麻烦了,它是真正的死结。
BufferQueue 是生产者和消费者之间的缓冲区队列。生产者(比如 App 的渲染线程)往里面放 Buffer,消费者(SurfaceFlinger)从里面取 Buffer 去合成显示。
死锁怎么发生的?我画个图你就明白了。
你看,当所有 Buffer 都被生产者“借走”了,但生产者还没提交。消费者这边呢,一个 Buffer 都拿不到,只能干等。生产者想继续干活,但需要消费者先释放一个 Buffer 回来。这就成了死循环。
我曾经在一个视频播放器项目里遇到过。播放器在快速切换分辨率时,旧 Buffer 没及时释放,新 Buffer 又申请不到。SurfaceFlinger 那边也卡住了,整个系统 UI 都冻住。最后只能靠看门狗重启 SurfaceFlinger。
避坑指南:我曾经在调试一个相机预览卡死问题时,发现是 HAL 层没有及时归还 Buffer。应用层以为 Buffer 还在用,SurfaceFlinger 以为应用层会释放。三方互相等,死锁就形成了。检查 BufferQueue 状态时,一定要看每个 Buffer 的 state 字段。
dump 分析技巧:我的“三板斧”
遇到死锁或超时,别慌。SurfaceFlinger 提供了很强大的 dump 机制。我个人习惯用这三招:
第一招:抓取 SurfaceFlinger dump
直接 adb 命令:
adb shell dumpsys SurfaceFlinger
这个命令会输出海量信息。别被吓到,我们只看关键部分。
第二招:定位 Transaction 状态
在 dump 输出里,搜索 Transaction 或 Pending。你会看到类似这样的内容:
Pending transactions:
- handle: 0x7a12345678, state: queued, age: 3500ms
- handle: 0x7a87654321, state: processing, age: 4200ms
看到 age 超过 3000ms 的,基本就是超时的 Transaction。我一般会先看 state: queued 的,它们还在排队,说明主线程忙不过来。
小技巧:如果 queued 的 Transaction 很多,而且 age 都很大,那大概率是 SurfaceFlinger 主线程被某个同步操作卡住了。这时候去查 Binder 调用栈,看看它在等谁。
第三招:检查 BufferQueue 状态
在 dump 里搜索 BufferQueue 或 Layer 名字。你会看到每个 Layer 对应的 BufferQueue 详情:
Layer 'com.example.app/com.example.app.MainActivity'
BufferQueue state:
mQueue: [0] [1] [2]
mActiveBuffers: 3
mDequeueBufferCannotBlock: true
mFrameNumber: 128
mLastFrameNumber: 125
关键看 mDequeueBufferCannotBlock。如果它是 true,说明生产者已经无法再申请新 Buffer 了,所有 Buffer 都在外面。这时候结合 mActiveBuffers 和 mQueue 里的数量,就能判断是不是 Buffer 泄漏了。
我一般还会看 mLastFrameNumber 和 mFrameNumber 的差值。如果差值很大,说明消费者(SurfaceFlinger)处理速度跟不上生产者(App)。
实战:一次典型的 ANR 分析
咱们走一遍完整流程。假设你遇到了 ANR,logcat 里有 SurfaceFlinger 超时的日志。
- 第一步:抓取
dumpsys SurfaceFlinger,保存到文件。 - 第二步:搜索
Timed out或Pending transactions。找到超时的 Transaction 对应的 Layer。 - 第三步:搜索那个 Layer 的名字,查看它的 BufferQueue 状态。
- 第四步:如果 BufferQueue 里
mDequeueBufferCannotBlock为 true,且mActiveBuffers等于最大 Buffer 数,那就是 Buffer 死锁。 - 第五步:如果 BufferQueue 正常,但 Transaction 仍然超时,那问题可能在 SurfaceFlinger 主线程本身。抓一个
systrace看看它在执行什么。
记住:SurfaceFlinger 死锁和 ANR 的根因,要么是 Transaction 处理不过来,要么是 BufferQueue 卡死。dump 就是你的“照妖镜”,多练几次就能一眼看出问题。
嗯,今天就聊到这儿。这些技巧我在项目里反复用过,希望能帮你少走弯路。下次遇到类似问题,别急着重启,先抓个 dump 看看。