23 KiB
蓝牙音乐模块功能流程梳理
更新时间:2026-07-13
工程路径:F:\zdCode\tongli
当前蓝牙音乐提交:8ec79048 modify(YX_BlueTooth):新增蓝牙音乐功能
1. 一句话结论
蓝牙音乐模块不是本地音乐播放器,而是 车机作为 A2DP Sink 接收手机音频 + 作为 AVRCP Controller 控制手机播放器。
也就是说:
- 音频从手机通过蓝牙 A2DP 传到车机。
- 播放、暂停、上一首、下一首通过 AVRCP 控制手机端播放器。
- 歌名、歌手、播放进度、总时长来自手机蓝牙媒体会话回调。
- 当前 UI 按老项目效果固定显示默认蓝牙音乐封面,不加载手机专辑图。
2. 模块边界
2.1 负责的功能
| 功能 | 当前实现 |
|---|---|
| 蓝牙音乐入口 | 主界面左侧“蓝牙音乐”菜单进入 BtMusicFragment |
| 独立音乐页 | BluetoothMusicActivity 已注册,但当前主要入口是主界面 Fragment |
| A2DP 状态 | 判断 A2DP 是否连接,未连接时显示连接提示 |
| A2DP 自动连接 | 已有手机设备且 A2DP 未连接时,尝试连接 A2DP |
| 播放控制 | 播放、暂停、上一首、下一首 |
| 歌曲信息 | 歌名、歌手、当前时间、总时长、进度 |
| 音频焦点 | 播放前申请蓝牙音乐焦点,焦点丢失时暂停/静音 |
| UI 对齐 | 已按老项目蓝牙音乐页 UI 对齐 |
2.2 不负责的功能
| 不负责项 | 说明 |
|---|---|
| 本地音乐扫描 | 不扫描 U 盘、本地文件或歌单 |
| 本地解码播放 | 不直接解码 mp3/flac 等媒体文件 |
| 手机歌单浏览 | 当前只做基础播放控制,不展示手机媒体库列表 |
| 专辑封面展示 | 当前按老项目固定显示默认蓝牙音乐图 |
| 蓝牙配对搜索完整流程 | 属于蓝牙设置模块 |
3. 关键代码入口
| 层级 | 文件 | 作用 |
|---|---|---|
| 主入口 | YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java |
左侧菜单切换到蓝牙音乐页 |
| Fragment UI | YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/BtMusicFragment.java |
主界面内的蓝牙音乐页面 |
| 独立 Activity | YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothMusicActivity.java |
独立蓝牙音乐页面 |
| ViewModel | YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BtMusicViewModel.java |
UI 与 SDK 之间的桥接层 |
| 控制门面 | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtMusicControlManager.java |
对外提供蓝牙音乐控制 API |
| 音乐管理 | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtMusicManager.java |
处理 AVRCP、A2DP、回调分发 |
| AVRCP 客户端 | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothAvrcpClient.java |
连接系统蓝牙媒体服务,发送播放控制 |
| MediaBrowser | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/MediaBrowserClient.java |
连接 com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService |
| 音频焦点 | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/audio/BtMusicAudioManager.java |
申请/释放音频焦点,处理焦点变化 |
| A2DP 设置 | YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtSettingControlManager.java |
判断/连接 A2DP |
4. 总体架构流程
这一节先看分层,再看两条主链路:控制链路 和 回调链路。这样比一张横向大图更清楚。
4.1 分层架构图
图里只保留层级关系,详细类名放到下方表格,避免图过大。
flowchart TB
A["页面层"]
B["ViewModel"]
C["SDK 管理层"]
D["系统蓝牙服务"]
E["手机端"]
A --> B
B --> C
C --> D
D --> E
| 层级 | 关键类 | 作用 |
|---|---|---|
| UI | BtMusicFragment / BluetoothMusicActivity |
显示界面,接收按钮点击 |
| ViewModel | BtMusicViewModel |
连接 UI 和 SDK,分发 LiveData |
| SDK 控制 | BtMusicControlManager / BtSettingControlManager |
对外提供播放控制、A2DP 连接接口 |
| 业务管理 | BtMusicManager |
组织 AVRCP、A2DP、焦点和回调 |
| AVRCP 控制 | BluetoothAvrcpClient / MediaBrowserClient |
控制手机播放器,接收媒体状态 |
| A2DP 音频 | BluetoothA2dpSinkClient |
接收手机蓝牙音频 |
| 音频焦点 | BtMusicAudioManager / AudioManager |
申请焦点、静音/解除静音、处理焦点丢失 |
| 系统蓝牙 | BluetoothMediaBrowserService / A2DP Sink Service |
Android 蓝牙系统服务 |
| 手机 | 手机音乐播放器 / 手机蓝牙音频 | 真正播放音乐并输出 A2DP 音频 |
SDK 管理层内部再分三条线:
| 内部链路 | 关键类 | 解决的问题 |
|---|---|---|
| AVRCP 控制 | BtMusicManager / BluetoothAvrcpClient / MediaBrowserClient |
播放、暂停、上一首、下一首 |
| A2DP 音频 | BtSettingControlManager / BluetoothA2dpSinkClient |
建立手机到车机的音频通道 |
| 音频焦点 | BtMusicAudioManager / AudioManager |
决定蓝牙音乐能不能出声、是否要暂停或静音 |
4.2 播放控制链路
用户点按钮时,命令从上往下走,最后控制手机播放器。
flowchart TB
A["用户操作"]
B["页面处理"]
C["ViewModel 转发"]
D["SDK 控制"]
E["系统媒体控制"]
F["手机播放器"]
A --> B
B --> C
C --> D
D --> E
E --> F
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 用户操作 | iv_bt_music_state / iv_bt_music_pre / iv_bt_music_next |
用户点击播放控制按钮 |
| 页面处理 | BtMusicFragment.onClick / BluetoothMusicActivity.onClick |
判断操作类型 |
| ViewModel 转发 | BtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusic |
UI 不直接碰 SDK |
| SDK 控制 | BtMusicControlManager / BtMusicManager |
申请焦点并组织 AVRCP 命令 |
| 系统媒体控制 | BluetoothAvrcpClient / MediaBrowserClient / TransportControls |
调用系统蓝牙媒体控制 |
| 手机播放器 | 手机音乐 App | 最终执行播放、暂停、切歌 |
4.3 状态回调链路
手机端播放状态变化后,状态从下往上回到 UI。
flowchart TB
A["手机播放器"]
B["系统媒体服务"]
C["SDK 回调处理"]
D["ViewModel LiveData"]
E["页面刷新"]
A --> B
B --> C
C --> D
D --> E
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 手机播放器 | 手机音乐 App | 歌曲、进度、播放状态变化 |
| 系统媒体服务 | BluetoothMediaBrowserService |
Android 蓝牙 AVRCP Controller 媒体服务 |
| SDK 回调处理 | MediaBrowserClient / BluetoothAvrcpClient / BtMusicManager |
接收并转换媒体状态 |
| ViewModel LiveData | BtMusicViewModel.mLiveDataMusicInfo / mLiveDataPlayState |
把状态推给 UI |
| 页面刷新 | BtMusicFragment / BluetoothMusicActivity |
刷新按钮、歌名、时间、进度 |
4.4 A2DP 与音频焦点链路
这条链路决定车机是否能真正出声。
先看 A2DP 音频通道:
flowchart TB
A["进入音乐页"]
B["检查 A2DP"]
C["连接手机 A2DP"]
D["音频通道就绪"]
E["车机扬声器输出"]
A --> B
B --> C
C --> D
D --> E
再看音频焦点:
flowchart TB
A["点击播放"]
B["申请音频焦点"]
C["解除 A2DP 静音"]
D["发送播放命令"]
E["手机开始播放"]
A --> B
B --> C
C --> D
D --> E
| 链路 | 成功条件 | 失败表现 |
|---|---|---|
| A2DP 音频通道 | 手机 A2DP 已连接或连接成功 | UI 保持未连接,车机无蓝牙音乐声 |
| 音频焦点 | requestAudioFocus 成功 |
手机可能在播,但车机可能无声或被其他音源压住 |
| AVRCP 控制 | TransportControls 可用 |
播放/暂停/切歌命令可能无效 |
核心理解:
- UI 不直接操作系统蓝牙。
- UI 只调用
BtMusicViewModel。 BtMusicViewModel调BtMusicControlManager和BtSettingControlManager。- 真正播放控制走
BluetoothAvrcpClient -> MediaBrowserClient -> TransportControls。 - 真正音频通道走 A2DP Sink。
- 真正能不能出声,还取决于 A2DP 是否连接和音频焦点是否申请成功。
5. 初始化流程
蓝牙 SDK 初始化在应用启动时完成。
flowchart TD
A["应用启动"]
B["初始化蓝牙 SDK"]
C["创建音乐管理器"]
D["准备控制与音频能力"]
E["注册媒体回调"]
A --> B
B --> C
C --> D
D --> E
涉及文件:
BluetoothApplication.javaBluetoothSdkApplication.javaBtMusicControlManager.javaBtMusicManager.javaBluetoothAvrcpClient.javaMediaBrowserClient.javaBtMusicAudioManager.java
初始化细节:
| 步骤 | 代码定位 | 结果 |
|---|---|---|
| 应用启动 | BluetoothApplication |
进入蓝牙 SDK 应用基类 |
| 初始化蓝牙 SDK | BluetoothSdkApplication.setPlatform |
初始化各蓝牙业务模块 |
| 创建音乐管理器 | BtMusicControlManager.init |
持有 BtMusicManager |
| 准备控制与音频能力 | BluetoothAvrcpClient / BtMusicAudioManager / BluetoothA2dpSinkClient |
准备 AVRCP、焦点、A2DP |
| 注册媒体回调 | MediaBrowserClient |
连接系统媒体服务并监听状态 |
6. 入口切换流程
主界面左侧菜单有四个入口:
| 位置 | 入口 |
|---|---|
| 0 | 蓝牙拨号 |
| 1 | 联系人 |
| 2 | 蓝牙音乐 |
| 3 | 蓝牙设置 |
Constant.TAB_BLUETOOTH_MUSIC = 2。
flowchart TD
A["点击蓝牙音乐"]
B["切换菜单位置"]
C["更新左侧选中态"]
D["切到音乐 Fragment"]
E["显示音乐页面"]
A --> B
B --> C
C --> D
D --> E
关键代码:
MainActivity.setPositionFragmentConstant.TAB_BLUETOOTH_MUSICBtMusicFragment
入口代码定位:
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 点击蓝牙音乐 | MainActivity.onItemClick |
左侧菜单点击 |
| 切换菜单位置 | setPositionFragment(position = 2) |
2 对应蓝牙音乐 |
| 更新左侧选中态 | mMenuAdapter.setSelectedPosition |
菜单高亮 |
| 切到音乐 Fragment | toFragment(mBtMusicFragment) |
页面容器替换 |
| 显示音乐页面 | fragment_bt_music.xml |
展示蓝牙音乐 UI |
7. 页面 UI 状态流程
蓝牙音乐 UI 由 isBtConnect 控制两种状态。
flowchart TD
A["进入音乐页"]
B{"A2DP 已连接"}
C["音乐控制面板"]
D["未连接提示"]
A --> B
B -->|是| C
B -->|否| D
UI 文件:
fragment_bt_music.xmlactivity_bluetooth_music.xml
主要控件:
| 控件 ID | 作用 |
|---|---|
iv_bt_music_album |
默认蓝牙音乐封面 |
tv_bt_music_title |
歌曲名 |
tv_bt_music_artist |
歌手 |
seek_bt_music |
播放进度展示,不允许拖动 |
tv_cur_time |
当前播放时间 |
tv_song_time |
总时长 |
iv_bt_music_pre |
上一首 |
iv_bt_music_state |
播放 / 暂停 |
iv_bt_music_next |
下一首 |
ll_bt_connect_panel |
未连接提示页 |
tv_connect_btn |
连接蓝牙按钮,目前未实现跳转 |
状态对应关系:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 音乐控制面板 | 默认封面、歌名、歌手、进度条、三个控制按钮 | isA2dpConnected == true |
| 未连接提示 | 连接手机提示、连接蓝牙按钮 | isA2dpConnected == false |
8. A2DP 连接流程
A2DP 是手机音乐音频传到车机的通道。
进入页面后的检查:
flowchart TD
A["进入音乐页"]
B["检查 A2DP"]
C["获取手机设备"]
D["发起 A2DP 连接"]
E["等待连接结果"]
A --> B
B --> C
C --> D
D --> E
分支规则:
| 判断点 | 是 | 否 |
|---|---|---|
| A2DP 是否已连接 | 显示音乐控制面板 | 继续获取已连接手机设备 |
| 已连接设备是否为手机 | 检查 A2DP 是否正在连接 | 保持未连接状态 |
| A2DP 是否正在连接 | 等待连接结果 | 调用 connectA2dp(mac) |
调用定位:
| 步骤 | 代码定位 |
|---|---|
| 进入音乐页 | BtMusicFragment.onResume / BluetoothMusicActivity.onResume |
| 检查 A2DP | BtMusicViewModel.isA2dpConnected |
| 获取手机设备 | BtMusicFragment.btMusicInfo / BluetoothMusicActivity.btMusicInfo |
| 发起连接 | BtMusicViewModel.connectA2dp |
| 底层连接 | BtSettingControlManager.connectA2dp / BluetoothA2dpSinkClient |
触发位置:
BtMusicFragment.btMusicInfoBluetoothMusicActivity.btMusicInfoBtMusicViewModel.connectA2dpBtSettingControlManager.connectA2dp
连接结果回调:
flowchart TD
A["A2DP 状态变化"]
B["SDK 连接监听"]
C["音乐管理器处理"]
D["LiveData 通知"]
E["UI 切换状态"]
A --> B
B --> C
C --> D
D --> E
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| A2DP 状态变化 | 系统蓝牙服务 | A2DP 连接或断开 |
| SDK 连接监听 | BluetoothConnectListenerClient |
接收系统连接状态 |
| 音乐管理器处理 | BtMusicManager.onA2dpConnect / onA2dpDisconnect |
清理或恢复蓝牙音乐状态 |
| LiveData 通知 | BtMusicViewModel.mLiveDataA2dpConnectStatus |
通知 UI |
| UI 切换状态 | BtMusicFragment / BluetoothMusicActivity |
显示音乐页或未连接页 |
9. 播放控制流程
9.1 点击播放 / 暂停
flowchart TD
A["点击按钮"]
B{"当前在播放"}
C["走暂停链路"]
D["走播放链路"]
E["手机播放器响应"]
A --> B
B -->|是| C
B -->|否| D
C --> E
D --> E
| 分支 | 调用顺序 | 说明 |
|---|---|---|
| 播放 | onClick -> isMusicPlaying == false -> playBtMusic -> requestFocus -> AVRCP play |
先拿焦点,再发送播放 |
| 暂停 | onClick -> isMusicPlaying == true -> pauseBtMusic -> AVRCP pause |
暂停不需要重新申请焦点 |
| UI 更新 | mLiveDataPlayState -> updatePlayState |
最终以回调状态刷新图标 |
9.2 点击上一首 / 下一首
flowchart TD
A["点击切歌"]
B["ViewModel 转发"]
C["SDK 申请焦点"]
D["发送 AVRCP 命令"]
E["手机播放器切歌"]
A --> B
B --> C
C --> D
D --> E
底层实际命令:
| 操作 | 页面入口 | ViewModel | 最终调用 |
|---|---|---|---|
| 播放 | onClick |
playBtMusic |
TransportControls.play() |
| 暂停 | onClick |
pauseBtMusic |
TransportControls.pause() |
| 下一首 | onClick |
nextBtMusic |
TransportControls.skipToNext() |
| 上一首 | onClick |
preBtMusic |
TransportControls.skipToPrevious() |
10. 歌曲信息与播放状态刷新流程
手机通过系统蓝牙 MediaBrowser 回传媒体状态。
播放状态回调:
flowchart TD
A["手机播放状态变化"]
B["系统媒体回调"]
C["SDK 转换状态"]
D["LiveData 通知"]
E["刷新按钮和进度"]
A --> B
B --> C
C --> D
D --> E
歌曲信息回调:
flowchart TD
A["手机歌曲变化"]
B["系统元数据回调"]
C["SDK 转换歌曲信息"]
D["LiveData 通知"]
E["刷新歌名和时长"]
A --> B
B --> C
C --> D
D --> E
代码定位:
| 回调类型 | SDK 入口 | ViewModel 入口 | UI 刷新内容 |
|---|---|---|---|
| 播放状态 | onPlaybackStateChanged |
onPlayMusicState / onPlayMusicInfo |
播放按钮、当前进度、当前时间 |
| 歌曲信息 | onMetadataChanged |
onPlayMusicInfo |
歌名、歌手、总时长 |
字段来源:
| 字段 | 来源 |
|---|---|
| 歌名 | MediaMetadata.METADATA_KEY_TITLE |
| 歌手 | MediaMetadata.METADATA_KEY_ARTIST |
| 专辑 | MediaMetadata.METADATA_KEY_ALBUM |
| 总时长 | MediaMetadata.METADATA_KEY_DURATION |
| 当前进度 | PlaybackStateCompat.getPosition() |
| 播放状态 | PlaybackStateCompat.STATE_PLAYING |
当前 UI 特殊点:
albumUrl虽然会接收,但 UI 不展示手机封面。- 默认封面固定使用
R.mipmap.icon_bluetooth_music。
11. 音频焦点流程
蓝牙音乐播放前需要申请音频焦点,否则可能出现手机在播但车机无声或被其他音源压住。
申请焦点:
flowchart TD
A["准备播放"]
B["确认 A2DP"]
C["请求音频焦点"]
D["解除 A2DP 静音"]
E["允许播放命令"]
A --> B
B --> C
C --> D
D --> E
失败规则:
| 失败点 | 结果 |
|---|---|
| A2DP 未连接 | requestFocus 返回 false |
| 音频焦点申请失败 | 不应认为蓝牙音乐已经有效播放 |
| MediaSession 未就绪 | AVRCP 控制可能无效 |
焦点变化:
flowchart TD
A["焦点变化"]
B["静音或恢复 A2DP"]
C["暂停或恢复播放"]
D["刷新音乐状态"]
A --> B
B --> C
C --> D
焦点变化处理:
| 焦点事件 | 当前处理 |
|---|---|
AUDIOFOCUS_GAIN |
解除 A2DP 静音;如果丢焦前在播,则恢复播放 |
AUDIOFOCUS_LOSS |
释放焦点;暂停蓝牙音乐;清空 Launcher 音乐信息 |
AUDIOFOCUS_LOSS_TRANSIENT |
A2DP 静音;记录是否正在播放;暂停 |
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK |
降低 A2DP 音量 |
12. 断开与关闭流程
flowchart TD
A["连接断开"]
B["SDK 收到回调"]
C["清空音乐缓存"]
D["通知 UI 未连接"]
E["显示未连接面板"]
A --> B
B --> C
C --> D
D --> E
相关回调:
onDisconnectonA2dpDisconnectonCloseonBtDisconnectonBtClose
清理内容:
| 清理项 | 目的 |
|---|---|
title / artist / album |
避免断开后继续显示旧歌曲 |
progress / duration |
避免进度条停在旧状态 |
| 播放状态 | 播放按钮恢复为播放图标 |
mLiveDataA2dpConnectStatus = false |
UI 切回未连接面板 |
13. 当前实现中的关键状态
| 状态 | 位置 | 含义 |
|---|---|---|
mLastPlayState |
BtMusicFragment / BluetoothMusicActivity |
记录上次播放状态,用于页面恢复时决定是否续播 |
musicPlaying |
BtMusicViewModel |
音频焦点丢失前是否在播放 |
mMusicInfo |
BtMusicViewModel |
最近一次音乐信息缓存 |
mLiveDataMusicInfo |
BtMusicViewModel |
歌曲信息 UI 通知 |
mLiveDataPlayState |
BtMusicViewModel |
播放状态 UI 通知 |
mLiveDataA2dpConnectStatus |
BtMusicViewModel |
A2DP 连接状态 UI 通知 |
isFocus |
BtMusicAudioManager |
蓝牙音乐是否持有音频焦点 |
isPlayingLossFocus |
BtMusicAudioManager |
丢失焦点前是否在播放 |
14. 和老项目对齐的现状
已对齐:
- 主界面增加“蓝牙音乐”入口。
- 蓝牙音乐 UI 使用老项目默认封面和圆形控制按钮。
- 进度条资源复用老项目。
- 封面固定使用默认蓝牙音乐图,不使用手机专辑图。
- 歌名、歌手、进度、时间通过现有 SDK 刷新。
- 播放、暂停、上一首、下一首继续走新项目 SDK,不搬旧项目业务代码。
未完全补齐:
- 未连接状态下
tv_connect_btn目前没有跳转蓝牙设置页。 - 没有“连接中”状态,例如 A2DP 正在连接时的进度提示。
- MediaBrowser 未连接时没有明显错误提示。
- 播放控制失败没有 UI 反馈。
- Fragment 与 Activity 两套蓝牙音乐逻辑重复,后续可抽公共逻辑。
15. 开发切入点
15.1 要改 UI
优先看:
fragment_bt_music.xmlactivity_bluetooth_music.xmlselector_music_play.xmlselector_music_pause.xmlselector_music_pre.xmlselector_music_next.xmlbt_music_progress_layer.xml
15.2 要改播放控制
优先看:
BtMusicFragment.onClickBluetoothMusicActivity.onClickBtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusicBtMusicControlManagerBtMusicManagerBluetoothAvrcpClient
15.3 要改连接逻辑
优先看:
BtMusicFragment.btMusicInfoBluetoothMusicActivity.btMusicInfoBtMusicViewModel.connectA2dpBtSettingControlManager.connectA2dpBluetoothA2dpSinkClient
15.4 要改音频焦点
优先看:
BtMusicAudioManager.requestAudioFocusBtMusicAudioManager.releaseAudioFocusBtMusicAudioManager.mAudioFocusListenerBtMusicManager.requestFocus
16. 建议补齐的功能
| 优先级 | 功能 | 原因 |
|---|---|---|
| P0 | 未连接按钮跳转蓝牙设置页 | 用户当前只有蓝牙音乐入口,通过设置页跳转时需要闭环 |
| P0 | A2DP 连接中状态 | 避免点击后无反馈 |
| P1 | MediaBrowser 未连接提示 | 手机蓝牙媒体服务未就绪时便于定位 |
| P1 | 播放控制失败日志/提示 | AVRCP 命令可能因手机端播放器未启动而无效 |
| P1 | Fragment 和 Activity 逻辑复用 | 降低后续维护成本 |
| P2 | 进度条拖动 seek | 当前只展示,不支持拖动 |
| P2 | 专辑图开关 | 如果后续产品要求展示手机专辑图,可恢复 albumUrl 加载 |
17. 测试清单
17.1 正常流程
- 手机已配对并连接 HFP。
- 进入蓝牙音乐页。
- 自动检查并连接 A2DP。
- 手机打开音乐播放器。
- 点击播放,车机出声。
- 点击暂停,手机播放器暂停。
- 点击上一首/下一首,手机播放器切歌。
- 歌名、歌手、时间、进度刷新。
17.2 连接异常
- 未连接手机进入蓝牙音乐页,应显示未连接面板。
- 手机只连 HFP 未连 A2DP,进入音乐页应尝试连接 A2DP。
- A2DP 连接失败,应能从日志看到
connectA2dp相关信息。 - 手机断开蓝牙,UI 应切回未连接状态。
17.3 焦点与打断
- 蓝牙音乐播放时来电,音乐应暂停或静音。
- 通话结束后,如果丢焦前在播,应尝试恢复。
- 切到收音机/USB 音乐,蓝牙音乐应释放或失去焦点。
- 重新切回蓝牙音乐,按状态恢复播放。
17.4 兼容性
- 不同手机品牌测试 AVRCP 元数据。
- 手机音乐 App 未打开时点击播放。
- 手机音乐 App 开着但无播放队列。
- 蓝牙断开后快速重连。
- 系统重启后首次进入蓝牙音乐。
18. 日志关键字
调试时可优先过滤这些关键字:
BtMusicFragment
BluetoothMusicActivity
BtMusicViewModel
BtMusicControlManager
BtMusicManager
BtMusicAudioManager
BluetoothAvrcpClient
MediaBrowserHelper
connectA2dp
onA2dpConnect
onA2dpDisconnect
onPlaybackStateChanged
onMetadataChanged
requestAudioFocus
AUDIOFOCUS_GAIN
AUDIOFOCUS_LOSS
19. 总结
蓝牙音乐模块可以按四条主线理解:
- 入口线:
MainActivity -> BtMusicFragment。 - 连接线:
BtMusicFragment -> BtMusicViewModel -> BtSettingControlManager -> A2DP。 - 控制线:
按钮点击 -> BtMusicViewModel -> BtMusicControlManager -> BtMusicManager -> BluetoothAvrcpClient -> 手机播放器。 - 回调线:
手机媒体状态 -> MediaBrowserClient -> BluetoothAvrcpClient -> BtMusicManager -> BtMusicViewModel LiveData -> UI。
开发这个模块时,优先保证 A2DP 连接、AVRCP 控制、音频焦点三条链路可验证,再做 UI 微调和异常状态补齐。