Files
Obsidian/项目文档/徐工/蓝牙/蓝牙模块开发熟悉手册.md
T
2026-11-05 14:35:04 +08:00

545 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蓝牙模块开发熟悉手册
> 整理日期:2026-07-06
> 整理范围:`F:\zdCode\xugong` 当前代码、`agent.md` 历史线索、蓝牙目录现有 Markdown、docx、xlsx、PDF 可抽取文本。
> 目的:帮助新接手开发者快速理解徐工项目蓝牙模块的架构、主流程、代码入口、日志排查路径和历史高风险点。
## 一句话结论
徐工蓝牙不是单一 APK 功能,而是一条跨 `YX_BlueTooth`、`YX_BluetoothSDK`、`YX_Music`、`YX_Voice`、`YX_Launcher`、`YX_SystemUI`、Android Frameworks、系统 Bluetooth 应用、顾凯 SDK/芯片的链路。开发时优先从 `YX_BluetoothSDK` 看协议封装,再看上层应用如何订阅 SDK 回调和 `DataManager` 数据。
## 1. 模块地图
| 模块 | 定位 | 重点入口 |
| ------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `YX_BluetoothSDK` | 蓝牙核心 SDK,封装 HFP/A2DP/AVRCP/PBAP/MAP、数据库、状态推送、语音同步 | `manager`、`modules`、`platform`、`audio`、`database` |
| `YX_BlueTooth` | 蓝牙电话主业务应用,联系人、通话记录、拨号、通话页、常驻服务、widget | `MainActivity`、`ContactsFragment`、`CallLogsFragment`、`BluetoothPhoneActivity`、`BluetoothService` |
| `YX_Music` | 蓝牙音乐页面所在应用,负责蓝牙音乐页面 UI、播放控制、封面显示 | `BluetoothMusicFragment`、`BluetoothMusicViewModel` |
| `YX_Voice` | 语音控制蓝牙开关、电话、音乐、通讯录/通话记录状态同步 | `VoiceProtocol`、`BlueToothHelper`、`BluetoothMonitorReceiver`、`VoiceEventHelper` |
| `YX_Launcher` | 首页音乐卡片、蓝牙音乐信息展示和媒体按键入口 | `WidgetsMedia`、`DisplayShareViewModel`、`Id3Info` |
| `YX_SystemUI` | 状态栏/导航媒体区、通话状态显示、系统级入口 | `MediaControlView`、`VirtualManager`、SystemUI 蓝牙封装类 |
| `YX_CarControl` | 配对弹窗、自动同步开关等外围联动 | 注意不要重复初始化 PBAP 联系人同步 |
| Frameworks / Bluetooth Process | Android 蓝牙 API、Binder、系统 Bluetooth app、JNI、协议栈 | `android.bluetooth.*`、`packages/apps/Bluetooth` |
| 顾凯 SDK/芯片 | 蓝牙协议栈供应商实现和 HCI 交互 | 底层联调、btsnoop/顾凯日志 |
## 2. 总体分层
PDF《蓝牙框架原理以及通讯数据交互流程》给出的系统层级可落到项目代码中:
```mermaid
flowchart TD
A["上层应用<br/>YX_BlueTooth / YX_Music / YX_Voice / Launcher / SystemUI"] --> B["YX_BluetoothSDK<br/>ControlManager / Manager / Platform Client"]
B --> C["Frameworks API<br/>android.bluetooth.*"]
C --> D["Bluetooth Process<br/>packages/apps/Bluetooth"]
D --> E["JNI<br/>packages/apps/Bluetooth/jni"]
E --> F["AOSP 蓝牙协议栈<br/>system/bt"]
F --> G["顾凯 SDK / HAL / HIDL"]
G --> H["蓝牙芯片"]
```
反向回调通常是:芯片/协议栈事件 -> JNI -> 系统 Bluetooth 服务 -> Frameworks 广播/API -> `YX_BluetoothSDK platform` 接收 -> `modules` 整理状态 -> 上层 UI、语音、Launcher/SystemUI。
## 3. SDK 内部分层
`YX_BluetoothSDK` 是最值得先读的模块,建议按下面顺序看:
| 层 | 包路径 | 职责 |
|---|---|---|
| 对外控制层 | `com.yaxon.bluetoothsdk.manager` | 单例入口,给 App/语音/音乐模块调用,例如 `BtPhoneControlManager`、`BtContactsControlManager`、`BtMusicControlManager` |
| 业务模块层 | `com.yaxon.bluetoothsdk.modules` | 真正组织业务状态机、监听、数据库、语音/投屏同步,例如 `BtContactsManager`、`BtPhoneManager`、`BtMusicManager`、`BtInfoPushManager` |
| 平台适配层 | `com.yaxon.bluetoothsdk.platform` | 封装 Android 蓝牙 API 和广播,例如 `BluetoothHfpClient`、`BluetoothPbapClient`、`BluetoothAvrcpClient`、`BluetoothConnectListenerClient` |
| 音频层 | `com.yaxon.bluetoothsdk.audio` | 蓝牙音乐音频焦点、A2DP mute/unmute、MediaSession 媒体键 |
| 数据层 | `com.yaxon.bluetoothsdk.database` | 联系人、通话记录、配对设备、收藏等本地 SQLite 数据 |
| 工具层 | `utils` | 系统联系人/通话记录读取、号码处理、日志、权限等 |
开发时一般从 `ControlManager` 找入口,然后下钻到 `modules`,再看 `platform` 是调用系统 API 还是接收广播。
## 4. 启动与初始化
蓝牙电话应用开机常驻入口:
```mermaid
flowchart LR
BootReceiver["YX_BlueTooth BootReceiver"] --> Service["BluetoothService.onCreate"]
Service --> SDK["初始化 BtPhone/BtContacts/BtCallRecords 等控制器"]
Main["MainActivity.onCreate/initData"] --> VM["SettingViewModel / Fragment ViewModel"]
VM --> Managers["BtSettingControlManager / BtContactsControlManager / BtPhoneControlManager"]
```
关键文件:
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/receiver/BootReceiver.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtSettingManager.java`
`BtSettingManager.init()` 会初始化 `BluetoothHfpClient`、`BluetoothA2dpSinkClient`、`BluetoothDeviceClient`、`BluetoothMapConnectClient`、`BluetoothConnectListenerClient`,并在蓝牙已打开时初始化已配对设备列表。它还会延迟检查已连接设备,并把当前活跃蓝牙 MAC 同步给语音模块。
## 5. 蓝牙开关、扫描、配对、连接
### 5.1 状态来源
蓝牙开关状态来自 `BluetoothAdapter.ACTION_STATE_CHANGED`,SDK 侧由 `BluetoothConnectListenerClient.setBluetoothSwitchState()` 处理:
| 状态值 | Android 常量 | 含义 |
|---|---|---|
| `10` | `BluetoothAdapter.STATE_OFF` | 蓝牙已关闭 |
| `11` | `BluetoothAdapter.STATE_TURNING_ON` | 正在打开 |
| `12` | `BluetoothAdapter.STATE_ON` | 蓝牙已打开 |
| `13` | `BluetoothAdapter.STATE_TURNING_OFF` | 正在关闭 |
注意:`BluetoothAdapter.enable()` / `disable()` 返回 `true` 只代表请求被接受,不代表最终状态已切到 `ON/OFF`。语音场景必须看后续 `blueState -> 12` 或 `blueState -> 10`。
### 5.2 连接状态
连接状态由 `BluetoothConnectListenerClient` 聚合 HFP、A2DP、A2DP Sink 等 profile 广播,再通知各模块:
- `notifyBluetoothConnected()`:写入连接 MAC、连接名、连接状态,并回调 `onConnect`
- `notifyBluetoothDisconnect()`:清理蓝牙音乐信息,写入断开状态,并回调 `onDisconnect`
- `setBondState()`:处理配对状态
- `setPairingRequest()`:处理配对请求
### 5.3 HFP 与 A2DP 的关系
交接说明里特别强调:蓝牙连接包含 `hfp`(电话)和 `a2dp`(音乐/音频连接);通讯录同步需要再连 `pbap`,同步完成后需要断开 `pbap`。
## 6. 通讯录与通话记录同步(PBAP)
PBAP 是 Phone Book Access Profile,用于从手机拉取通讯录和通话记录。本项目中通讯录和通话记录共享一次 PBAP 同步窗口:联系人完成、通话记录完成后,才认为本轮电话本下载整体完成,并主动断开 PBAP,避免阻塞和卡死。
### 6.1 主流程
```mermaid
sequenceDiagram
participant UI as ContactsFragment/CallLogsFragment
participant CM as BtContactsControlManager
participant M as BtContactsManager
participant P as BluetoothPbapClient
participant SYS as Android PBAP/System Provider
participant DB as DataContactsHelper/DataCallRecordHelper
participant V as ViewModel/Voice
UI->>CM: phoneBookPullRequest(context)
CM->>M: phoneBookPullRequest(context)
M->>P: connectPbap 或 phoneBookPullRequest
P->>SYS: 删除系统 Contacts/CallLog 缓存
P->>SYS: 发送 Intent.ACTION_USER_UNLOCKED
SYS-->>P: contacts.download.complete
P-->>M: onContactsPullCompleted()
M->>SYS: ContactUtils.getAllContacts()
M->>DB: deleteContacts + saveContactsList
M-->>V: onContactsPhoneBook(list)
SYS-->>P: calllogs.download.complete
P-->>CallRecords: onCallLogsPullCompleted()
CallRecords->>SYS: CallLogUtils.getCallLogList()
CallRecords->>DB: clearCallRecord + saveCallRecordList
CallRecords-->>V: onCallRecords(list)
M->>P: contacts + calllogs 都完成后 disconnectPbap()
```
### 6.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| UI 点击同步联系人 | `ContactsFragment.phoneBookPullRequest()` |
| UI 点击/通话页触发同步 | `CallLogsFragment` -> `CallHistoryViewModel.phoneBookPullRequest()` |
| SDK 对外入口 | `BtContactsControlManager.phoneBookPullRequest()` |
| 同步状态、权限、自动同步 | `BtContactsManager.phoneBookPullRequest()`、`isAutoSync()`、`checkContactsPermissionGranted()` |
| PBAP 连接和系统广播 | `BluetoothPbapClient.connectPbap()`、`phoneBookPullRequest()`、`PbapClientReceiver` |
| 联系人读取与保存 | `ContactUtils.getAllContacts()`、`DataContactsHelper.savePhoneBookList()` |
| 通话记录读取与保存 | `CallLogUtils.getCallLogList()`、`BtCallRecordsManager.loadCallsLog()`、`DataCallRecordHelper.saveCallRecordList()` |
| UI 状态判断 | `ContactsFragment.syncPhoneBookSynchronized()`、`CallLogsFragment.notifyPhoneRecordList()` |
### 6.3 关键状态位
| 状态 | 说明 | 风险 |
|---|---|---|
| `isPhoneBookPulling` | PBAP 本轮同步是否正在进行 | UI loading 和重复点击判断要优先看它 |
| `isContactsPullCompeted` / `getContactLoadWonType()` | SDK 本轮联系人读取、写库、回调是否完成 | 比 `isPhoneBookSynchronized()` 更能代表本轮同步完成 |
| `isPhoneBookSynchronized()` | 本地 DB 当前设备是否已有至少一条联系人 | 只能代表“本地有数据”,不代表本轮 PBAP 完成 |
| `isCallRecordUpdated()` | 本地 DB 当前设备是否已有通话记录 | 通话记录页显示和 loading 判断会用到 |
### 6.4 已验证历史结论
- 清空蓝牙本地 SQLite 表不是通讯录同步失败的直接原因;正常清空后 PBAP 会重新写入。
- `isPhoneBookSynchronized()` 只表示 DB 有联系人,不能作为“本轮同步完成”的唯一条件。
- 曾出现 `YX_CarControl` 和 `YX_BlueTooth` 双进程同时初始化 PBAP,导致重复 `phoneBookPullRequest`、重复删写联系人表;当前应统一由 `com.yaxon.bluetooth` 负责 PBAP 同步。
- 联系人同步中应优先显示 loading,避免在旧库数据或第一条联系人写入后提前显示“0 个联系人”或旧列表。
- 通话记录 Adapter 曾因传入同一个 list 导致 `clear()` 自清空,已通过传 `new ArrayList<>(mCallRecordList)` 规避。
### 6.5 排查日志关键字
| 阶段 | 关键字 |
|---|---|
| 请求开始 | `BluetoothPbapClient: phoneBookPullRequest` |
| PBAP 连接变化 | `PbapClientReceiver device name`、`prevState`、`state` |
| 联系人下载完成 | `BtContactsManager: onContactsPullCompleted` |
| 联系人读取完成 | `contacts read completed size =` |
| 联系人 UI 收到 | `ContactListViewModel: onContactsPhoneBook =` |
| 通话记录读取 | `mCallRecordBeanList size =` |
| 通话记录保存 | `saveCallRecordList size =` |
| 通话记录 UI | `CallLogsFragment: notifyPhoneRecordList list ==` |
| 失败 | `onContactsPullFailed`、`contacts synchronization failed`、`通讯录同步失败` |
## 7. 蓝牙电话(HFP)
HFP 是 Hands-Free Profile,用于拨号、接听、拒接、挂断、通话音频状态等。
### 7.1 电话状态
| 状态值 | 常量 | 说明 |
|---|---|---|
| `0` | `CALL_STATE_ACTIVE` | 通话中 |
| `1` | `CALL_STATE_HELD` | 保持中 |
| `2` | `CALL_STATE_DIALING` | 正在拨号 |
| `3` | `CALL_STATE_ALERTING` | 正在响铃,项目中通常合并到拨号处理 |
| `4` | `CALL_STATE_INCOMING` | 来电 |
| `5` | `CALL_STATE_WAITING` | 第三方等待 |
| `6` | `CALL_STATE_HELD_BY_RESPONSE_AND_HOLD` | 响应并保持,项目中通常合并到保持处理 |
| `7` | `CALL_STATE_TERMINATED` | 挂断 |
### 7.2 拨号调用链
PDF《蓝牙电话交互流程解析》给出的拨号链路,对应当前项目可理解为:
```mermaid
flowchart TD
A["YX_BlueTooth UI / BluetoothService / Voice"] --> B["BtPhoneControlManager.dialing"]
B --> C["BtPhoneManager.dialing"]
C --> D["BluetoothHfpClient.dial"]
D --> E["BluetoothHeadsetClient.dial"]
E --> F["BluetoothHeadsetClientBinder.dial"]
F --> G["HeadsetClientService.dial"]
G --> H["HeadsetClientStateMachine.Connected.dial"]
H --> I["NativeInterface.dial"]
I --> J["com_android_bluetooth_hfpclient.dialNative"]
J --> K["顾凯 SDK / 蓝牙芯片"]
```
接听、拒接、挂断与拨号类似,只是 Frameworks API 分别换成:
- `BluetoothHeadsetClient.acceptCall()`
- `BluetoothHeadsetClient.rejectCall()`
- `BluetoothHeadsetClient.terminateCall()`
### 7.3 状态回调链
```mermaid
sequenceDiagram
participant FW as BluetoothHeadsetClient 广播
participant HFP as BluetoothHfpClient
participant M as BtPhoneManager
participant UI as BluetoothPhoneViewModel/UI/Window
participant Push as BtInfoPushManager
participant Voice as BtVoiceControlManager
FW-->>HFP: ACTION_CALL_CHANGED
HFP->>HFP: getCurrentCalls()
HFP-->>M: OnCallPhoneStateListener.callState(list)
M->>M: 转成 CallingState
M-->>UI: onHfpCallChanged(list)
M->>Push: setPhoneState / setPhoneCallState / setPhoneGroups
M->>Voice: notifyBtInComing / notifyBtCall / notifyBtAccept / notifyBtHangup
```
### 7.4 关键文件
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtPhoneControlManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtPhoneManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothHfpClient.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BluetoothPhoneViewModel.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothPhoneActivity.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/TelephoneWindowControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/ThirdCallingControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
### 7.5 排查日志关键字
| 问题 | 关键字 |
|---|---|
| 拨号没有发出 | `BluetoothHfpClient: dial phoneNumber`、`start dial phoneNumber` |
| HFP 未连接 | `hfp_phone_protocol_not_connected`、`isHfpConnect disconnect` |
| 来电/去电 UI 不刷新 | `ACTION_CALL_CHANGED`、`clientCallList`、`onHfpCallChanged` |
| 挂断/拒接失败 | `hangupCall`、`rejectCall`、`acceptCall flag` |
| 通话音频异常 | `ACTION_AUDIO_STATE_CHANGED`、`audio connection state`、`connectAudio`、`disconnectAudio` |
## 8. 蓝牙音乐(A2DP/AVRCP/MediaSession)
蓝牙音乐涉及两条线:
- A2DP:音频流链路,决定声音是否从手机到车机。
- AVRCP/MediaBrowser/MediaSession:播放控制、播放状态、歌曲元数据、媒体按键。
### 8.1 主流程
```mermaid
sequenceDiagram
participant UI as YX_Music/BluetoothMusicFragment
participant CM as BtMusicControlManager
participant M as BtMusicManager
participant Audio as BtMusicAudioManager
participant AVRCP as BluetoothAvrcpClient/MediaBrowserClient
participant Push as BtInfoPushManager/DataManager
participant L as Launcher/SystemUI/Voice
UI->>CM: requestAudioFocus/playBtMusic
CM->>M: requestAudioFocus/playBtMusic
M->>Audio: requestAudioFocus()
Audio->>M: setA2dpUnMute / informAudioFocusStateNative=1
M->>AVRCP: play()
AVRCP-->>M: onPlaybackStateChanged / onMetadataChanged
M-->>UI: onPlayMusicState/onPlayMusicInfo
M->>Push: sendMusicInfoToLauncher(BT_MUSIC_INFO)
Push-->>L: ContentObserver/DataManager 读取 BT_MUSIC_INFO
```
### 8.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| 蓝牙音乐页面 | `YX_Music/src/music/java/com/yaxon/musicplay/view/music/BluetoothMusicFragment.java` |
| 音乐 ViewModel | `YX_Music/src/main/java/com/yaxon/musicplay/viewmodel/BluetoothMusicViewModel.java` |
| SDK 控制入口 | `BtMusicControlManager.playBtMusic()`、`pauseBtMusic()`、`requestAudioFocus()` |
| 音乐业务状态 | `BtMusicManager.onPlaybackStateChanged()`、`onMetadataChanged()` |
| 音频焦点和媒体键 | `BtMusicAudioManager.requestAudioFocus()`、`onMediaButtonEvent()` |
| AVRCP 控制 | `BluetoothAvrcpClient.play/pause/previous/next()` |
| A2DP 音频状态 | `BluetoothA2dpSinkClient` |
| Launcher/SystemUI 显示 | `BtInfoPushManager.sendMusicInfoToLauncher()`、`DataUtils.BT_MUSIC_INFO` |
### 8.3 焦点与保护逻辑
`BtMusicManager.onPlaybackStateChanged()` 中有一条关键保护:如果蓝牙音乐开始播放,但当前蓝牙音乐不在前台且没有蓝牙音乐焦点,就会 mute A2DP 并 `pauseBtMusic()`。这是为了防止手机端或后台异常播放抢声道,但也会影响 Launcher 首页音乐卡片冷启动首次播放场景。
历史上已经通过 SDK 侧 `BtMusicAudioManager` 的 MediaSession 兜底处理过 Launcher 首次点击:
- MediaSession 在无焦点但蓝牙音乐是当前音源时也可接媒体键。
- 收到 `KEYCODE_MEDIA_PLAY_PAUSE` 后先申请焦点,再 `playBtMusic()`。
- A2DP 连接、元数据、播放状态变化后刷新 MediaSession,避免第一次点击只激活 session、第二次才播放。
### 8.4 封面高风险点
蓝牙音乐封面风险集中在固定路径:
```text
/sdcard/ID3/id3cover.JPEG
```
问题模式:
- 系统/底层复用同一个封面路径。
- 歌曲元数据先到,封面路径后到。
- 文件可能短时间为 0 字节或半写入。
- 空 `albumUrl` 或无效文件可能覆盖当前有效封面。
排查关键字:
- `albumUrl = /sdcard/ID3/id3cover.JPEG`
- `length = 0`
- `Failed to create image decoder`
- `获取bitmap为null`
- `skip invalid album file`
- `loadFileImageToView success`
## 9. 语音与蓝牙交互
### 9.1 语音开关蓝牙
```mermaid
flowchart TD
A["TXZ / VoiceService 请求<br/>openBluetooth / closeBluetooth"] --> B["VoiceProtocol.handleBluetooth"]
B --> C["BlueToothHelper.openBlueToothByVoice / closeBlueToothByVoice"]
C --> D["BluetoothAdapter.enable / disable"]
D --> E["BluetoothMonitorReceiver.ACTION_STATE_CHANGED"]
E --> F["BlueToothHelper.syncBluetoothStatusToTxzByStateChanged"]
F --> G["VoiceEventHelper.notifyBluetoothOpenStaus / notifyBluetoothConnectStaus"]
```
关键文件:
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/VoiceProtocol.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/BlueToothHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/receiver/BluetoothMonitorReceiver.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/VoiceEventHelper.java`
重点:
- `openBlueToothByVoice()` / `closeBlueToothByVoice()` 做了同目标去抖、相反目标保护、有限重试、延迟状态回填。
- `BluetoothMonitorReceiver` 收到 `STATE_ON/OFF` 后立即同步 TXZ,并延迟补发状态。
- 不能以 `enable()/disable()` 返回值作为最终成功依据。
### 9.2 语音电话与通讯录
语音电话/通讯录状态同步主要由 SDK 侧 `BtVoiceManager` 和 `BtVoiceControlManager` 承接:
- 电话来电:`BtPhoneManager` -> `BtVoiceControlManager.notifyBtInComing`
- 去电:`notifyBtCall`
- 接通:`notifyBtAccept`
- 挂断:`notifyBtHangup`
- 联系人同步完成:`BtVoiceManager.syncBtActiveDeviceMac`、`BtVoiceControlManager.notifyBtContactReload`
- 通话记录变化:`BtVoiceManager.notifyCallRecordChange`
## 10. Launcher/SystemUI/投屏联动
### 10.1 数据通道
SDK 通过 `BtInfoPushManager` 写入公共数据,外围模块通过 `ContentObserver` 或 `DataManager.getObject()` 读取。
| 数据 | 写入方 | 读取方 |
|---|---|---|
| `DataUtils.BT_MUSIC_INFO` | `BtInfoPushManager` | `YX_Launcher`、`YX_SystemUI`、`YX_Voice` |
| `DataUtils.BT_INFO` | `BtInfoPushManager` | Launcher/SystemUI/投屏相关 |
| 电话状态、联系人、号码、通话时长 | `BtPhoneManager` -> `BtInfoPushManager` | 仪表、Launcher、SystemUI、语音 |
### 10.2 常见现象
- Launcher 卡片能显示蓝牙音乐元数据,不代表按钮控制一定会走蓝牙音乐控制链路;按钮可能只是全局媒体键。
- SystemUI/Launcher 读取封面路径时要防空路径、0 字节、解码失败、同一首歌空封面覆盖已有封面。
- 电话投屏状态依赖 `BtInfoPushManager.setPhoneState()`、`setPhoneCallState()`、`setPhoneGroups()`。
## 11. 数据存储
| 数据 | 类 | 说明 |
|---|---|---|
| 联系人 | `DataContactsHelper` | 按蓝牙设备 MAC 存储联系人 |
| 通话记录 | `DataCallRecordHelper` | 按蓝牙设备 MAC 存储通话记录,过滤网络电话等异常号码 |
| 已配对设备 | `DataPairedHelper` | 配对设备列表 |
| 收藏 | `DataCollectionHelper` | 收藏联系人/号码 |
| 系统 Provider | `ContactUtils`、`CallLogUtils` | PBAP 下载完成后从系统联系人/通话记录 Provider 读取 |
| 跨模块状态 | `DataManager`、`YxIPC` | 音乐、电话、语音、Launcher/SystemUI 状态同步 |
开发时要分清:
- 系统联系人/通话记录 Provider 是 PBAP 下载中间数据。
- SDK 自己的 SQLite 是业务最终缓存。
- `DataManager` 是跨应用状态和展示数据的同步通道。
## 12. 常见问题排查索引
| 问题 | 先看模块 | 关键日志 |
|---|---|---|
| 蓝牙打不开/关不掉,语音已回复成功 | `YX_Voice`、`BluetoothConnectListenerClient` | `openBlueTooth`、`closeBlueTooth`、`blueState -> 10/11/12/13`、`syncBluetoothStatusToTxz` |
| 手机能连但电话不可用 | HFP | `hfp onServiceConnected`、`updateHfpConnectedState`、`isHfpConnect` |
| 手机能播但车机无声 | A2DP/焦点 | `bluetooth music requestAudioFocus`、`setA2dpUnMute`、`informAudioFocusStateNative` |
| Launcher 首页点播放无效 | MediaSession/焦点 | `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`、`MediaSessionService: Sending KeyEvent`、`BtMusicAudioManager: onMediaButtonEvent` |
| 通讯录不同步 | PBAP | `phoneBookPullRequest`、`onContactsPullCompleted`、`contacts read completed size`、`onContactsPhoneBook` |
| 通话记录为空 | PBAP/Adapter/UI | `mCallRecordBeanList size`、`saveCallRecordList size`、`notifyPhoneRecordList list ==` |
| 封面闪烁/消失/上一首封面 | 蓝牙音乐封面 | `albumUrl`、`length = 0`、`decodeFile`、`skip invalid album file` |
| 电话 UI 不弹/不消失 | HFP/UI/悬浮窗 | `ACTION_CALL_CHANGED`、`onHfpCallChanged`、`CallingState`、`TelephoneWindowControl` |
| 断电上电状态错 | ACC/POWER/SOURCE_ID | `POWER_MODE`、`ACC_STATE`、`SOURCE_ID`、`restoreBtMusicIfNeeded` |
## 13. 开发路线图
建议按这个顺序熟悉:
1. 先读现有文档:`蓝牙模块流程概览.md`、`蓝牙框架原理以及数据通讯交互流程.pdf`、`蓝牙电话交互流程解析.pdf`、`徐工项目蓝牙模块交接说明.docx`。
2. 看 `YX_BluetoothSDK` 分层:先 `manager`,再 `modules`,最后 `platform/audio/database`。
3. 跟一遍 PBAP:从 `ContactsFragment.phoneBookPullRequest()` 跟到 `BluetoothPbapClient.phoneBookPullRequest()`,再跟回 `onContactsPullCompleted()`。
4. 跟一遍 HFP:从 `BluetoothPhoneViewModel.acceptCall/rejectCall` 或 `BtPhoneControlManager.dialing()` 跟到 `BluetoothHfpClient`。
5. 跟一遍蓝牙音乐:从 `BluetoothMusicFragment` 跟到 `BtMusicControlManager`、`BtMusicManager`、`BtMusicAudioManager`、`BluetoothAvrcpClient`。
6. 跟语音开关蓝牙:从 `VoiceProtocol.handleBluetooth()` 跟到 `BlueToothHelper` 和 `BluetoothMonitorReceiver`。
7. 看外围联动:`BtInfoPushManager` 写 `BT_MUSIC_INFO/BT_INFO`,再看 Launcher/SystemUI 的读取和显示。
8. 最后看历史问题:`agent.md` 中的 PBAP 双进程、联系人 loading、通话记录自清空、封面固定路径、Launcher 首次点击播放。
## 14. 修改代码时的注意事项
- PBAP 同步不要在多个进程重复初始化监听,联系人/通话记录同步应以 `YX_BlueTooth` 进程为主。
- UI 判断“同步完成”不要只看 `isPhoneBookSynchronized()`,本轮同步期间还要看 `isPhoneBookPulling` 和 `getContactLoadWonType()`。
- PBAP 完成后要断开 PBAP,否则历史上有“阻塞、软件卡死”的风险。
- 蓝牙开关语音反馈要等最终 `STATE_ON/OFF`,不能只看 `enable/disable result=true`。
- 蓝牙音乐播放控制必须先处理音频焦点和 A2DP unmute,再发 AVRCP play。
- 封面路径下发/展示前要校验文件存在、非 0 字节、可解码,且要处理固定路径重写竞态。
- 通话记录/联系人传给 Adapter 或回调前尽量使用快照,避免共享 list 被清空或并发修改。
- `destroy()` 类清理要注意真正 `shutdown()` 线程池,历史代码里存在只调用 `isShutdown()` 的可疑写法。
## 15. 已知潜在风险
| 风险 | 说明 |
|---|---|
| `BtMusicControlManager.isA2dpSinkConnected()` | 历史排查中怀疑可能固定返回 false,需要结合当前代码确认 |
| 多处线程池销毁 | 部分 `destroy()` 里可能只调用 `isShutdown()` 而不是 `shutdown()` |
| `BluetoothMapConnectClient.isMapConnect()` | 历史排查中提到潜在 NPE |
| PBAP 同步耗时 | 现场 1973 联系人约 85 秒级,日志时间还可能因设备校时跳变 |
| 固定封面路径 | `/sdcard/ID3/id3cover.JPEG` 会复用、清零、半写入,跨 Music/Launcher/SystemUI 都要防 |
| 全局媒体键路由 | Launcher 卡片点击可能先被系统媒体 session 路由影响,需看 `MediaSessionService` 日志 |
## 16. 构建、部署、底层日志
常用构建:
```powershell
.\gradlew :YX_BluetoothSDK:assembleDebug --console plain
.\gradlew :YX_BlueTooth:assembleDebug --console plain
.\gradlew :YX_Music:assembleDebug --console plain
.\gradlew :YX_Voice:assembleDebug --console plain
```
交接说明里的部署注意点:
- 蓝牙音乐修改后,需手动打包更新到 `third_apps` 目录下。
- 配合顾凯 SDK 修改时,可以打开顾凯 SDK 日志:
```shell
adb shell setprop persist.bluetooth.btsnoopenable true
```
日志生成位置:
```text
/data/misc/bluetooth/logs
```
烧录文档补充:
- `pac` 格式通过 `SDToolBox -> SDFactoryTool` 烧录。
- `zip` 格式为 U 盘升级,将 `totalUpdate.zip` 放 U 盘根目录,系统弹窗升级。
## 17. 参考资料
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙模块流程概览.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\bluetooth_git_summary.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块交接说明.docx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块主流程.xlsx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙框架原理以及数据通讯交互流程.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙电话交互流程解析.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目软件烧录文档.pdf`
## 18. 快速入口清单
### SDK
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtSettingManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtPhoneManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtContactsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtCallRecordsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtInfoPushManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothConnectListenerClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothHfpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothPbapClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\MediaBrowserClient.java`
### 蓝牙电话应用
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothPhoneActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\ContactsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\CallLogsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\ContactListViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\CallHistoryViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BluetoothPhoneViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\service\BluetoothService.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\provider\BluetoothContactsProvider.java`
### 音乐、语音和外围
- `F:\zdCode\xugong\YX_Music\src\music\java\com\yaxon\musicplay\view\music\BluetoothMusicFragment.java`
- `F:\zdCode\xugong\YX_Music\src\main\java\com\yaxon\musicplay\viewmodel\BluetoothMusicViewModel.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\protocol\VoiceProtocol.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\BlueToothHelper.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\receiver\BluetoothMonitorReceiver.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\VoiceEventHelper.java`
- `F:\zdCode\xugong\YX_Launcher\src\main\java\com\yaxon\launcher\view\WidgetsMedia.java`
- `F:\zdCode\xugong\YX_SystemUI\src\main\java\com\android\systemui\sdk\view\navigation\MediaControlView.java`