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

    • 项目简介与功能总览
    • 运行环境与快速开始
    • 工程结构与文档布局
  • 架构与核心机制

    • 插件架构与原生平台桥接
    • 基类管理器与常量体系
    • 事件流与接收通知机制
  • 蓝牙连接与设备管理

    • 蓝牙连接与状态管理
    • 设备信息、配置与按键设置
    • 双设备连接与多链路管理
    • 数据传输与自定义命令
  • 音乐与媒体控制

    • 设备音乐与手机音乐播放控制
    • 音量与音频输出管理
  • 音效与音频模式

    • 均衡器与音效调节
    • 音频模式与降噪(ANC)设置
    • Auracast 音频广播
  • 设备功能控制

    • 闹钟管理
    • FM 收音机控制
    • 灯光控制
    • 充电仓与彩屏仓管理
  • 示例应用:杰理之家 Demo

    • 应用框架与交互组件
    • 设置、多语言与调试
  • 接口参考与文档中心

    • 发送接口参考
    • 接收接口与事件参考
    • 官方文档与集成指南

双设备连接与多链路管理

本文介绍 JL SDK 中"双设备连接与多链路管理"能力的完整实现:App 侧通过 BleDoubleDeviceManager 发送获取连接列表、设置开关状态等指令,原生平台通过事件流将双设备列表变更回传给 BleEventStream / BleDoubleDeviceProcessor,最终由 TWS 设备设置界面展示并驱动用户操作。本文覆盖从方法常量、发送接口、事件接收、数据模型到 UI 入口的端到端链路。

Purpose and Scope

本文档作为"连接设备 → 双设备"目录页,说明双设备(TWS 双耳 / 多链路)连接管理在 Flutter 侧 SDK 中的实现机制,包括:

  • BLE 方法通道(MethodChannel)中与双设备相关的方法常量与参数常量(ble_method_constants.dart);
  • 发送接口 BleDoubleDeviceManager(libs/Send Interface/)如何封装原生调用;
  • 事件接收链路:BleEventStream.doubleDeviceList → BleDoubleDeviceProcessor → DoubleDeviceModel(libs/Receive Interface/);
  • TWS 设置界面(device_settings_type_builder.dart)如何按设备类型构建"双设备连接"入口,以及相关的多语言文案与二次确认逻辑。

不在本文范围、由同目录其他页面承载的主题:单设备的基础连接/断开流程、消息推送(Message Push)、SPDIF 音源、PC 从机模式、翻译/语言选择等相邻能力。它们与双设备共享同一套方法常量文件,但属于各自独立的能力页面。

Overview

双设备连接(dualDeviceConnection,中文文案"双设备连接")是 TWS 耳机场景的核心能力之一:一副耳机(左右双耳)可以与多个音源设备(如手机、PC)建立多条链路,或 App 需要同时管理与双耳相关的连接状态。为了让 App 呈现"当前连接了哪些设备、总开关是否打开"并允许用户切换,SDK 在 Flutter 侧暴露了两个命令式接口和一个被动式事件流:

  1. 查询:getDoubleDeviceConnectList() 请求原生返回当前双设备连接列表;
  2. 控制:setDoubleDeviceState({required bool doubleDeviceState}) 打开/关闭双设备连接总开关;
  3. 订阅:BleEventStream.doubleDeviceList(Stream<List<DoubleDeviceModel>>)持续接收列表变更通知。

设计意图:查询/控制走"请求-响应"通道(保证操作有明确结果),而列表变更走"事件推送"通道(原生状态变化时主动通知 App,避免轮询)。这种"命令 + 事件"双通道模式贯穿整个 JL SDK,双设备能力是其中典型代表。

Architecture

