30 KiB
蓝牙模块开发熟悉手册
整理日期: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《蓝牙框架原理以及通讯数据交互流程》给出的系统层级可落到项目代码中:
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. 启动与初始化
蓝牙电话应用开机常驻入口:
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.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.javaYX_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、连接名、连接状态,并回调onConnectnotifyBluetoothDisconnect():清理蓝牙音乐信息,写入断开状态,并回调onDisconnectsetBondState():处理配对状态setPairingRequest():处理配对请求
5.3 HFP 与 A2DP 的关系
交接说明里特别强调:蓝牙连接包含 hfp(电话)和 a2dp(音乐/音频连接);通讯录同步需要再连 pbap,同步完成后需要断开 pbap。
6. 通讯录与通话记录同步(PBAP)
PBAP 是 Phone Book Access Profile,用于从手机拉取通讯录和通话记录。本项目中通讯录和通话记录共享一次 PBAP 同步窗口:联系人完成、通话记录完成后,才认为本轮电话本下载整体完成,并主动断开 PBAP,避免阻塞和卡死。
6.1 主流程
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《蓝牙电话交互流程解析》给出的拨号链路,对应当前项目可理解为:
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 状态回调链
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.javaYX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtPhoneManager.javaYX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothHfpClient.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BluetoothPhoneViewModel.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothPhoneActivity.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/TelephoneWindowControl.javaYX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/ThirdCallingControl.javaYX_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 主流程
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 封面高风险点
蓝牙音乐封面风险集中在固定路径:
/sdcard/ID3/id3cover.JPEG
问题模式:
- 系统/底层复用同一个封面路径。
- 歌曲元数据先到,封面路径后到。
- 文件可能短时间为 0 字节或半写入。
- 空
albumUrl或无效文件可能覆盖当前有效封面。
排查关键字:
albumUrl = /sdcard/ID3/id3cover.JPEGlength = 0Failed to create image decoder获取bitmap为nullskip invalid album fileloadFileImageToView success
9. 语音与蓝牙交互
9.1 语音开关蓝牙
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.javaYX_Voice/src/main/java/com/yaxon/voice/helper/BlueToothHelper.javaYX_Voice/src/main/java/com/yaxon/voice/receiver/BluetoothMonitorReceiver.javaYX_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. 开发路线图
建议按这个顺序熟悉:
- 先读现有文档:
蓝牙模块流程概览.md、蓝牙框架原理以及数据通讯交互流程.pdf、蓝牙电话交互流程解析.pdf、徐工项目蓝牙模块交接说明.docx。 - 看
YX_BluetoothSDK分层:先manager,再modules,最后platform/audio/database。 - 跟一遍 PBAP:从
ContactsFragment.phoneBookPullRequest()跟到BluetoothPbapClient.phoneBookPullRequest(),再跟回onContactsPullCompleted()。 - 跟一遍 HFP:从
BluetoothPhoneViewModel.acceptCall/rejectCall或BtPhoneControlManager.dialing()跟到BluetoothHfpClient。 - 跟一遍蓝牙音乐:从
BluetoothMusicFragment跟到BtMusicControlManager、BtMusicManager、BtMusicAudioManager、BluetoothAvrcpClient。 - 跟语音开关蓝牙:从
VoiceProtocol.handleBluetooth()跟到BlueToothHelper和BluetoothMonitorReceiver。 - 看外围联动:
BtInfoPushManager写BT_MUSIC_INFO/BT_INFO,再看 Launcher/SystemUI 的读取和显示。 - 最后看历史问题:
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. 构建、部署、底层日志
常用构建:
.\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 日志:
adb shell setprop persist.bluetooth.btsnoopenable true
日志生成位置:
/data/misc/bluetooth/logs
烧录文档补充:
pac格式通过SDToolBox -> SDFactoryTool烧录。zip格式为 U 盘升级,将totalUpdate.zip放 U 盘根目录,系统弹窗升级。
17. 参考资料
F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙模块流程概览.mdF:\AI\Obsidian\项目文档\徐工\蓝牙\bluetooth_git_summary.mdF:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块交接说明.docxF:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块主流程.xlsxF:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙框架原理以及数据通讯交互流程.pdfF:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙电话交互流程解析.pdfF:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目软件烧录文档.pdf
18. 快速入口清单
SDK
F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtSettingManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtPhoneManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtContactsManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtCallRecordsManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtInfoPushManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothConnectListenerClient.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothHfpClient.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothPbapClient.javaF:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.javaF:\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.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothPhoneActivity.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\ContactsFragment.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\CallLogsFragment.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\ContactListViewModel.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\CallHistoryViewModel.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BluetoothPhoneViewModel.javaF:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\service\BluetoothService.javaF:\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.javaF:\zdCode\xugong\YX_Music\src\main\java\com\yaxon\musicplay\viewmodel\BluetoothMusicViewModel.javaF:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\protocol\VoiceProtocol.javaF:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\BlueToothHelper.javaF:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\receiver\BluetoothMonitorReceiver.javaF:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\VoiceEventHelper.javaF:\zdCode\xugong\YX_Launcher\src\main\java\com\yaxon\launcher\view\WidgetsMedia.javaF:\zdCode\xugong\YX_SystemUI\src\main\java\com\android\systemui\sdk\view\navigation\MediaControlView.java