first commit
This commit is contained in:
commit
669f49b1d6
342 files changed
+320811
No files matched your search
@@ -0,0 +1,544 @@
|
||||
# 蓝牙模块开发熟悉手册
|
||||
|
||||
> 整理日期: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`
|
||||
Reference in new issue
Block a user