Open Headunit UserExitHotspotPolicy:用户手动退出热点后的系统行为设计指南
【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunit
Open Headunit是一款把车机变成 Android Auto 主机的开源应用,而它的UserExitHotspotPolicy(用户退出热点策略)正是解决一个容易被忽略的问题:当你手动结束投屏会话时,车机上的 Wi-Fi 热点该如何处置。本文带你完整理解这套"退出即决策"的设计——它不是一行简单的"关热点"代码,而是一套兼顾权限、硬件能力与用户体验的状态机。
为什么断开连接还不够:问题的根源
想象这样一个场景:你在车机应用里点了"断开",会话结束了,但手机其实并没有离开 Wi-Fi 网络。
源码注释里讲得很直白(见 UserExitHotspotPolicy.kt):关闭 AAP 套接字、发送ByeByeRequest并不会让手机脱离网络。手机仍然"挂"在热点上,而 Android Auto 会不断重试无线设置,直到把自己限流为止——因为在手机看来,是"车在中途跑路了"。
这个应用能动的唯一杠杆就是让网络本身消失:把车机托管的那个软 AP(soft AP,即 Wi-Fi 热点)关掉,手机自然会被甩出网络。
决策逻辑速览:三种退出动作
策略的核心入口是UserExitHotspotPolicy.onUserExit(),它根据当前连接配置返回三种动作之一:
| 动作 | 含义 | 典型场景 |
|---|---|---|
NONE | 不关热点——这条路不归它管 | 走 WiFi Direct(P2P)组网,由别处拆除 |
DISABLE | 把车机托管的热点关掉,手机被真正踢出网络 | 用户已授权应用管理热点 |
WARN_LEFT_UP | 保持热点不动,只记录警告说明代价 | 热点是用户手动开的,应用没有权限代管 |
判断顺序(对应 UserExitHotspotPolicy.kt 第 53-65 行):
- 先问归属:当前模式(Native AA / Helper)是否真的把手机放在了车机自托管的热点上?不是 →
NONE。这一问保证了它与负责 WiFi Direct 的WifiModePolicy是"互补"关系——同一次断连,两边有且仅有一边会说"是",避免同时拆除 P2P 组和热点。 - 再问硬件记忆:这台设备是否已经被证明"关了热点就再也开不回来"(
teardownProvenUnsafe)?是 →WARN_LEFT_UP,绝不再冒险。 - 最后问授权:用户是否通过"自动开启热点"(
autoEnableHotspot)设置把热点的开关权交给了应用?是 →DISABLE;否 →WARN_LEFT_UP。
💡 关键设计哲学:门槛是"归属权"而不是"症状"。P2P 组是应用为会话创建的,随时可拆;而软热点通常是用户自己手动打开的,关掉它再想恢复在很多未 root 的车机上根本做不到。把别人的热点关了却恢复不了,比连接卡顿更糟糕——卡顿用户可以自己去清掉。
DISABLE 的执行细节:先拆完,再"重启"而非"关闭"
决策只是第一步,真正执行在 AapService.kt 的断开流程里(第 1555-1597 行),有两个讲究:
- 顺序严格:必须等
commManager.awaitDisconnectComplete()确认 ByeBye 握手与套接字关闭全部完成,才能动热点,否则拆除过程中新来的连接会被误拒。 - 重启而非单纯关闭:如果只把热点关掉,下一次连接就要付上完整的开机代价——源码实测某台拒绝
setSoftApConfiguration()的车机要耗掉约 20 秒。所以应用选择"重启热点":热点消失的一瞬间把手机踢出网络,然后立即再拉起来,这 20 秒的开销就花在用户已经走开的当下,而不是下次的等待时间里。
自我学习机制:teardownProvenUnsafe标志
这是整个设计里最"反直觉"也最精妙的部分。有些无线芯片(源码中记录了一台 UNISOC 车机)热点一旦被应用关掉,由于 hostapd 的频道扫描与驱动重建接口发生竞争,就再也开不回来——包括本该作为退路的"自动开启"也失败。
在这种设备上,每次手动退出都会"烧掉"一个热点,代价极高。于是应用采用了一次性的探测策略:
- 第一次照常执行
DISABLE,若HotspotManager.restart()失败,就把 Settings.kt 中的hotspotTeardownProvenUnsafe置为true并持久化保存; - 因为这个属性属于硬件、运行间不会改变,持久化后每次退出都直接走
WARN_LEFT_UP,用户只在日志里收到一次友好提示:"以后结束会话时,请从手机端的 Android Auto 通知栏退出,或手动关掉热点"。
用"一个热点"的代价换来了"每次会话一个热点"的长期损失,这是典型的用最少试错换取长期正确。
用户视角:这个开关在哪里
在 SettingsFragment.kt 中,当你选择 Native AA 的"热点"传输路线时,设置页会自动出现热点管理开关(autoEnableHotspot)。这个开关在两个地方生效:
- 连接建立前:找不到热点时由应用自动开启;
- 用户手动退出后:由
UserExitHotspotPolicy依据它决定是否拆除网络。
也就是说:开启 = 把热点的完整生命周期交给应用;关闭 = 应用只读不动,退出时仅提示。
测试如何守住"互补"契约
单元测试 UserExitHotspotPolicyTest.kt 覆盖了两类关键性质:
- 永不重叠:遍历所有"模式 × 策略 × 传输"组合,断言
usesHeadUnitHotspot与usesWifiDirect不会同时为真(第 137-153 行),否则同一次断连会双重拆除; - 永不落空:所有"车机托管手机网络"的路线,必须至少被两个策略之一认领(第 163-178 行),否则网络会一直挂着、手机永远无法离开。
后者尤其重要,因为"两边都不认领"这种错误不会报错、只会在真机上表现为"断开后手机还连着热点",测试就是为了让这种静默失败无处藏身。
小结
UserExitHotspotPolicy展示了开源项目里少见的工程克制:
- 按归属权决策,而不是按症状硬关;
- 用一次试错学习硬件能力,并把结果持久化;
- 拆除顺序严格,先握手后拆网,重启代替关闭以摊平延迟;
- 用穷举测试锁死"恰好一个策略负责"的契约。
如果你也在做车机投屏类应用,这套"退出即决策"的模式(权限 → 能力 → 授权三层判断 + 失败记忆)非常值得借鉴。
核心源码索引
- 策略本体:connection/wifi/UserExitHotspotPolicy.kt
- 执行流程:aap/AapService.kt
- 不安全标志:utils/Settings.kt
- 设置入口:main/SettingsFragment.kt
- 配套测试:connection/wifi/UserExitHotspotPolicyTest.kt
【免费下载链接】open-headunitHeadunit App for displaying Android Auto项目地址: https://gitcode.com/GitHub_Trending/he/open-headunit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考