flowchart TD
    subgraph sg_UI["App UI 层 (example)"]
        Builder["device_settings_type_builder<br/>TWS 设备类型 2/10/12"]
        TWSList["_buildSettingsTWSList<br/>构建双设备连接设置项"]
    end

    subgraph sg_Send["发送接口 libs/Send Interface"]
        Manager["BleDoubleDeviceManager"]
        Base["BleBaseManager.invokeMethod"]
    end

    subgraph sg_Const["常量定义 lib/constant"]
        Methods["BleMethodConstants<br/>methodGetDoubleDeviceConnectList / methodSetDoubleDeviceState / argDoubleDeviceState"]
        Events["BleEventConstants<br/>typeUpdateDoubleDeviceList / keyDoubleDeviceList / keyIsCurrentDevice / keyTotalSwitchState"]
    end

    subgraph sg_Native["原生平台 (MethodChannel)"]
        Native["原生 BLE 服务"]
    end

    subgraph sg_Receive["接收接口 libs/Receive Interface"]
        Stream["BleEventStream.doubleDeviceList"]
        Proc["BleDoubleDeviceProcessor"]
        Model["DoubleDeviceModel"]
    end

    Builder --> TWSList
    TWSList -->|"开关切换/进入页面"| Manager
    Manager --> Methods
    Manager --> Base
    Base -->|"invokeMethod"| Native
    Native -->|"updateDeviceList 事件"| Stream
    Stream -->|"委托"| Proc
    Proc -->|"解析 doubleDeviceList 等字段"| Model
    Model -->|"UI 刷新列表/开关状态"| TWSList
    Events --> Proc

架构说明:

  • UI 层:device_settings_type_builder.dart 对设备类型 2(TWS 耳机)、10(LE Audio 耳机)、12(彩屏充电仓)统一走 _buildSettingsTWSList 构建设置列表,其中包含"双设备连接"入口;非 TWS 类型(如 deviceType != 12 的普通设备)不会构建该入口。
  • 发送接口:BleDoubleDeviceManager 是双设备能力唯一的命令出口,所有指令统一经由 BleBaseManager.invokeMethod 走原生桥接,方法名与参数名来自 BleMethodConstants。
  • 常量层:BleMethodConstants 定义方法名(命令侧),BleEventConstants 定义事件类型与事件载荷字段名(通知侧),两侧通过 lib/constant/ 下的集中常量文件解耦,防止魔法字符串散布。
  • 接收链路:原生通过事件通道推送 updateDeviceList 类型消息,BleEventStream.doubleDeviceList 静态 getter 直接委托 BleDoubleDeviceProcessor.doubleDeviceList,处理器将原生 JSON 载荷解析为 List<DoubleDeviceModel>(包含 doubleDeviceList、isCurrentDevice、totalSwitchState 等字段),UI 据此刷新连接列表与总开关状态。

主要实现内容

1. 方法与参数常量定义

双设备能力的命令侧常量集中在 code/JieLi_Home_Demo/lib/constant/ble_method_constants.dart,与消息推送、SPDIF、PC 从机等其他能力的方法常量并列。与双设备直接相关的有三个:

常量值说明
methodGetDoubleDeviceConnectList"getDoubleDeviceConnectList"获取双设备连接的列表
methodSetDoubleDeviceState"setDoubleDeviceState"设置双设备的开关状态
argDoubleDeviceState"doubleDeviceState"开关状态参数名(bool)
  /// 获取双设备连接的列表
  static const String methodGetDoubleDeviceConnectList = "getDoubleDeviceConnectList";

  /// 设置双设备的开关状态
  static const String methodSetDoubleDeviceState = "setDoubleDeviceState";

Source: ble_method_constants.dart

  /// 设置双设备连接的打开/关闭的状态
  static const String argDoubleDeviceState = "doubleDeviceState";

Source: ble_method_constants.dart

设计意图:方法名与参数名全部收敛为静态常量,避免调用方硬编码字符串;argDoubleDeviceState 是 bool 类型,原生侧以此键解析开关值,App 侧在事件回传中也能用同一键名反查状态。

2. 发送接口:BleDoubleDeviceManager

libs/Send Interface/ble_double_device_manager.dart 是双设备能力的命令出口,类本身为纯静态封装,内部只做"拼参数 + 调用桥接",不持有状态:

class BleDoubleDeviceManager {

  static Future<void> getDoubleDeviceConnectList() async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodGetDoubleDeviceConnectList,
    );
  }

  static Future<void> setDoubleDeviceState({
    required bool doubleDeviceState,
  }) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodSetDoubleDeviceState,
      arguments: {
        BleMethodConstants.argDoubleDeviceState: doubleDeviceState
      },
    );
  }
}

Source: ble_double_device_manager.dart

实现要点:

  • getDoubleDeviceConnectList() 无参数,仅触发一次"查询当前连接列表"的原生调用;结果不直接返回,而是由原生随后推送 updateDeviceList 事件,App 通过事件流获取——这是"命令触发、事件回报"的异步模式,与蓝牙外设状态天然异步的特性匹配。
  • setDoubleDeviceState({required bool doubleDeviceState}) 使用命名必选参数,强制调用方显式给出开关意图,避免 true/false 位置参数带来的可读性问题;参数通过 arguments Map 以 argDoubleDeviceState 为键传给原生。
  • 两个方法都返回 Future<void>,调用方可 await 等待指令投递完成;底层 BleBaseManager.invokeMethod 封装了平台通道调用与错误透传。

