AOSP vs 小米车机(MiCar)蓝牙已配对列表设计对比

2026-08-11 · 锚点:MANNPROB-3187 / MANNPROB-3205 · 配套知识库:30-模块流程篇/34-已配对列表摘要状态机-AOSP与MiCar.md v2(2026-09-07)增量锚点:MIICOSBUG-2471 —— 双源结构被证出第二种失败模式(同设备跨 profile 覆写,§2.3/§4/§5);补 AOSP 被忽视的第二支柱「后台线程算摘要」(§2.4)。 结论先行:两套实现在”已连接/正在连接”上功能等价;差异集中在”空闲态怎么呈现”(AOSP 空白 vs MiCar「已保存」)和”正在连接的判据结构”(AOSP 状态机单源 vs MiCar 状态机×旁路开关双源)。后者是 3187/3205 两个 bug 的结构性土壤。


1. 设计哲学对比

维度AOSP 原生MiCar 车机
空闲态呈现哲学静默:已配对未连接 = 无摘要,行只显示设备名显式:给空闲态一个明确标签「已保存」
状态推导哲学单源:摘要 100% 从 profile 状态机实时推导,无缓存无旁路双源:状态机(isBusy/isConnected)× Controller 旁路(mIsConnectingState)联合判据
过渡态粒度四态全呈现:连接中/断开中是两个文案压缩为三态:断开中不呈现,isBusy 混含 DISCONNECTING
交互保护无防抖无守卫(置灰即时,靠状态机天然正确)300ms 置灰防抖 + isBusy 点击守卫(PS3)
连接失败反馈无 toast(摘要停在断开文案)mStartToConnect 失败 toast
信息量电量/使用中(active)变体丰富精简三态 + 排序置顶 + 蓝色选中态
重计算线程模型后台线程算摘要+主线程贴结果(Settings 侧 ThreadUtils.postOnBackgroundThread)主线程直接算(139360 主线程 binder 风暴的土壤)

一句话:AOSP 追求”状态机的忠实投影,不该说话时沉默”;MiCar 追求”每个状态都有明确标签”,为此引入了旁路状态位——信息量的提升是以状态双源化为代价的,而双源必然产生同步窗口,所有相关 bug 都长在这个窗口上。

2. 状态模型逐项对比

2.1 状态集合

flowchart LR
    subgraph AOSP 四态+空白
        A1[空白 null]  A2[正在连接…]  A3[正在断开…]  A4[已连接]  A5[正在配对…]
    end
    subgraph MiCar 三态
        M1[已保存]  M2[正在连接...]  M3[已连接]
    end
协议栈真实状态AOSP 显示MiCar 显示评价
已配对,全空闲空白已保存MiCar 信息更明确;代价是栈层回连延迟被可视化
任一 profile CONNECTING正在连接…正在连接…(+动画)等价;MiCar 多动画但多旁路风险
任一 profile DISCONNECTING正在断开…已连接→已保存 之间跳变(3205 前)/ 已保存(3205 后)AOSP 更精确;MiCar 有意压缩,3205 修掉了跳变
任一 profile CONNECTED已连接(+电量/使用中)已连接(+蓝色+置顶)等价
BOND_BONDING正在配对…(配对中行走未配对列表)场景不同不比较

2.2 判据结构

AOSP 判据MiCar 判据结构性风险
已连接任一 profile CONNECTED(单源实时)同左(单源实时)两边都安全
正在连接任一 profile CONNECTING(单源实时)isBusy && mIsConnectingState(双源联合)MiCar 独有:双源可不同步
空闲(兜底 null)else → 已保存(兜底)MiCar 的 else 承接所有双源失败

mIsConnectingState 旁路的三个写入点(MiCar 独有):

  1. preference 重建时默认 false(Adapter ON → innerUpdateState removeAll+re-add)
  2. Controller setConnecting(state==CONNECTING)(profile 回调驱动)
  3. !isBusy 时自动清 false(refreshDeviceUi kt:75)

旁路存在的理由:MiCar 把 DISCONNECTING 也纳入 isBusy,若只用 isBusy 单条件,断开时会误显示「正在连接」——旁路开关用来区分”连接中”与”断开中”。AOSP 不需要这个旁路,因为它给断开中一个独立文案

2.3 双源结构的两种失败模式(v2 增补)

