news 2026/9/18 16:32:58

Open Headunit ProjectionWatchdogPolicy投屏看门狗:画面假死如何被检测与重启

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Open Headunit ProjectionWatchdogPolicy投屏看门狗:画面假死如何被检测与重启

Open Headunit ProjectionWatchdogPolicy投屏看门狗:画面假死如何被检测与重启

【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunit

Open Headunit(Headunit Revived)是一个把 Android 平板变成 Android Auto 中控的开源投屏应用。它的核心组件投屏看门狗 ProjectionWatchdogPolicy,专门负责检测投屏画面"假死"(黑屏、卡住不动)并自动向手机重新索要视频流,让你开车时不再手动折腾。

什么是投屏"假死"?先认识这种故障

用 Android Auto 投屏时,你可能遇到过这类情况:

  • 📺 画面突然卡住不动,或变黑屏,但音乐还在放
  • 📵 导航界面定格在某一张图,怎么点都没反应
  • 🔄 需要"杀 App 重开"才能恢复,甚至直接重启车机

这就是"假死":手机和车机之间的连接可能还活着,但视频流停了,画面再也不刷新。对开车的人来说,一个不再更新的黑屏比没连接更危险。

Open Headunit 的答案是一个常驻的"看门狗":每隔 2 秒巡逻一次,发现画面异常就自动执行恢复动作。核心决策逻辑集中在 ProjectionWatchdogPolicy.kt 这个文件里。

看门狗如何检测:每秒都在"数心跳"

投屏看门狗并不是简单的"超时重连",它把"画面停没停"和"连接断没断"当成两个独立问题来判断。这是整个设计最聪明的地方。

心跳一:最后渲染帧距今多久

看门狗会记录解码器最后一次把画面渲染到屏幕上的时间戳。如果距今超过10 秒(常量FRAME_GAP_MS),就判定"画面已停"。

心跳二:整条链路静默了多久

这里有个 Android Auto 的"坑":当屏幕上没有任何内容在动时(比如暂停的播放器),手机会彻底停止发送视频,但连接依然健康。如果只看"多久没收到帧",看门狗会把正常待机误报成断线——这正是历史上 issue #852 的由来:音乐暂停 15~30 秒,画面就被盖上"连接丢失"的遮罩。

所以第二个心跳测量的是"手机在任何通道上最后一次发消息距今多久"(LINK_QUIET_MS,10 秒)。只有两个条件同时满足才会向用户显示"正在重连":

现象帧间隔 > 10s链路静默 > 10s看门狗的结论
音乐暂停待机正常,静默恢复
视频流中途死亡判定断连,提示用户
画面正常播放什么都不做

判断函数 shouldShowReconnecting 就一行:两个阈值必须超过才报警。

自动重启恢复:黑屏不用等,主动"要画面"

检测出来只是第一步,真正的价值在于恢复。

重新索要视频焦点

当"画面已停"被确认时,看门狗会主动向手机发送一条VideoFocusEvent(gain = true, unsolicited = true)消息——相当于对手机说:"请把视频再发给我。"这条请求受到节流保护(见 VideoRecoveryPolicy),避免每个 2 秒的 tick 都轰炸手机。

注意一个细节:恢复动作只看画面停没停,不看遮罩显没显示。因为"看起来像待机"的卡顿流和真正停掉的流长得很像,如果恢复动作绑在提示遮罩上,恰好会把该救的流漏掉。

显示器卡死:重建投屏视图

另一类假死出在车机端:手机还在发视频,但某些车机(尤其是 MediaTek 芯片的 GLES 模式)的显示消费端"罢工"了,画面冻结(常见于定格在 Android Auto 启动 Logo 上),音频却正常——这是 issue #650。

看门狗为此设了双重探测:

  1. 完全冻结:连续5 秒DISPLAY_FREEZE_MS)没有任何画面被绘制
  2. 吞吐崩溃:滑动窗口(10 秒)内出现 ≥10 个异常长帧——MediaTek 上表现为掉到 2~5fps,单纯"5 秒无帧"检查抓不到