3. 事件接收与数据模型

接收侧由 libs/Receive Interface/ 承载。BleEventStream 对外暴露静态 getter,业务代码无需关心处理器细节:

  // Get double device list
  static Stream<List<DoubleDeviceModel>> get doubleDeviceList =>
      BleDoubleDeviceProcessor.doubleDeviceList;

Source: ble_event_stream.dart

事件类型与载荷字段名定义在 code/JieLi_Home_Demo/lib/constant/ble_event_constants.dart:

  • typeUpdateDoubleDeviceList = "updateDeviceList":原生推送"双设备列表已更新"的事件类型;
  • keyDoubleDeviceList = "doubleDeviceList":载荷中的列表字段;
  • keyIsCurrentDevice = "isCurrentDevice":列表中"是否为当前设备"标记;
  • keyTotalSwitchState = "totalSwitchState":双设备连接总开关状态。
  static const String typeUpdateDoubleDeviceList = "updateDeviceList";

Source: ble_event_constants.dart

  static const String keyIsCurrentDevice = "isCurrentDevice";
  static const String keyDoubleDeviceList = "doubleDeviceList";
  static const String keyTotalSwitchState = "totalSwitchState";

Source: ble_event_constants.dart

BleDoubleDeviceProcessor 负责把原生推送的 JSON 载荷解析成强类型模型 List<DoubleDeviceModel>。DoubleDeviceModel 至少包含 isCurrentDevice(是否当前设备)与总开关状态等语义字段,UI 据此区分"当前使用的设备"并渲染开关。

4. UI 设置入口与多链路状态展示