失败模式机理状态
①跨设备写入污染(3187)Controller 回调循环无设备过滤,A 设备的回调清掉 B 设备行的旁路开关✅ 已修(362953 设备匹配过滤)
同设备跨 profile 覆写(2471)过滤到设备后,setConnecting(state==CONNECTING) 仍按”最近一次回调”整体覆写单布尔;同设备 HFP(16)/A2DP(11)/MAP(18)/PBAP(17) 并行连接时,某 profile 真实首试失败 DISCONNECTED(栈内 70100ms 后重试)把 flag 清零,其余 profile 仍 CONNECTING 撑住 isBusy=true → 摘要回落「已保存」160200ms未修,dev/dev_a17_0610/rls-xcd-x-ee-2608-2609 全带

写入点可枚举不等于写入安全:§6 建议 2 说”旁路写入点必须可枚举”,2471 证明即使只有 setConnecting 一个主动写入点,只要写入粒度(设备级布尔)小于事件粒度(profile 级回调),覆写污染就是结构性的。AOSP 免疫不是因为写入点少,而是根本没有旁路——聚合在读取侧现算。

2.4 AOSP 的第二支柱:后台线程算摘要(v2 增补)

AOSP 状态单源解决”算得对”,但 getConnectionSummary() 是逐 profile 活 binder 调用(profile.getConnectionStatus(mDevice) = mService.getConnectionState(device)),手机 Settings 怎么扛住 binder 成本?答案是换线程,不是缓存状态:

// AOSP packages/apps/Settings BluetoothDevicePreference.onPreferenceAttributesChanged()
ThreadUtils.postOnBackgroundThread(() -> {
    String connectionSummary = getConnectionSummary();  // 后台线程:binder 链随便调
    boolean isBusy = mCachedDevice.isBusy();
    ...
    ThreadUtils.postOnMainThread(() -> {
        setSummary(connectionSummary);
        setEnabled(!isBusy);
    });
});

对比 MiCar:refreshDeviceUi()/getDeviceSummary() 全部主线程直算,遇 60+ 设备排序刷新风暴即成 N801PROB-139360 的主线程 binder 风暴病灶。AOSP 两大支柱:读取侧聚合(不算错)× 后台线程计算(不算卡);MiCar 两个都没抄到 —— 705515c51 自造旁路开关破坏了前者,主线程直算放弃了后者。

3. 时序行为对比(关开蓝牙自动回连,实测)

sequenceDiagram
    autonumber
    participant U as 用户
    participant ST as 协议栈
    participant AOSP as AOSP UI
    participant MC as MiCar UI
    U->>ST: 关蓝牙
    ST-->>AOSP: 已连接→(正在断开…)→空白
    ST-->>MC: 已连接→已保存
    U->>ST: 开蓝牙
    Note over ST: 建链耗时(广播实测 40~77ms 不慢;HFP 1.2~3.8s/台,双设备串行 4~5.5s;A14 更快→栈侧问题)
    ST-->>AOSP: 空白(无感)
    ST-->>MC: 已保存(可见!用户感知"闪")
    ST->>ST: 首个 profile CONNECTING
    ST-->>AOSP: 正在连接…
    ST-->>MC: 正在连接...(旁路置位)
    ST->>ST: 首个 profile CONNECTED
    ST-->>AOSP: 已连接
    ST-->>MC: 已连接(置顶+蓝色)
阶段AOSPMiCar差异本质
OFF 后空白已保存文案策略
ON → 栈开始连(广播 4077ms 到;建链 1.23.8s/台)空白已保存唯一被用户感知的差异窗口
连接中正在连接…正在连接…+动画等价
回连中途他设备回调无影响(无旁路)3187 修复前:中途回退已保存;PS3 后:无影响MiCar 旁路风险之一,已修(362953)
连接途中同设备他 profile 失败回调无影响(读取侧逐 profile 聚合,单 profile DISCONNECTED 不影响其余 CONNECTING 的判定)回落「已保存」160~200ms(2471 实证:07-20 iPhone 两次、07-07 K90 一次)旁路风险之二,未修
断开中正在断开…已保存(3205 前:置灰波动)MiCar 有意压缩粒度

4. 缺陷史对照(为什么 bug 只长在 MiCar 侧)