触发后,看门狗会自动重建投屏视图(等价于手动"Home + 重新打开")。如果重建后仍反复卡顿,会升级到 SurfaceView 后端(本会话内生效),最多尝试 4 次,避免无限重建。这段逻辑在 AapProjectionActivity.kt 的maybeRecoverFromDisplayStall中实现。

第一帧催促:黑屏起步也能被救

新会话刚开始、画面还没出来时,还有一条"首帧催促循环":只要会话存活、surface 已就绪、但画面上还没有可信的画面(灰屏不算),看门狗就每隔 1.5 秒催一次手机发关键帧,直到画面出现或升级策略接手(见 shouldNudgeForFirstFrame)。

看门狗自己也"死"过:一段真实修复史

值得讲一个小故事,因为它解释了为什么看门狗被单独抽成一个 Policy 类。

早期的内联检查只认HandshakeComplete这一种连接状态。但握手完成只是视频开始前的短暂窗口,整个行驶过程的稳定状态是TransportStarted。结果看门狗在每个会话的第一个 tick 就"判自己死亡"、不再自我调度——挂在它身上的所有恢复机制(重连遮罩、显示卡顿恢复、渲染器确认提示)在正常会话里全都不可达。视频流中途挂掉后,屏幕就一直黑着,再也没人开口要

复活看门狗后它又立刻犯了另一个毛病:只凭"帧间隔"就把 Android Auto 的正常待机叫成断线(issue #852)。最终方案是把两个决策拆开:画面停值一次恢复请求,但不一定值得弹遮罩。这些边界条件都被逐条钉死在单元测试里:

  • 状态表测试:ProjectionWatchdogPolicyTest.kt
  • 链路静默监测(诊断用):LinkGapMonitor.kt

关键参数一览表

常量数值作用
WATCHDOG_TICK_MS2 秒看门狗巡检周期
FRAME_GAP_MS10 秒画面停摆判定阈值
LINK_QUIET_MS10 秒"链路静默"判定阈值
DISPLAY_FREEZE_MS5 秒显示端冻结判定阈值
LONG_FRAME_WINDOW_MS10 秒长帧滑动统计窗口

所有数值均可在 ProjectionWatchdogPolicy.kt 中查到,每个常量旁都有注释说明它从哪次真实故障中得来。

投屏卡死时你应该做什么

大多数情况下什么都不用做——看门狗会在 10 秒内自行恢复,你可能只会看到短暂黑屏或一次"正在重连"提示。如果问题持续:

  1. ⚙️ 在设置中切换视图模式(TextureView / SurfaceView / GLES),看门狗升级失败时的兜底就是切到 SurfaceView
  2. 📄 通过应用的日志导出功能导出日志(LogExporter.kt),日志里的看门狗行会写明"画面空闲了 Xms,但链路 Yms 前还在说话",方便定位
  3. 🔁 极端情况下退出重进投屏界面,看门狗会在每次进入时重新武装

小结

Open Headunit 的投屏看门狗把一个容易"看一次算一次"的玄学问题,变成了一组可测试、可追溯的规则:

  • 检测:帧间隔 + 链路静默双心跳,区分"真断线"和"正常待机"
  • 恢复:主动索要视频焦点、重建投屏视图、升级渲染后端,层层递进
  • 克制:节流、次数上限、冷却期,防止看门狗自己变成黑屏的制造者

下次投屏黑屏时,不妨多等 10 秒——它很可能已经在路上了。🚗

【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 16:24:24

Win10+CUDA环境配置:硬件-驱动-编译器协同原理与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 16:24:21

Flutter列表跳动问题排查与修复:身份、位置、尺寸对齐指南

你正在调试一个 Flutter 项目,列表在底部加载新数据后瞬间“跳”回顶部;你只是往聊天列表里插一条新消息,结果已经读过的历史内容像被推了一把;你又怀疑是图片加载问题,于是把网络图全部改成固定高度,滚到一…

作者头像 李华