杰理 SDK 文档中心
首页
首页
  • 快速开始

    • 环境要求与权限配置
    • 集成SDK依赖
    • 运行示例应用
  • 核心架构与协议

    • RCSP协议与数据通道
    • 蓝牙连接与设备管理
    • TWS双耳功能
    • 基础功能接口与自定义命令
  • 设备功能控制

    • 设备音乐控制与ID3信息
    • 文件浏览与传输
    • FM收音与发射
    • 灯光控制
    • 闹钟与时间管理
    • 查找设备与防丢
    • ANC与噪声处理
    • 按键功能设置
    • 彩屏仓控制
    • AI翻译
  • 音效与音频处理

    • 均衡器音效调节
    • 录音与语音控制
    • Line-in、SPDIF与声卡功能
    • 音频编解码库
  • 扩展功能库

    • OTA固件升级
    • 数据加密与解密
    • 图片与动图格式转换
  • 示例应用 btsmart

    • 应用架构与界面导航
    • 设备功能适配与数据层
    • 设备配置JSON与资源文件
  • 参考与版本

    • 错误码参考
    • 版本历史与更新日志
    • 开发文档中心导航

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

这段代码体现了两层防护设计:

  1. 内容比对(!notifyAdvInfo.equals(cacheAdvInfo)):即使收到通知,若内容与缓存完全一致(如电量未变),则跳过后续处理,避免无意义的写入与回调;
  2. 状态翻转检测(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(读取掩码)int0xffffffff 表示全部getDeviceSettingsInfo 按位选择要读取的属性集,按需读取可减少空口数据量
enable(广播开关)boolean由业务决定controlAdvBroadcast 开启后设备推送广播,关闭后停止,控制功耗
设备请求操作 opint见 Constants.ADV_REQUEST_OP_*UPDATE_CONFIGURE / UPDATE_AFTER_REBOOT / SYNC_TIME / RECONNECT_DEVICE / SYNC_DEVICE_INFO
超时时间longBluetoothOption.getTimeoutMs()sendCommandAsync 等待设备回复的超时阈值,超时走 onErrCode 回调
音箱 TWS 配置JSONac696x_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 配置)
Prev
蓝牙连接与设备管理
Next
基础功能接口与自定义命令