示例 App 中,TWS 类型设备(2:TWS 耳机、10:LE Audio 耳机、12:彩屏充电仓)的设置列表统一由 device_settings_type_builder.dart 构建:

      case 2: // TWS耳机类型
      case 10: // LE Audio耳机类型
      case 12: // 彩屏充电仓类型
        return _buildSettingsTWSList(
          context,

Source: device_settings_type_builder.dart

  Widget _buildSettingsTWSList(
    BuildContext context,

Source: device_settings_type_builder.dart

    if (deviceType != 12) {
      // TWS耳机,非彩屏充电仓
      settingsItems.add(

Source: device_settings_type_builder.dart

从该构建器可以看到两条关键分支逻辑:

  • 入口条件:只有 TWS/LE Audio/彩屏充电仓类型才进入 _buildSettingsTWSList,普通单耳设备不展示双设备相关设置;
  • 机型差异:deviceType != 12 时(即 TWS 耳机而非彩屏充电仓)额外追加 TWS 专属设置项——说明双设备入口会按"耳机 vs 充电仓"细分展示,充电仓(12)的场景聚焦于仓体相关能力。

多语言文案由 l10n 提供,用户关闭双设备连接时还会弹出确认对话框:

  String get dualDeviceConnection => '双设备连接';

Source: app_localizations_zh.dart

  String get areYouSureTurnOffDual => '您是否确定关闭双设备连接?';

Source: app_localizations_zh.dart

areYouSureTurnOffDual 的存在说明关闭双设备属于高影响操作(会断开另一条链路),因此 UI 层强制二次确认后才调用 setDoubleDeviceState(false)。

Core Flow

查询双设备连接列表

sequenceDiagram
    participant UI as TWS 设置页
    participant M as BleDoubleDeviceManager
    participant B as BleBaseManager
    participant N as 原生 BLE 服务
    participant P as BleDoubleDeviceProcessor
    participant S as BleEventStream

    UI->>M: getDoubleDeviceConnectList()
    M->>B: invokeMethod("getDoubleDeviceConnectList")
    B->>N: 平台通道调用
    N-->>P: 推送 updateDeviceList 事件
    P->>P: 解析 doubleDeviceList / isCurrentDevice / totalSwitchState
    P-->>S: doubleDeviceList Stream
    S-->>UI: Stream<List<DoubleDeviceModel>> 订阅回调
    UI->>UI: 刷新连接列表与总开关

流程要点:

  1. UI 进入双设备页面(或下拉刷新)时调用 BleDoubleDeviceManager.getDoubleDeviceConnectList();
  2. 指令经 BleBaseManager.invokeMethod 投递到原生 BLE 服务;
  3. 原生查询当前多链路状态后,以 updateDeviceList 事件类型回推;
  4. BleDoubleDeviceProcessor 解析载荷(doubleDeviceList、isCurrentDevice、totalSwitchState)生成 List<DoubleDeviceModel>;
  5. BleEventStream.doubleDeviceList 的订阅者收到新列表并刷新 UI。

切换双设备开关

flowchart TD
    A["用户点击双设备开关"] --> B{"当前为开启状态?"}
    B -->|"开启 → 关闭"| C["弹出确认框<br/>areYouSureTurnOffDual"]
    B -->|"关闭 → 开启"| D["直接生效"]
    C -->|"确认"| E["setDoubleDeviceState(false)"]
    C -->|"取消"| F["状态回滚,无调用"]
    D --> G["setDoubleDeviceState(true)"]
    E --> H["原生更新多链路配置"]
    G --> H
    H --> I["原生推送 updateDeviceList"]
    I --> J["BleEventStream 通知 UI 刷新"]

设计意图:关闭是破坏性操作(影响正在使用的另一条链路),需要二次确认;开启是无损操作,可直接生效。两种路径最终都落到同一个 setDoubleDeviceState 方法,状态收敛、UI 回显完全依赖事件流驱动,保证界面与原生真实状态一致。

Usage Examples

获取双设备连接列表并订阅

// 请求原生返回当前双设备连接列表
await BleDoubleDeviceManager.getDoubleDeviceConnectList();

// 订阅列表更新(静态流,全局唯一)
final subscription = BleEventStream.doubleDeviceList.listen((list) {
  // list: List<DoubleDeviceModel>
  // 依据 model.isCurrentDevice 区分当前设备
  setState(() => _deviceList = list);
});

Sources:

  • ble_double_device_manager.dart
  • ble_event_stream.dart

打开/关闭双设备连接

// 打开双设备连接
await BleDoubleDeviceManager.setDoubleDeviceState(doubleDeviceState: true);

// 关闭双设备连接(UI 侧先弹二次确认框)
await BleDoubleDeviceManager.setDoubleDeviceState(doubleDeviceState: false);

Source: ble_double_device_manager.dart

Configuration Options

双设备能力没有独立配置文件,所有"配置"均为跨桥接的常量协议,集中定义在两处常量文件中:

常量值类型所属文件说明
methodGetDoubleDeviceConnectList"getDoubleDeviceConnectList"StringBleMethodConstants查询双设备连接列表的方法名
methodSetDoubleDeviceState"setDoubleDeviceState"StringBleMethodConstants设置双设备开关的方法名
argDoubleDeviceState"doubleDeviceState"StringBleMethodConstants开关状态参数键(bool)
typeUpdateDoubleDeviceList"updateDeviceList"StringBleEventConstants列表更新事件类型
keyDoubleDeviceList"doubleDeviceList"StringBleEventConstants事件载荷中的列表字段
keyIsCurrentDevice"isCurrentDevice"StringBleEventConstants事件载荷中的当前设备标记
keyTotalSwitchState"totalSwitchState"StringBleEventConstants事件载荷中的总开关字段

注:若原生协议升级(如新增字段、方法),只需在常量文件中同步扩展,Flutter 调用方与 UI 层无需改动业务代码。

API Reference

BleDoubleDeviceManager.getDoubleDeviceConnectList()

static Future<void> getDoubleDeviceConnectList() async

请求原生返回当前双设备连接列表。方法本身不返回值,查询结果通过 BleEventStream.doubleDeviceList 事件流异步送达。

参数: 无

返回: Future<void> —— 仅表示指令已投递,不表示查询完成。

异常: 底层 BleBaseManager.invokeMethod 平台通道错误会向上透传。

BleDoubleDeviceManager.setDoubleDeviceState({required bool doubleDeviceState})

static Future<void> setDoubleDeviceState({
  required bool doubleDeviceState,
}) async

设置双设备连接总开关状态。

参数:

  • doubleDeviceState(bool,必选命名参数):true 打开双设备连接,false 关闭。

返回: Future<void>。

异常: 平台通道调用失败时透传底层异常。

BleEventStream.doubleDeviceList(静态 getter)

static Stream<List<DoubleDeviceModel>> get doubleDeviceList

全局唯一的双设备列表事件流,直接委托 BleDoubleDeviceProcessor.doubleDeviceList。业务侧应通过 listen 订阅,并在页面销毁时 cancel 订阅,避免泄漏。

返回: Stream<List<DoubleDeviceModel>>,每个事件为一次完整的列表快照。

失败模式、边界情况与并发

TWS 未连接

双设备能力依赖 TWS 双耳链路,链路缺失时相关操作必须优雅降级。示例 App 的枚举 EnterSelectLanguageResult 明确列出该边界:

  notInCalling(1),    // 不在通话中
  twsNotConnected(2), // TWS未连接
  invalidIndex(3);    // 无效索引

Source: translate_enums.dart

twsNotConnected(2) 表明:当双耳未建立 TWS 连接时,依赖多链路的后续能力(如语言切换等)会返回失败码,UI 需要据此弹出提示而不是继续流程。双设备页面对应的处理模式是:若事件流长时间无更新或收到空列表,应视为"当前无可用双设备连接"并禁用相关开关。

关闭操作的二次确认与状态回滚

areYouSureTurnOffDual("您是否确定关闭双设备连接?")的存在说明关闭是高影响操作。若用户在确认框取消,UI 必须回滚开关显示状态且不发任何指令——这是典型的"乐观 UI + 事件流校正"组合:界面先回滚到开启态,最终一致性由 updateDeviceList 事件兜底。

非 TWS 设备的入口隔离

device_settings_type_builder.dart 仅对设备类型 2(TWS 耳机)、10(LE Audio 耳机)、12(彩屏充电仓)构建 TWS 设置列表;deviceType != 12 分支再细分耳机与充电仓。普通单设备类型不会出现"双设备连接"入口,避免用户在无多链路能力的设备上误操作。

并发与订阅生命周期

  • BleEventStream.doubleDeviceList 是全局静态流:任何页面订阅都会收到全部事件,多个页面同时监听时需自行过滤业务上下文;
  • 命令通道 invokeMethod 是异步投递,连续快速调用 setDoubleDeviceState 时,原生侧按到达顺序处理,UI 应以最后一次 updateDeviceList 事件为准,不要依赖调用顺序;
  • 页面销毁时必须 subscription.cancel(),否则静态流上的回调会持续触发已销毁页面的 setState,导致内存泄漏与 "setState after dispose" 异常。

性能与运维注意

  • 无轮询:列表状态完全由事件推送驱动,原生无变更时不产生流量;App 仅在进入页面/下拉刷新时调用一次 getDoubleDeviceConnectList() 主动同步。
  • 轻量载荷:DoubleDeviceModel 仅携带连接列表与开关标记(isCurrentDevice、totalSwitchState),单次事件开销小,适合蓝牙通道的带宽约束。
  • 常量收敛:方法/事件名全部集中于 lib/constant/,升级协议时只需改常量文件;发布时注意 Flutter SDK 与原生 SDK 的版本对应关系,方法名不一致会导致 invokeMethod 抛 MethodNotImplemented 异常。

扩展点

  1. 新增指令:在 BleMethodConstants 增加方法名常量 → 在 BleDoubleDeviceManager 增加静态方法封装 → 原生侧实现对应 handler。
  2. 新增回传字段:在 BleEventConstants 增加 key 常量 → BleDoubleDeviceProcessor 解析新字段并扩展 DoubleDeviceModel。
  3. 多链路差异化 UI:在 _buildSettingsTWSList 中按 deviceType 或 DoubleDeviceModel.isCurrentDevice 分支渲染不同入口(当前已存在 deviceType != 12 的机型差异分支可作参考范式)。
  4. 状态同步:任何依赖双设备状态的能力(如语言选择)可复用 twsNotConnected 失败码模式,先校验链路再进入流程。

Related Links

  • BleDoubleDeviceManager(发送接口)
  • BleEventStream(接收接口)
  • BleMethodConstants(方法常量)
  • BleEventConstants(事件常量)
  • device_settings_type_builder.dart(TWS 设置构建器)
  • app_localizations_zh.dart(中文文案)
  • 相邻能力:消息推送、SPDIF 音源、PC 从机模式、语言选择 —— 均见"连接设备"目录下的对应页面;本页只覆盖双设备连接与多链路管理。
Prev
设备信息、配置与按键设置
Next
数据传输与自定义命令