BugMiCar 机理AOSP 免疫的结构性原因
MANNPROB-3187(双设备回连先已保存)Controller 回调循环无设备过滤,旁路开关被跨设备清零,双条件失败落 else无旁路、无「已保存」、无 Controller 全量刷新
MANNPROB-3205(断开采灰波动)isBusy 含 DISCONNECTING 误驱动画+setEnabled(!isBusy) 翻转断开有独立文案与状态呈现,不需要 isBusy 混用驱动视觉
「已保存」等待窗口(测试误报为 bug)「已保存」else 兜底把栈层建链耗时(串行 4~5.5s)可视化空闲=空白,等待窗口无感
MIICOSBUG-2471(连接中回落已保存)同设备跨 profile 覆写:MAP(18) 首试失败 DISCONNECTED 清掉设备级单布尔,isBusy 被其余 profile 撑住 → 双条件失败落 else;iPhone MAP 8 试 0 成(含栈侧自发重试)使其从偶发变必现无旁路;聚合在读取侧现算,单 profile 失败不清其余 profile 的 CONNECTING 判定

共性教训:MiCar 的三处定制(「已保存」文案 / mIsConnectingState 旁路 / isBusy 混含断开)每一个单独看都是合理的产品决策,但三者叠加形成”状态双源+兜底可见”的结构,凡协议栈时序抖动(多 profile 异步、双设备交叉、回连延迟)都会在该结构上放大为可见 UI 异常。

5. 修复现状与残留语义问题

状态
3187 跨设备污染(中途回退已保存)✅ 已修(362953 PS3,设备匹配过滤)——只修跨设备;同设备跨 profile 残余=2471,未修
3205 断开采灰波动✅ 已修(动画判据对齐+300ms 防抖+同目标不重计时)
ON→全连完的「已保存」窗口(广播 4077ms,建链串行 45.5s)⚠️ 语义问题,非 bug:栈层建链耗时的忠实呈现(A14 实测更快 → 协议栈性能差异,转栈侧优化)。要消灭需乐观标记(ON 时已配对设备置 connecting),代价=离线设备先”正在连接”再失败回退,待产品裁决
DISCONNECTING 无独立文案⚠️ 设计压缩,3205 后视觉正确;若产品要”正在断开”文案可映射 DISCONNECTING 单独显示
2471 同设备跨 profile 覆写❌ 未修。方案 A(推荐):Controller 回调改喂 per-profile 连接中集合(setProfileConnecting(profile, state==CONNECTING)),Set 替代单布尔,summary/动画/置灰三消费点同步 isNotEmpty(),!isBusy 终态 clear —— 本质是用回调缓存复刻 AOSP 读取侧聚合语义,零新增 binder。方案 B(彻底对齐):摘要改调 cachedDevice.getConnectionSummary() + AOSP 式后台线程计算 —— 需配合 139360 刷新预算/快照基础设施,留给排序风暴整改统一做。禁止:回退 isBusy 单判(断开中误显)/ 主线程现查 getProfileConnectionState(binder 风暴)

6. 给后续开发的设计建议

  1. 不要再给摘要加第四个状态文案——每加一个状态,双源同步窗口就多一类;先评估能否用现有三态映射
  2. 旁路状态位的写入点必须可枚举——mIsConnectingState 全仓 3 个写入点(默认值/setConnecting/!isBusy 自清),新增写入点要过评审(3187 的教训:写入点失控=污染源)
  3. 跨设备操作必须带设备过滤——Controller 遍历列表的循环,默认第一行就写 cachedDevice.equals(...) 守卫
  4. 防抖/延时逻辑警惕重计时(P14):持续事件流中”每次都重新计时”会把生效时刻无限推迟;同目标不重计时
  5. 状态位粒度必须 ≥ 事件粒度(2471 新增):设备级布尔承接 profile 级回调必然被覆写;要么聚合读取(AOSP),要么 per-profile 集合缓存(方案 A),不允许”最近一次回调”整体覆写
  6. binder 链计算的 AOSP 答案是换线程不是缓存(2471 新增):getConnectionSummary()/isBusy() 这类逐 profile 活 binder 调用,AOSP 一律 postOnBackgroundThread 后台算、主线程贴;车机排序风暴整改(139360)应对齐此模型
  7. 改这片代码前必读:30-模块流程篇/34-已配对列表摘要状态机-AOSP与MiCar.md + jira-analyze LESSONS #24