69 · dcddif 版全览 — 1456 行小白导览与三方改动对照
源码基线:dcddif(
base/settingsLibAndroid/src/dcddif/java/com/android/settingslib/bluetooth/CachedBluetoothDevice.java,dev 分支,共 1456 行)。 本系列 61-68 章的基线是 f3dif(2719 行,A17 主线纯 AOSP);本章反过来以 **dcddif(dcd flavor,骁龙平台)**为基线做一篇自成一体的入门导览——先整体后细节,不要求先读完 61-68。 交叉引用:60-总览与学习地图、61-一台蓝牙设备的Android身份、22-双flavor差异、70-active设备闭环篇
0. 这个文件是什么来头
CachedBluetoothDevice = 车机上一台蓝牙设备的”档案卡 + 遥控器”:
- 档案卡:缓存这台设备的名字、电量、连接状态、支持哪些 profile(内存里,免跨进程反复查询)
- 遥控器:对外提供连接、断开、配对、取消配用的操作入口
dcddif 版文件头有三段”血统”,改代码前必须能分辨:
| 来源 | 证据(行号) | 内容 |
|---|---|---|
| Android 原生 (AOSP) | :1-15 Apache 协议 | settingslib 蓝牙模块核心类,与手机设置同源 |
| 高通 (Qualcomm) | :17-52 BSD 协议 | 双蓝牙改造,代码内 [Dual Bluetooth] 标记 |
| 小米 (MICAR) | MICAR-PORTING-START/END 标记(dcddif 共 10 行标记) | 车机定制:自动连接黑名单、HID 配对走默认适配器、超时翻倍 |
dcddif 是什么:不是笔误。settingsLibAndroid/build.gradle:49-50 把 src/dcddif/java、src/dcddif/res 并入 dcd flavor 的源码集——即”国内骁龙平台差异化代码”的存放位置。三 flavor 机制见 22 篇。
1. 预备概念(30 秒版,详细见概念篇)
| 概念 | 一句话 | 展开 |
|---|---|---|
BluetoothDevice | 系统对象,代表一台远端设备,MAC 唯一标识;每次查询跨进程 | 10 篇 |
| bond(配对/绑定) | BOND_NONE → BOND_BONDING → BOND_BONDED,先配对才能连接 | 11 篇 |
| profile | 蓝牙按用途分的通道:A2DP 放歌 / HFP 通话 / PBAP 电话簿 / MAP 消息……”连接手机”= 逐条 profile 建连 | 12 篇 |
| UUID | 设备”我支持哪些功能”的清单,配对早期可能拿不到 → 大量”晚点再连”逻辑 | 10 篇 |
| 为什么叫 Cached | 状态缓存在本进程 + 事件推送更新 + 统一通知 UI——类的设计主线 | 61 篇 |
2. 字段地图(:103-158)
┌─ 身份 ─────────────────────────────────────────────┐
│ mDevice 本设备对应的系统 BluetoothDevice │
│ mActiveDevice 双蓝牙下"当前实际操作"用的设备对象(高通) │
│ mLocalAdapter 默认蓝牙适配器(车机蓝牙芯片1) │
│ mActiveAdapter 当前使用的适配器(可能是芯片2,高通) │
│ mRssi 信号强度(越接近0越强) │
│ mHiSyncId 助听器左右耳配对组 ID(:121) │
│ mSubDevice 助听器的"另一只耳朵"(:156) │
├─ 能力 ─────────────────────────────────────────────┤
│ mProfiles 这台设备支持的 profile 列表(:126) │
│ mRemovedProfiles 被移除的 profile(:129) │
├─ 状态 ─────────────────────────────────────────────┤
│ mIsActiveDeviceA2dp/Headset/HearingAid 是否当前活跃 │
│ mIsXxxProfileConnectedFail 连接是否超时 │
│ mJustDiscovered / mUnpairing / mConnectAttempted │
├─ 基础设施 ──────────────────────────────────────────┤
│ mHandler 主线程 Handler=连接超时看门狗(:160) │
│ mCallbacks 观察者列表(:136) │
│ mDrawableCache 设备图标 LruCache(:158) │
│ mProfileLock 保护 mProfiles 的锁(:118) │
└─────────────────────────────────────────────────────┘
三个值得注意的设计:
CopyOnWriteArrayList(:126、:136):读多写少的线程安全容器。UI 线程频繁遍历,蓝牙回调线程偶尔增删。- 一个 Handler 三种消息(:160-180):
msg.what直接复用 profile 数字 ID(BluetoothProfile.A2DP/HEADSET/HEARING_AID),收到消息=“该 profile 连接超时”。 - mDevice vs mActiveDevice 两个设备对象:双蓝牙架构下同一 MAC 在两个栈里各有一个
BluetoothDevice。mDevice=档案归属,mActiveDevice=实际发命令用的那个。
3. 方法分组地图
| 分组 | 代表方法(dcddif 行号) | 一句话 |
|---|---|---|
| 构造初始化 | 构造器 :182、fillData :495、updateProfiles :737 | 出生时填档案:UUID→profile 集合、活跃位、权限迁移 |
| 连接/断开/配对 | connect :359、connectAllEnabledProfiles :387、disconnect :323/:337、startPairing :440、unpair :461 | 遥控器的按钮 |
| 事件入口(被回调) | onProfileStateChanged :221、onUuidChanged :788、onBondingStateChanged :839、onActiveDeviceChanged :640、onBluetoothStateChanged :292 | 档案的更新来源,事件驱动 |
| 状态查询 | isConnected :705、isBusy :724、getBondState :625、isActiveDevice :678 | 档案卡的读法 |
| UI 文案 | getConnectionSummary :1045、getCarConnectionSummary :1164(车机专用) | 界面副标题的算法 |
| 观察者 | registerCallback :887、dispatchAttributesChanged :895 | 变化通知 UI |
| 排序 | compareTo :922 | 已连接 > 已配对 > 刚发现 > 信号强 > 名字序 |
| 双蓝牙(高通) | initAdapterDevice :1379、unpairCounterpartDevice :1436、getCounterpartDevice :1427 | 同一 MAC 双栈路由 |
4. 三个必懂机制
4.1 连接超时看门狗(onProfileStateChanged :233-261 + mHandler :160-180)
STATE_CONNECTING → mHandler 发 60s 延时消息(what=profileId,MAX_MEDIA_PROFILE_CONNECT_DELAY=60000,:111)
STATE_CONNECTED → removeMessages 撤看门狗(60s 内连上了,正常)
STATE_DISCONNECTED → 看门狗还在 = 连了 60s 没连上就被断 → 移除消息 + 置失败标志位
消息落地(60s 到) → handleMessage 置 mIsXxxProfileConnectedFail=true + refresh()
→ UI 走 profile_connect_timeout_subtext 显示"连接超时"(:1053-1055)
4.2 UUID 晚到再连(connectAllEnabledProfiles :387 + onUuidChanged :788)
配对刚完成时 UUID 清单可能没同步到,mProfiles 为空时直接 return 不连(:390-399,注释写明防与车机配对的竞态);等 ACTION_UUID 事件到来,onUuidChanged 检查时间窗(普通 10s / HOGP 60s / 助听器 30s,小米把窗口全部 ×2,:793-799)内尝试过连接就补连。
4.3 配对完成自动连 + 小米黑名单(onBondingStateChanged :839-860)
BOND_BONDED 且 isBondingInitiatedLocally()(本机主动发起)→ 自动 connect();小米在此插入 isCloseAutoConnectDevice()(:821-837):读 Settings.Secure 的 bluetooth_close_autoconnect(只存一个 MAC),命中则不自动连。
⚠️ f3dif(A17)没有这个判断——CarPlay 黑名单失效问题,详见 66 §自动回连与31 §4。
5. 完整流程串讲:用户在车机上点”连接我的手机”
1. UI 层(micarConnectionSettings 的 preference/controller)调 cachedDevice.connect()
2. ensurePaired()(:431)── 未配对则先 startPairing() 并返回
3. connectAllEnabledProfiles()
├─ mProfiles 为空(UUID 未到)→ 暂不连,等 onUuidChanged
└─ 否则 mActiveAdapter.connectAllEnabledProfiles(mDevice)
4. 蓝牙栈逐条 profile 建连,状态变化 → 广播
5. BluetoothEventManager 收广播 → LocalBluetoothProfileManager
→ onProfileStateChanged(CONNECTING → 发看门狗 / CONNECTED → 撤看门狗)
6. dispatchAttributesChanged()(:895)→ Callback.onDeviceAttributesChanged()(:944-946)
7. UI preference 收到回调重读 getName()/isConnected()/MiCar 三态摘要(getDeviceSummary,
MiCarBluetoothBondedDevicePreference.kt:92-108)→ 刷新
⚠️ 注意:AOSP 的 getCarConnectionSummary()/getConnectionSummary() 在本仓是零调用死链,
车机 UI 用的是 MiCar 自建三态,详见 74 章
卡在 CONNECTING 60 秒 → 看门狗触发 → 失败标志位 → UI 显示连接超时。
6. 三方改动对照表(改 dcddif 前必读)
| 代码段 | 行号 | 归属 | 内容 |
|---|---|---|---|
initAdapterDevice() | :1379-1405 | 高通双蓝牙 | 同一 MAC 双栈路由,决定 mActiveAdapter/mActiveDevice |
unpairCounterpartDevice() | :1436-1447 | 高通双蓝牙 | 解绑时把另一个栈里的同一设备也解掉 |
isNewAdapterRequired() | :1452-1455 | 高通双蓝牙 | 写死 return true(TODO 说要从 Settings 读开关) |
startPairing() HID 分支 | :442-449 | 小米 | HID 设备(键盘/手柄)强制走默认适配器配对 |
isCloseAutoConnectDevice() | :819-837 | 小米 | 自动连接黑名单(Settings.Secure bluetooth_close_autoconnect,单 MAC) |
onUuidChanged() 窗口×2 | :792-800 | 小米 | 自动重连窗口翻倍 |
实践提醒:MICAR-PORTING 与 [Dual Bluetooth] 圈住的逻辑是平台定制的,动它们易在 flavor 间产生行为分歧;上游更新时这些补丁要手工合并。本文件在 dcddif 源码集——dev 分支 src/ 下现存 dcddif / xcddif 两份副本(build.gradle:46-67:dcd 用 dcddif,xcd 与 global 共用 xcddif;f3dif 仅是 A17 分支的历史基线,本分支不存在)。改 dcddif 后须确认是否同步改 xcddif 同名文件(检查清单见 22 篇)。
7. 在本工程里谁在用它
由同目录 CachedBluetoothDeviceManager 创建持有;settingsPage/micarConnectionSettings 大量消费(grep 核实):
controller/BluetoothBondedDevicesPrefController.java— 已配对设备列表preference/MiCarBluetoothBondedDevicePreference.kt— 单个设备条目(注册 Callback 刷新自己)view/MiBluetoothPairingDialogActivity.kt— 配对弹窗view/BluetoothDisconnectConfirmDialogFragment.java— 断开确认controller/BluetoothOperationsController.java— 连接/断开操作中枢
另有两个仓内模块共享同一个 LocalBluetoothManager 单例(不碰 active,但共享事件线程,红队核验补全):base/settingsBaseUi 的 VoiceAssistProvider.kt:416(查 PBAP 状态)、settingsPage/globalOnly 的 BluetoothRequestPermissionActivity.java:97。
8. 自测清单
- 能说出”档案卡 + 遥控器”各自对应哪些方法组?
- mDevice 与 mActiveDevice 为什么有两个?谁在什么场景写?
- 60s 看门狗的四种 profile 状态分别对应看门狗什么操作?
- mProfiles 为空时 connect 链发生了什么?谁负责补连?
- 小米黑名单存在哪个 Settings.Secure 键?f3dif 上为什么失效?
- dcddif/xcddif/f3dif 各自的 MICAR-PORTING 标记数量?(答:10/33/0)
- 蓝牙列表排序的五个优先级从高到低?
9. 交叉引用
- 深入单类(f3dif 基线):61 身份 → 62 字段 → 63 一生 → 64 连接断开 → 65 刷新分发 → 66 bond与回连 → 67 active → 68 案例
- active 闭环深讲(dcddif 基线):70-总览 → 71 查询 → 72 监听 → 73 设置 → 74 消费
- 背景知识:10-16 基础概念篇、21-settingslib架构与事件流、22-双flavor差异