TWS双耳功能
TWS(True Wireless Stereo,真无线立体声)双耳功能是杰理蓝牙 SDK 与 Android 应用端协同实现的整套双耳设备能力,涵盖 TWS 连接状态感知、ADV 广播信息同步、设备设置信息读写、按键/灯光/降噪等功能的远程配置,以及左右耳地理位置同步等场景。
目的与范围
本文档讲解 Android-JL_Bluetooth 工程中 TWS 双耳功能的完整实现机制,包括:
- App 层如何通过蓝牙事件回调链感知 TWS 连接状态(
BTEventCallbackManager) - SDK 层
RCSPController/ITwsOp提供的 TWS 相关 API 及其用法 TwsDemo展示的标准调用模式(获取 ADV 信息、控制广播、修改设置、同步时间等)- TWS 状态变化时的设备信息同步(左右耳经纬度等)
以下主题属于其他页面范畴,本文档只做指引、不展开:双连接(double_connect)能力、设备搜索流程(SearchDevicePresenter/SearchDeviceViewModel)、定位服务(LocationHelper)的完整实现。本文聚焦于 TWS 状态与 ADV 信息这条主链路的端到端机制。
概述
TWS 双耳设备(典型如左右耳独立收纳的蓝牙耳机)由一只主耳(Main)与一只副耳(Sub)组成。手机蓝牙仅与主耳建立经典蓝牙连接,副耳通过私有协议与主耳组网。对 App 而言,"TWS 是否已连接" 并非系统蓝牙 API 直接给出的状态,而是需要通过杰理私有 RCSP 协议中的 ADV 通知信息(NotifyAdvInfoCmd / ADVInfoResponse)来推断:
当通知信息中左耳数量(
getLeftDeviceQuantity())与右耳数量(getRightDeviceQuantity())均大于 0 时,判定 TWS 已连接。
这一判定结果通过 onTwsStatusChange(device, isTwsConnected) 事件逐层分发到 UI(设备搜索页、播放控制弹窗)与业务模块(定位助手、历史设备管理)。
围绕 TWS 状态,SDK 还提供一整套设备信息管理能力:通过掩码(mask)批量读取设备设置(getDeviceSettingsInfo)、按功能类型修改设置(modifyDeviceSettingsInfo)、开启/关闭设备信息广播(controlAdvBroadcast)、同步接入时间、配置设备名称/按键/灯光,以及噪声处理模式(ANC、智能免摘、场景降噪、风噪检测)的读写。
架构
flowchart TD
subgraph sg_Device["TWS 设备端"]
Main["主耳 Main(经典蓝牙连接)"]
Sub["副耳 Sub(私有协议组网)"]
Main <-->|"私有 TWS 协议"| Sub
end
subgraph sg_SDK["杰理蓝牙 SDK 层"]
RCSP["RCSPController<br/>(实现 ITwsOp 接口)"]
BTManager["JL_BluetoothManager<br/>sendCommandAsync"]
CmdBuilder["CommandBuilder<br/>buildGetADVInfoCmd"]
Cmd["GetADVInfoCmd / NotifyAdvInfoCmd"]
CmdBuilder --> Cmd
BTManager --> Cmd
end
subgraph sg_App["Android App 层"]
EventMgr["BTEventCallbackManager<br/>(TWS 状态判定)"]
Callback["BTEventCallback<br/>onTwsStatusChange"]
DeviceStatus["DeviceStatusManager<br/>(ADV 缓存)"]
Consumers["UI / 业务消费方<br/>搜索页 · 播放弹窗 · 定位助手"]
EventMgr --> DeviceStatus
EventMgr --> Callback
Callback --> Consumers
end
Main -->|"RCSP 协议链路"| BTManager
BTManager -->|"命令响应/主动通知"| EventMgr
RCSP -->|"API 调用"| BTManager
Consumers --> RCSP
架构说明:
- 设备端:主耳承担与手机的经典蓝牙连接,副耳通过私有 TWS 协议与主耳保持同步;左右耳数量信息通过 ADV 广播上报给手机。
- SDK 层:
RCSPController(单例)是 App 与设备交互的门面,实现ITwsOp接口定义的全部 TWS 能力;JL_BluetoothManager.sendCommandAsync()负责将CommandBuilder构造的命令下发到设备并等待回复。 - App 层:
BTEventCallbackManager监听 SDK 的NotifyAdvInfoCmd主动通知,将其转换为ADVInfoResponse后与DeviceStatusManager中的缓存比对,仅在状态变化时触发onTwsStatusChange事件,避免 UI 无效刷新。
核心机制:TWS 状态判定与事件分发
数据来源:ADV 设备通知
TWS 状态并非 App 主动查询得到,而是设备在连接状态下通过 RCSP 命令 CMD_ADV_DEVICE_NOTIFY(NotifyAdvInfoCmd)主动推送的。BTEventCallbackManager 在收到该命令后,将通知参数转换为可比较的 ADVInfoResponse 对象:
case Command.CMD_ADV_DEVICE_NOTIFY:
NotifyAdvInfoCmd notifyAdvInfoCmd = (NotifyAdvInfoCmd) cmd;
NotifyAdvInfoParam advInfo = notifyAdvInfoCmd.getParam();
if (advInfo != null) {
ADVInfoResponse notifyAdvInfo = UIHelper.convertADVInfoFromBleScanMessage(UIHelper.convertBleScanMsgFromNotifyADVInfo(advInfo));
来源:BTEventCallbackManager.java
设计意图:设备广播信息(ADV)同时承载了左右耳数量、电量、功能配置版本等大量字段,将其统一转换为 ADVInfoResponse 后,App 层所有业务都可以用同一套对象模型处理,且可以直接与缓存对象做 equals 比较。
状态判定算法:左右耳数量
TWS 是否连接的判定基于 ADV 信息中的左右耳数量字段。判定逻辑同时考虑了缓存状态与新状态的比对,只有状态发生翻转时才触发回调:
ADVInfoResponse cacheAdvInfo = DeviceStatusManager.getInstance().getAdvInfo(device);
boolean isCacheTwsConnected = cacheAdvInfo != null && cacheAdvInfo.getLeftDeviceQuantity() > 0 && cacheAdvInfo.getRightDeviceQuantity() > 0;
if (!notifyAdvInfo.equals(cacheAdvInfo)) {
DeviceStatusManager.getInstance().updateDeviceAdvInfo(device, notifyAdvInfo);
boolean isTwsConnected = notifyAdvInfo.getLeftDeviceQuantity() > 0 && notifyAdvInfo.getRightDeviceQuantity() > 0;
JL_Log.d(TAG, "-onTwsStatus- isCacheTwsConnected : " + isCacheTwsConnected + ", isTwsConnected : " + isTwsConnected);
if (isCacheTwsConnected != isTwsConnected) {
// 如果TWS已连接,同步左右设备的经纬度
...
onTwsStatusChange(device, isTwsConnected);
}
}
来源:BTEventCallbackManager.java
这段代码体现了两层防护设计:
- 内容比对(
!notifyAdvInfo.equals(cacheAdvInfo)):即使收到通知,若内容与缓存完全一致(如电量未变),则跳过后续处理,避免无意义的写入与回调; - 状态翻转检测(
isCacheTwsConnected != isTwsConnected):只有 "未连接 → 已连接" 或 "已连接 → 断开" 的边界才触发事件,普通 ADV 更新(如电量变化)不会触发 TWS 状态事件。
TWS 连接时的附加动作:左右耳经纬度同步
当检测到 TWS 由未连接变为已连接时,App 会查找该设备的历史定位记录,将经纬度同时写入主耳(DEVICE_FLAG_MAIN)与副耳(DEVICE_FLAG_SUB)的历史设备信息中。这样后续无论是主耳还是副耳单独回连,都能拿到设备最后一次的定位数据。
事件分发链
BTEventCallbackManager 是 SDK 事件与 App 业务之间的总线。onTwsStatusChange 最终通过 handleBtCallback 在主线程上遍历所有已注册的 BTEventCallback 并逐个回调:
public void onTwsStatusChange(final BluetoothDevice device, final boolean isTwsConnected) {
handleBtCallback(new BtCallback() {
@Override
public void onCallback(BTEventCallback callback) {
callback.onTwsStatusChange(device, isTwsConnected);
}
});
}
来源:BTEventCallbackManager.java
基类回调定义于 BTEventCallback.java:
public void onTwsStatusChange(BluetoothDevice device, boolean isTwsConnected){
已知消费方包括:
| 消费方 | 文件 | 行为 |
|---|---|---|
| 设备搜索页(MVP) | SearchDevicePresenter | 通过 mView.onTwsStatus(device, isTwsConnected) 刷新搜索列表中的 TWS 状态展示 |
| 设备搜索页(MVVM) | SearchDeviceViewModel | 过滤目标设备后更新 TWS 状态 LiveData |
| 定位助手 | LocationHelper | 状态变化时调用 setNeedUpdateGpsDev(device),触发 GPS 数据更新与同步 |
| 播放控制弹窗 | PlaySoundCtrlDialog | 校验目标设备后刷新 TWS 连接相关 UI |
核心流程:TWS 状态上报端到端时序
sequenceDiagram
participant Dev as TWS 设备(主耳)
participant SDK as RCSPController / JL_BluetoothManager
participant Mgr as BTEventCallbackManager
participant Cache as DeviceStatusManager(ADV 缓存)
participant UI as 业务消费方(搜索页/定位助手等)
Dev->>SDK: NotifyAdvInfoCmd(CMD_ADV_DEVICE_NOTIFY)
SDK-->>Mgr: onDeviceCommand 分发
Mgr->>Mgr: 转换为 ADVInfoResponse
Mgr->>Cache: 读取缓存 ADV 信息
Cache-->>Mgr: cacheAdvInfo
Mgr->>Mgr: equals 比对(内容变化?)
alt 内容无变化
Mgr-->>Mgr: 跳过,不处理
else 内容有变化
Mgr->>Cache: updateDeviceAdvInfo(device, notifyAdvInfo)
Mgr->>Mgr: 判定左右耳数量 > 0 → isTwsConnected
alt 状态翻转(isCacheTwsConnected != isTwsConnected)
alt TWS 变为已连接
Mgr->>Mgr: 同步主/副耳经纬度(DEVICE_FLAG_MAIN / DEVICE_FLAG_SUB)
end
Mgr->>UI: onTwsStatusChange(device, isTwsConnected)
UI->>UI: 刷新 UI / 触发 GPS 更新
else 状态未翻转
Mgr-->>Mgr: 仅更新缓存,不触发事件
end
end
时序要点:整个链路是事件驱动的——App 从不轮询 TWS 状态,而是依赖设备侧在连接参数变化(左右耳入盒/出盒、配对成功/断开)时主动推送 ADV 通知。这种设计降低了功耗与空口占用,也是 controlAdvBroadcast 开启广播后才有持续通知流的原因。
使用示例
以下示例全部提取自工程内的 TwsDemo 测试用例(TwsDemo.java),展示了 TWS 功能的标准调用范式:获取门面对象 → 构造/携带参数 → 异步调用 → 回调处理。
示例一:通过 RCSPController 获取设备设置信息
public void getTwsInfo(int mask) {
//获取RCSPController对象
RCSPController controller = RCSPController.getInstance();
//mask = 0xffffffff; //获取所有属性
//执行获取设备设置信息功能并等待结果回调
controller.getDeviceSettingsInfo(controller.getUsingDevice(), mask, new OnRcspActionCallback<ADVInfoResponse>() {
@Override
public void onSuccess(BluetoothDevice device, ADVInfoResponse message) {
//成功回调
//message - 设置信息
}
@Override
public void onError(BluetoothDevice device, BaseError error) {
//失败回调
//error - 错误信息
}
});
}
来源:TwsDemo.java
这是 TWS 信息读取的推荐路径:RCSPController 内部已封装命令构造、超时、状态检查等细节,回调直接给出强类型的 ADVInfoResponse。mask 用于按位选择需要读取的属性集合,0xffffffff 表示全部。
示例二:通过 JL_BluetoothManager 手动构造命令(底层路径)
public void getTwsInfoV0(Context context, int mask) {
//获取JL_BluetoothManager对象
JL_BluetoothManager manager = JL_BluetoothManager.getInstance(context);
//构造功能命令
//mask = 0xffffffff; -- 获取所有属性
CommandBase getTwsInfoCmd = CommandBuilder.buildGetADVInfoCmd(mask);
//执行获取设备设置信息功能并等待结果回调
manager.sendCommandAsync(manager.getConnectedDevice(), getTwsInfoCmd, manager.getBluetoothOption().getTimeoutMs(), new RcspCommandCallback() {
@Override
public void onCommandResponse(BluetoothDevice device, CommandBase cmd) {
//检查设备状态
if (cmd.getStatus() != StateCode.STATUS_SUCCESS) {
onErrCode(device, new BaseError(ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS, "Device reply an bad status: " + cmd.getStatus()));
return;
}
//获取对应的命令数据
GetADVInfoCmd command = (GetADVInfoCmd) cmd;
//获取回复数据
boolean isHasResponse = command.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
|| command.getType() == CommandBase.FLAG_NO_PARAMETER_AND_RESPONSE;
if (isHasResponse) {
ADVInfoResponse response = command.getResponse();
if (null == response) {
onErrCode(device, new BaseError(ErrorCode.SUB_ERR_DATA_FORMAT, "Response data is error."));
return;
}
//处理设备回复数据
}
}
@Override
public void onErrCode(BluetoothDevice device, BaseError error) {
//失败回调
}
});
}
来源:TwsDemo.java
这是底层路径,展示了 RCSP 命令的完整生命周期:构造命令 → 异步发送 → 校验状态码 → 解析响应体。与示例一的差别在于它暴露了命令层细节,需要调用方自行处理 StateCode.STATUS_SUCCESS 校验、命令类型判断(是否有响应数据)以及 SUB_ERR_* 错误码。一般业务应优先使用示例一的高层封装。
示例三:控制 ADV 广播并监听设备广播消息
public void controlADVBroadcast(boolean enable) {
RCSPController controller = RCSPController.getInstance();
//注册蓝牙RCSP事件监听器
controller.addBTRcspEventCallback(new BTRcspEventCallback() {
@Override
public void onDeviceBroadcast(BluetoothDevice device, DevBroadcastMsg broadcast) {
//此处将会回调设备广播信息
}
});
//执行控制设备广播信息功能并等待结果回调
controller.controlAdvBroadcast(controller.getUsingDevice(), enable, new OnRcspActionCallback<Boolean>() {
@Override
public void onSuccess(BluetoothDevice device, Boolean message) {
//enable = true, 开启设备广播信息,数据将在BTRcspEventCallback#onDeviceBroadcast回调
//enable = false, 关闭设备广播信息
}
@Override
public void onError(BluetoothDevice device, BaseError error) {
//失败回调
}
});
}
来源:TwsDemo.java
设计意图:设备广播是按需开启的。开启后设备会周期性/事件性推送广播消息(电量、播放状态等),通过 onDeviceBroadcast 回调接收;关闭后停止推送以节省功耗。注意广播消息与上文 NotifyAdvInfoCmd 是两条独立的数据通路。
示例四:处理设备的主动请求操作
public void requestDeviceOperation() {
final RCSPController controller = RCSPController.getInstance();
controller.addBTRcspEventCallback(new BTRcspEventCallback() {
@Override
public void onTwsStatusChange(BluetoothDevice device, boolean isTwsConnected) {
//此处将会回调TWS连接状态
}
@Override
public void onDeviceRequestOp(BluetoothDevice device, int op) {
//此处将会回调设备请求操作
switch (op) {
case Constants.ADV_REQUEST_OP_UPDATE_CONFIGURE: //主动更新配置信息
break;
case Constants.ADV_REQUEST_OP_UPDATE_AFTER_REBOOT://更新配置信息,需要重启生效
break;
case Constants.ADV_REQUEST_OP_SYNC_TIME: //请求同步连接时间
break;
case Constants.ADV_REQUEST_OP_RECONNECT_DEVICE://请求回连设备
break;
case Constants.ADV_REQUEST_OP_SYNC_DEVICE_INFO: //请求同步设备信息
break;
}
}
});
}
来源:TwsDemo.java
onDeviceRequestOp 展示了设备主动发起的协作场景:设备侧配置被用户通过耳机按键修改、或设备重启后,会请求 App 回读配置、同步时间(updateConnectedTime)、同步设备信息等。Demo 中的注释代码(如 controller.rebootDevice)提示了典型应对策略——回读全部配置后重启设备使配置生效。
示例五:同步接入时间与配置设备名称
public void updateConnectedTime() {
RCSPController controller = RCSPController.getInstance();
int connectedTime = (int) (Calendar.getInstance().getTimeInMillis() / 1000); //接入时间
//执行同步接入时间功能并等待结果回调
controller.updateConnectedTime(controller.getUsingDevice(), connectedTime, new OnRcspActionCallback<Integer>() {
@Override
public void onSuccess(BluetoothDevice device, Integer message) {
//message - 结果码, 0为成功,其他为错误码
}
@Override
public void onError(BluetoothDevice device, BaseError error) {
//失败回调
}
});
}
来源:TwsDemo.java
该示例体现了 TWS 场景的时间基准对齐需求:TWS 主/副耳各自维护接入时间,App 通过 updateConnectedTime 将当前时间(Unix 秒)写入设备,使左右耳的时间基准一致,用于播放时长统计、电量曲线等场景。注意 OnRcspActionCallback<Integer> 的 message 是结果码:0 表示成功,非 0 为错误码。
API 参考
SDK 层将 TWS 相关能力统一收敛在 ITwsOp 接口中,由 RCSPController 实现。以下依据 SDK 文档源整理(tws_func_api.rst.txt):
监听器管理
| 方法 | 说明 |
|---|---|
void addOnTwsEventListener(OnTwsEventListener listener) | 添加 TWS 事件监听器 |
void removeOnTwsEventListener(OnTwsEventListener listener) | 移除 TWS 事件监听器 |
ADV 信息与设置读写
| 方法 | 说明 |
|---|---|
ADVInfoResponse getADVInfo(BluetoothDevice device) | 获取缓存的 ADV 信息(同步返回,不发起命令) |
void controlAdvBroadcast(BluetoothDevice device, boolean enable, OnRcspActionCallback<Boolean> callback) | 控制设备信息广播开关;开启后通过 BTRcspEventCallback#onDeviceBroadcast 接收广播 |
void getDeviceSettingsInfo(BluetoothDevice device, int mask, OnRcspActionCallback<ADVInfoResponse> callback) | 按掩码读取设备设置信息;mask = 0xffffffff 读取全部 |
void modifyDeviceSettingsInfo(BluetoothDevice device, int type, byte[] data, OnRcspActionCallback<Integer> callback) | 修改指定类型的设备设置;回调 message 为结果码,0 成功 |
void updateFunctionValue(BluetoothDevice device, int type, byte value, OnRcspActionCallback<Integer> callback) | 更新指定功能标志的新值 |
噪声处理与智能功能
| 方法 | 说明 |
|---|---|
void getAllVoiceModes(BluetoothDevice device, OnRcspActionCallback<Boolean> callback) | 获取所有噪声处理模式信息 |
void getCurrentVoiceMode(BluetoothDevice device, OnRcspActionCallback<Boolean> callback) | 获取当前噪声处理模式 |
void setCurrentVoiceMode(BluetoothDevice device, VoiceMode voiceMode, OnRcspActionCallback<Boolean> callback) | 设置当前噪声处理模式 |
boolean isSupportAdaptiveANC(BluetoothDevice device) | 是否支持自适应 ANC 算法 |
void getAdaptiveANCData / setAdaptiveANCData / startAdaptiveANC(...) | 自适应 ANC 数据的读写与检测启动;结果通过 OnRcspEventListener#onVoiceFunctionChange 回调 |
boolean isSupportSmartNoPick(BluetoothDevice device) | 是否支持智能免摘功能 |
void getSmartNoPick / setSmartNoPickParam(...) | 智能免摘参数的读写 |
boolean isSupportSceneDenoising(BluetoothDevice device) | 是否支持场景降噪 |
void getSceneDenoising / setSceneDenoising(...) | 场景降噪参数的读写 |
boolean isSupportWindNoiseDetection(BluetoothDevice device) | 是否支持风噪检测 |
void getWindNoiseDetection / setWindNoiseDetection(...) | 风噪检测参数的读写 |
说明:
isSupportXxx系列方法为同步判断,通常在 UI 初始化时用于决定是否展示对应设置入口;get/set系列为异步操作,通过OnRcspActionCallback返回结果。
设备基础配置
| 方法 | 说明 |
|---|---|
void configDeviceName(BluetoothDevice device, String name, OnRcspActionCallback<Integer> callback) | 配置设备名称(TWS 场景下通常同时作用于主/副耳) |
void configKeySettings(BluetoothDevice device, List<ADVInfoResponse.KeySettings> list, OnRcspActionCallback<Integer> callback) | 配置按键功能设置 |
void configLedSettings(BluetoothDevice device, List<ADVInfoResponse.LedSettings> list, OnRcspActionCallback<Integer> callback) | 配置灯光效果设置 |
void updateConnectedTime(BluetoothDevice device, int timeSec, OnRcspActionCallback<Integer> callback) | 同步接入设备时间(单位:秒,Unix 时间戳) |
通用回调约定
所有异步方法统一使用 OnRcspActionCallback<T> 回调:
onSuccess(BluetoothDevice device, T message):操作成功;message为结果数据或结果码(Integer类型时0为成功,非 0 为设备侧错误码)。onError(BluetoothDevice device, BaseError error):操作失败;error携带错误信息,典型错误码包括ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS(设备回复异常状态)与ErrorCode.SUB_ERR_DATA_FORMAT(响应数据格式错误)。
配置选项
| 配置项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
mask(读取掩码) | int | 0xffffffff 表示全部 | getDeviceSettingsInfo 按位选择要读取的属性集,按需读取可减少空口数据量 |
enable(广播开关) | boolean | 由业务决定 | controlAdvBroadcast 开启后设备推送广播,关闭后停止,控制功耗 |
设备请求操作 op | int | 见 Constants.ADV_REQUEST_OP_* | UPDATE_CONFIGURE / UPDATE_AFTER_REBOOT / SYNC_TIME / RECONNECT_DEVICE / SYNC_DEVICE_INFO |
| 超时时间 | long | BluetoothOption.getTimeoutMs() | sendCommandAsync 等待设备回复的超时阈值,超时走 onErrCode 回调 |
| 音箱 TWS 配置 | JSON | ac696x_soundbox_tws_json.txt | 工程 assets 内置的声霸(Soundbox)类设备 TWS 配置模板,用于特定固件产品 |
其中音箱 TWS 配置模板位于 ac696x_soundbox_tws_json.txt,说明 TWS 能力同样覆盖双音箱组网场景,而不仅限于耳机。
失败模式、边界情况与并发
设备无响应 / 命令超时
sendCommandAsync 使用 BluetoothOption.getTimeoutMs() 作为超时阈值;设备处于 TWS 组网切换(主副耳角色互换)或固件异常时可能不回复。此时走 onErrCode 回调,调用方应提示用户重试或重新连接,而不是无限等待。SDK 层同时以 StateCode.STATUS_SUCCESS 校验命令状态,非成功状态直接映射为 SUB_ERR_RESPONSE_BAD_STATUS 错误(见示例二)。
响应数据缺失或格式错误
GetADVInfoCmd 分为有响应/无响应两种类型(FLAG_HAVE_PARAMETER_AND_RESPONSE / FLAG_NO_PARAMETER_AND_RESPONSE)。即使命令状态成功,响应体也可能为 null,Demo 中显式判空并抛出 SUB_ERR_DATA_FORMAT 错误——这是 RCSP 命令解析的常见坑,调用方不可假设 getResponse() 非空。
TWS 状态误判防护
TWS 连接状态由 leftDeviceQuantity / rightDeviceQuantity 双字段共同判定,任一为 0 即视为未连接。这避免了单耳在盒、单耳丢失等场景下误报 TWS 已连接。同时,内容 equals 比对与状态翻转检测两层过滤确保事件仅在真实状态变化时触发,防止 UI 抖动。
并发与线程模型
BTEventCallbackManager通过handleBtCallback将事件回调统一派发到主线程,业务回调内不应执行耗时操作,否则会阻塞 UI 与后续事件分发。DeviceStatusManager的 ADV 缓存更新与读取发生在同一事件处理线程内,天然串行化,但业务方直接调用getADVInfo读取缓存时需要注意数据的新鲜度(广播未开启或设备未推送时缓存可能过期)。- 事件回调可能密集到达(设备周期性广播),消费方应做幂等处理。
广播开启/关闭的功耗权衡
controlAdvBroadcast(true) 开启后设备会持续推送广播信息,这是 TWS 状态实时感知的前提,但也增加空口与设备功耗。业务上应在进入需要实时状态的界面时开启、离开时关闭,LocationHelper 等模块正是基于此按需启用。
扩展点
OnTwsEventListener/BTRcspEventCallback:SDK 提供的事件监听接口,App 侧通过addBTRcspEventCallback/addOnTwsEventListener注册自定义消费逻辑,是接入 TWS 状态、设备广播、设备请求操作的标准扩展点。BTEventCallback继承:App 内所有蓝牙事件消费者继承BTEventCallback并注册到BTEventCallbackManager,新增 TWS 业务模块只需覆写onTwsStatusChange即可无侵入接入事件流。DeviceStatusManager缓存:ADV 信息统一缓存于此,新功能可直接读取getAdvInfo(device)获取左右耳数量、电量等字段,无需自行维护协议解析。CommandBuilder.buildGetADVInfoCmd:需要自定义 mask 读取策略或扩展 ADV 字段时,可基于命令构造层做二次封装。
测试
工程在 TwsDemo.java 中提供了完整的 TWS 功能测试用例,覆盖:设置信息读取(高层与底层两条路径)、广播开关控制、按键/灯光/降噪配置、设备请求操作响应、接入时间同步、设备名称配置等。测试用例同时充当 API 使用文档,是开发者接入 TWS 功能的首选参考。
相关链接
- TwsDemo.java(TWS 功能测试用例)
- BTEventCallbackManager.java(TWS 状态判定与事件分发)
- BTEventCallback.java(事件回调基类)
- TWS 功能接口文档(SDK 中文文档源)
- TWS 功能说明文档(SDK 中文文档源)
- 设备搜索流程:见「设备搜索」相关页面(
SearchDevicePresenter/SearchDeviceViewModel) - 双连接(一拖二)能力:见「双连接」相关页面(
double_connect命令与DoubleConnectionSp配置)