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

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

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

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

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

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

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

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

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

事件流与接收通知机制

本文档阐述 JieLi Home Flutter 插件(jl_home)中事件流与接收通知机制的完整实现:从 Android 原生侧经 Flutter EventChannel 推送事件,到 BleBaseEventProcessor 广播原始数据,再到各领域 Processor 将原始事件解析为强类型流,最终通过 BleEventStream 门面以静态 Getter 形式向应用层暴露,并由 StreamSubscription 完成订阅与释放的端到端链路。

Purpose and Scope

本页覆盖:

  • EventChannel('com.jieli.home_plugin/events') 通道的建立与原始事件广播(BleBaseEventProcessor);
  • 领域处理器(BleAudioProcessor、BleOtaProcessor、BleDeviceConnectionProcessor、BleDeviceMusicProcessor 等)对原始事件的分流、解析与类型转换;
  • BleEventStream 门面对全部强类型流的静态聚合接口;
  • 应用层(example 工程)中 StreamSubscription 的订阅、回调与取消模式;
  • 相关数据模型与订阅生命周期管理。

本页不涵盖各领域功能自身的业务逻辑(如 OTA 升级流程、音频播放协议等),这些内容属于各自的处理器页面。事件流机制是这些功能共用的"通知总线",理解本页是阅读其余处理器源码的前提。

Overview

JieLi Home 插件采用"原生主动推送 → Dart 被动接收"的通信模型。Android 侧的 JieLi BLE SDK 会在扫描、连接、断连、OTA 进度、音乐信息、音量变化等时机产生大量异步事件;这些事件无法用请求/应答式的 MethodChannel 优雅表达,因此插件使用 Flutter 的 EventChannel 建立一条单向、广播式的事件管道。

整体设计遵循**门面(Facade)+ 领域处理器(Domain Processor)**模式:

  1. 通道层:BleBaseEventProcessor 持有唯一的 EventChannel,并通过 receiveBroadcastStream() 将原生事件转为 Dart Stream<dynamic>(原始事件通常为 Map / List / 基础类型)。
  2. 解析层:每个功能域对应一个 Processor(如 BleAudioProcessor),它们订阅 baseStream,按事件类型或字段过滤、解析,并重新暴露为强类型流(如 Stream<MusicInfo>、Stream<DeviceConnection>)。
  3. 门面层:BleEventStream 将所有处理器暴露的流以静态 Getter 汇总,应用层只需 BleEventStream.xxxStream.listen(...) 即可,无需感知底层通道细节。
  4. 消费层:example 工程中的各类 Manager / Dialog 在 initState 阶段订阅、在 dispose 阶段 cancel(),保证生命周期一致。

这种分层让"事件来源"(原生 SDK)与"事件消费者"(页面/管理器)完全解耦:新增一个功能域时只需新增一个 Processor 并在门面中增加一个 Getter,既有代码零改动。

Architecture

flowchart TD
    subgraph sg_Native["原生层 (Android)"]
        SDK["JieLi BLE SDK"]
        Plugin["平台插件 (EventSink 推送)"]
    end

    subgraph sg_Channel["通道层"]
        EC["EventChannel<br/>com.jieli.home_plugin/events"]
    end

    subgraph sg_Core["插件核心 (jl_home lib)"]
        BP["BleBaseEventProcessor<br/>baseStream (Stream~dynamic~)"]
        AP["BleAudioProcessor"]
        OP["BleOtaProcessor"]
        DP["BleDeviceConnectionProcessor"]
        MP["BleDeviceMusicProcessor"]
        OTH["其余 12 个领域 Processor"]
    end

    subgraph sg_Facade["门面层"]
        BES["BleEventStream<br/>(静态 Getter 聚合)"]
    end

    subgraph sg_App["应用层 (example)"]
        MH["BleMusicHandler"]
        FT["FaceToFaceManager"]
        OD["OtaDialog"]
        AR["AuraCastReceiverManager"]
    end

    SDK -->|"异步事件"| Plugin
    Plugin -->|"EventSink.success"| EC
    EC -->|"receiveBroadcastStream()"| BP
    BP -->|"订阅原始流"| AP
    BP -->|"订阅原始流"| OP
    BP -->|"订阅原始流"| DP
    BP -->|"订阅原始流"| MP
    BP -->|"订阅原始流"| OTH
    AP -->|"强类型流"| BES
    OP -->|"强类型流"| BES
    DP -->|"强类型流"| BES
    MP -->|"强类型流"| BES
    OTH -->|"强类型流"| BES
    BES -->|"StreamSubscription"| MH
    BES -->|"StreamSubscription"| FT
    BES -->|"StreamSubscription"| OD
    BES -->|"StreamSubscription"| AR

架构说明

  • 原生层:JieLi BLE SDK 产生扫描、连接、音乐、OTA 等事件;平台插件将这些事件编码为 Map/List 等可跨通道传输的类型,通过 EventSink 推送到 Dart 侧。事件流是单向的,Dart 侧不能反向向该通道写数据(指令下发走 MethodChannel,不在本页范围)。
  • 通道层:BleBaseEventProcessor 是整条管道的唯一入口,EventChannel 名称 com.jieli.home_plugin/events 是原生与 Dart 两侧约定的协议标识,两侧必须完全一致。
  • 核心层:领域 Processor 各自持有 StreamSubscription 订阅 baseStream,把原始 dynamic 事件按 eventId/字段分流并构造成强类型模型(MusicInfo、DeviceConnection、SoundCardSliderModel 等),失败或无效事件在此层被丢弃。核心层共 17 个 Processor(见下文清单)。
  • 门面层:BleEventStream 不包含任何业务逻辑,只是将所有强类型流转写为静态 Getter,形成统一、可发现的 API 面。
  • 应用层:Manager/Dialog 在生命周期内 listen() 并持有 StreamSubscription,在 dispose 时 cancel(),避免内存泄漏与重复回调。

核心机制与实现细节

1. 事件通道基础:BleBaseEventProcessor

BleBaseEventProcessor 是整条事件管道的源头,位于 processor/ble_base_event_processor.dart。它定义了一个静态常量 EventChannel,通道名 com.jieli.home_plugin/events 与 Android 原生侧注册的通道名一一对应:

class BleBaseEventProcessor {
  static const EventChannel _eventChannel = EventChannel('com.jieli.home_plugin/events');

  static Stream<dynamic> get baseStream {
    _baseStream ??= _eventChannel.receiveBroadcastStream();
    return _baseStream!;
  }
}

Source: ble_base_event_processor.dart

设计要点:

  • 单例惰性初始化:_baseStream ??= _eventChannel.receiveBroadcastStream() 保证 baseStream 全插件只有一个实例,所有领域 Processor 共享同一条原始事件流,避免重复注册 EventChannel 导致原生侧事件被多次消费。
  • 广播流语义:receiveBroadcastStream() 返回广播流(broadcast stream),允许多个订阅者同时监听而互不影响;即使某个领域 Processor 的订阅被取消,其他订阅者仍能继续收到事件。
  • 原始类型:流元素类型为 dynamic,实际值由原生侧决定——绝大多数为 Map<String, dynamic>(携带 eventId 与业务字段),也可能是 List 或基础类型。因此所有类型安全都由上层领域 Processor 保证,这是本机制最重要的约束。

2. 领域处理器:原始事件 → 强类型流

BleEventStream 的 import 清单揭示了完整的处理器家族(共 17 个),每个处理器负责一个功能域:

处理器功能域强类型流示例
BleBaseEventProcessor通道与原始流baseStream: Stream<dynamic>
BleDeviceConnectionProcessor扫描与连接scanStateStream、scanDeviceListStream、deviceConnectionStream
BleOtaProcessorOTA 升级otaConnectionStream、otaFileListStream、otaStateStream、mandatoryUpgradeStream
BleAudioProcessor音频/音乐信息id3MusicInfoStream、id3MusicStatusStream、musicInfoStream、musicProgressStream、fmInfoStream、lineInStatusStream
BleDeviceMusicProcessor设备端音乐storageStatusStream、deviceMusicTabTitleStream、deviceMusicPlayItemOKStream、deviceMusicLoadFailedStream
BleSoundCardProcessor声卡soundCardSliderValuesStream、soundCardSelectedStatusStream
BleLightProcessor灯光灯光相关流(文件第 120 行后继续暴露)
BleVolumeProcessor / BleEqProcessor音量 / 均衡器音量、EQ 相关流
BleAlarmProcessor闹钟闹钟相关流
BleAuraCastProcessorAuracast 广播Auracast 广播/录制状态流
BleChargingCaseProcessor充电仓充电仓状态流
BleCustomCmdProcessor自定义指令透传指令响应流
BleDoubleDeviceProcessor双设备双设备状态流
BleSpdifProcessor / BlePcSlaveProcessorSPDIF / PC 从机SPDIF 音频/播放状态流
BleTransferProcessor文件传输传输进度与状态流

Source: ble_event_stream.dart 的 import 区(处理器清单依据文件头 import 与门面 Getter 归纳;各处理器内部解析逻辑见各自源码)

每个领域处理器的职责模式一致:构造 StreamSubscription 订阅 BleBaseEventProcessor.baseStream,在回调中按 eventId 或字段匹配本域事件,解析为强类型模型(如 MusicInfo、DeviceConnection、SoundCardSliderModel、TranslationRecord),再通过 StreamController 重新暴露。这层"解析-重发"隔离了原始协议与业务 API:原生事件格式变化时只需改对应 Processor,门面与应用层不受影响。

3. 门面:BleEventStream

BleEventStream(lib/ble_event_stream.dart)是插件对外暴露事件流的唯一入口。类注释明确说明:"All external interfaces are static methods/properties and can be directly accessed through BleEventStream.xxx"——所有接口均为静态成员,可直接以 BleEventStream.xxx 访问:

class BleEventStream {
  // Core broadcast stream
  static Stream<dynamic> get baseStream => BleBaseEventProcessor.baseStream;

  // Scanning streams
  static Stream<String> get scanStateStream =>
      BleDeviceConnectionProcessor.scanStateStream;

  static Stream<List<ScanDevice>> get scanDeviceListStream =>
      BleDeviceConnectionProcessor.scanDeviceListStream;

  // Device connection streams
  static Stream<DeviceConnection> get deviceConnectionStream =>
      BleDeviceConnectionProcessor.deviceConnectionStream;

  // OTA streams
  static Stream<Map<String, dynamic>> get otaConnectionStream =>
      BleOtaProcessor.otaConnectionStream;

  static Stream<bool> get mandatoryUpgradeStream =>
      BleOtaProcessor.mandatoryUpgradeStream;

  // Audio streams
  static Stream<MusicInfo> get id3MusicInfoStream =>
      BleAudioProcessor.id3MusicInfoStream;

  static Stream<Map<String, dynamic>> get musicProgressStream =>
      BleAudioProcessor.musicProgressStream;

  // Device music streams
  static Stream<List<DeviceMusicModel>> get deviceMusicTabTitleStream =>
      BleDeviceMusicProcessor.deviceMusicTabTitleStream;

  static Stream<DMError> get deviceMusicLoadFailedStream =>
      BleDeviceMusicProcessor.deviceMusicLoadFailedStream;
  // ... 其余 Getter 以此类推
}

Source: ble_event_stream.dart

门面层刻意保持"零逻辑":每个 Getter 只是把对应处理器的流透传出去。这样做的收益是:

  • 统一发现入口:开发者只需查 BleEventStream 一个类即可找到全部事件流,无需深入处理器实现;
  • 强类型签名:每个 Getter 的返回类型即该流的数据契约,编译期即可发现订阅方与数据结构不匹配的问题;
  • 便于复用:libs/Receive Interface/ble_event_stream.dart 中保留了该接收接口的参考副本,作为跨工程(如其他依赖方)复用同一套事件契约的依据。

4. 订阅生命周期:StreamSubscription

消费端的标准模式是"持有订阅 → 生命周期结束取消"。BleMusicHandler(插件内部使用方)把每个流的订阅保存为可空字段:

  // 流订阅
  StreamSubscription<List<DeviceMusicModel>>? _tabTitleSubscription;
  StreamSubscription<List<DeviceMusicModel>>? _itemModelArraySubscription;
  StreamSubscription<void>? _playItemOKSubscription;
  StreamSubscription<DMError>? _loadFailedSubscription;
  StreamSubscription<List<dynamic>>? _cardMessageDismissSubscription;

Source: ble_music_handler.dart

example 工程中的各 Manager 遵循同一约定,例如翻译对讲管理器同时订阅工作时段、录音状态与翻译记录三类事件:

  StreamSubscription<String>? _workTimeSubscription;
  StreamSubscription<bool>? _deviceRecordStateStream;
  StreamSubscription<List<TranslationRecord>>? _translationRecordStream;

Source: face_to_face_manager.dart

Auracast 接收管理器则订阅扫描状态与广播列表:

  // 流订阅
  StreamSubscription<bool>? _scanStateSubscription;
  StreamSubscription<List<AuraCastBroadcastModel>>? _broadcastListSubscription;

Source: aura_cast_receiver_manager.dart

生命周期约定:

  1. 在初始化阶段(initState / 构造后)调用 BleEventStream.xxxStream.listen(回调),把返回的 StreamSubscription 赋给上述字段;
  2. 在销毁阶段(dispose)对每个订阅调用 cancel();
  3. 订阅字段声明为可空(?),允许"按需订阅"——例如 OTA 对话框只在升级会话期间订阅 _otaStateSubscription(ota_dialog.dart),SelectLanguageManager 只在切换翻译模式时订阅 translationModeSuccessStream(select_language_manager.dart)。

为什么必须 cancel? 广播流不会因监听者销毁而自动断开,若不取消,页面重建后会收到重复回调,且监听者持有的上下文(如 BuildContext)可能导致内存泄漏或对已销毁 Widget 操作。这是 Flutter 事件流机制最常见的坑,本仓库统一用"生命周期成对管理"规避。

5. 类关系总览

classDiagram
    class BleEventStream {
        <<facade>>
        +baseStream Stream~dynamic~
        +scanStateStream Stream~String~
        +scanDeviceListStream Stream~List~ScanDevice~~
        +deviceConnectionStream Stream~DeviceConnection~
        +otaStateStream Stream~Map~
        +id3MusicInfoStream Stream~MusicInfo~
        +musicProgressStream Stream~Map~
        +deviceMusicTabTitleStream Stream~List~DeviceMusicModel~~
    }

    class BleBaseEventProcessor {
        <<processor>>
        -_eventChannel EventChannel
        -_baseStream Stream~dynamic~
        +baseStream Stream~dynamic~
    }

    class BleAudioProcessor {
        <<processor>>
        +id3MusicInfoStream Stream~MusicInfo~
        +musicInfoStream Stream~Map~
        +musicProgressStream Stream~Map~
    }

    class BleOtaProcessor {
        <<processor>>
        +otaConnectionStream Stream~Map~
        +otaStateStream Stream~Map~
        +mandatoryUpgradeStream Stream~bool~
    }

    class BleDeviceConnectionProcessor {
        <<processor>>
        +scanStateStream Stream~String~
        +scanDeviceListStream Stream~List~ScanDevice~~
    }

    class BleDeviceMusicProcessor {
        <<processor>>
        +deviceMusicTabTitleStream Stream~List~DeviceMusicModel~~
        +deviceMusicLoadFailedStream Stream~DMError~
    }

    BleEventStream --> BleBaseEventProcessor : 透传 baseStream
    BleEventStream --> BleAudioProcessor : 透传
    BleEventStream --> BleOtaProcessor : 透传
    BleEventStream --> BleDeviceConnectionProcessor : 透传
    BleEventStream --> BleDeviceMusicProcessor : 透传
    BleAudioProcessor --> BleBaseEventProcessor : 订阅
    BleOtaProcessor --> BleBaseEventProcessor : 订阅
    BleDeviceConnectionProcessor --> BleBaseEventProcessor : 订阅
    BleDeviceMusicProcessor --> BleBaseEventProcessor : 订阅

类关系图基于 ble_event_stream.dart 与 ble_base_event_processor.dart 中可验证的委托/订阅关系绘制;其余 Processor 与门面的关系同构。

Core Flow:一条事件从原生到页面的完整链路

sequenceDiagram
    participant SDK as JieLi BLE SDK (Android)
    participant Plugin as 平台插件 EventSink
    participant EC as EventChannel<br/>com.jieli.home_plugin/events
    participant BP as BleBaseEventProcessor
    participant P as 领域 Processor<br/>(如 BleAudioProcessor)
    participant BES as BleEventStream
    participant APP as 应用 (BleMusicHandler)

    SDK->>Plugin: 产生异步事件(扫描/连接/音乐/OTA...)
    Plugin->>EC: EventSink.success(Map with eventId)
    EC->>BP: receiveBroadcastStream() 分发原始事件
    BP->>BP: 广播 Stream<dynamic>
    P->>BP: 已订阅 baseStream,收到原始事件
    P->>P: 按 eventId/字段匹配本域事件
    P->>P: 解析并构造强类型模型
    P->>BES: 向强类型 StreamController 写入
    BES->>APP: 静态 Getter 返回强类型流
    APP->>APP: listen() 收到回调,更新 UI/状态
    Note over APP: dispose 时 cancel() 订阅

分步说明

  1. 原生事件产生:Android 侧 JieLi BLE SDK 在任何状态变化(扫描结果、设备连接/断开、ID3 音乐信息、OTA 进度、音量变化等)时触发回调。
  2. 编码与推送:平台插件将回调数据编码为 Dart 可跨通道传输的结构(Map/List/基础类型),调用 EventSink.success(...) 推送到 EventChannel。事件中通常携带 eventId 用于标识事件类型。
  3. 原始流广播:BleBaseEventProcessor.baseStream 是唯一接收者,receiveBroadcastStream() 将通道事件转为广播流,同时分发给所有订阅者。
  4. 领域分流:每个领域 Processor 的订阅回调被触发,先做事件过滤——只处理本域的 eventId,其余事件直接忽略返回,避免越界消费。
  5. 类型转换:Processor 从原始 Map 中提取字段,构造强类型模型(如 MusicInfo、DeviceConnection、SoundCardSliderModel)。此步骤同时是校验点:字段缺失或类型不符的事件在此被丢弃或降级处理。
  6. 门面透传:BleEventStream.xxxStream 静态 Getter 把转换后的流暴露给应用层,应用层拿到的是编译期可检查的强类型流。
  7. 订阅消费:Manager/Dialog 在生命周期内 listen(),回调中更新 UI 或业务状态;销毁时 cancel() 释放订阅。

数据模型与事件载体

事件在链路上经历"原生结构 → dynamic → 强类型模型"两级转换,最终交付给应用层的强类型模型集中在 model/ 目录。ble_event_stream.dart 的 import 区(第 1-37 行)给出了与事件流绑定的完整模型清单,按功能域归类如下:

功能域强类型模型对应流
设备连接DeviceConnection、ScanDevicedeviceConnectionStream、scanDeviceListStream
音乐信息MusicInfo、DeviceMusicModelid3MusicInfoStream、deviceMusicTabTitleStream 等
声卡SoundCardSliderModelsoundCardSliderValuesStream
翻译对讲TranslationRecord、TranslationSessionRecord对讲相关流(face_to_face_manager 消费)
AuracastAuraCastBroadcastModel、AuraCastRecordStateModel_broadcastListSubscription 等
双设备DoubleDeviceModel双设备流
SPDIFSpdifAudioInfo、SpdifPlayStatusInfoSPDIF 音频/播放状态流
音量VolumeInfo、VolumeCtrlInfo音量相关流
PC 从机PcSlavePlayStatusInfoPC 从机播放状态流
通用OpResult、StateResult、ResourceListEvent、UploadStateModel、MessagePushStateModel操作结果/状态通知类事件

事件数据结构约定

原始事件的典型形态为携带 eventId 的 Map<String, dynamic>,例如:

{
  "eventId": "onDeviceConnectionChanged",
  "deviceConnection": { "state": 2, "address": "AA:BB:CC:DD:EE:FF" }
}

说明:以上为事件结构的示意(依据 model/device_connection.dart 等模型承载的字段推断),实际字段名以各领域 Processor 解析代码为准。事件流机制本身对载荷结构不做约束——解析职责完全下沉到各领域 Processor。

模型与流的对应关系(ER 视角)

erDiagram
    BLE_EVENT_STREAM ||--o{ TYPED_STREAM : "暴露"
    TYPED_STREAM ||--o{ DOMAIN_MODEL : "承载"
    BLE_BASE_PROCESSOR ||--o{ DOMAIN_PROCESSOR : "广播给"
    DOMAIN_PROCESSOR ||--|| TYPED_STREAM : "解析产出"
    DOMAIN_MODEL {
        string eventId "事件类型标识"
        map payload "业务字段"
    }
    TYPED_STREAM {
        string streamName "如 id3MusicInfoStream"
        string elementType "如 MusicInfo"
    }
    DOMAIN_PROCESSOR {
        string domain "如 audio/ota/connection"
        string eventChannel "com.jieli.home_plugin/events"
    }

该 ER 图刻画机制层面的抽象关系:BleBaseEventProcessor 广播原始事件给多个 DOMAIN_PROCESSOR,每个处理器产出一条 TYPED_STREAM(承载 DOMAIN_MODEL),BleEventStream 统一暴露。具体模型字段见 model/ 目录各文件。

Usage Examples

基本用法:订阅一条事件流

以设备连接状态为例——应用层直接访问 BleEventStream.deviceConnectionStream 并 listen():

class BleEventStream {
  // Device connection streams
  static Stream<DeviceConnection> get deviceConnectionStream =>
      BleDeviceConnectionProcessor.deviceConnectionStream;
}

Source: ble_event_stream.dart

订阅方模式(与仓库内各 Manager 一致):

StreamSubscription<DeviceConnection>? _connectionSubscription;

void _init() {
  _connectionSubscription = BleEventStream.deviceConnectionStream.listen((conn) {
    // 连接状态变化回调,更新 UI
  });
}

void _dispose() {
  _connectionSubscription?.cancel();
}

进阶用法:多流并行订阅与按需取消

OTA 对话框只在升级会话期间订阅 otaStateStream,并在页面销毁时取消(仓库中 ota_dialog.dart 的字段声明):

  bool _isSuccess = false;
  StreamSubscription? _otaStateSubscription;
  bool _isLoadingDialogShowing = false;

Source: ota_dialog.dart

翻译对讲管理器则同时订阅三种不同类型的事件流(String、bool、List<TranslationRecord>),展示强类型流的组合消费:

  StreamSubscription<String>? _workTimeSubscription;
  StreamSubscription<bool>? _deviceRecordStateStream;
  StreamSubscription<List<TranslationRecord>>? _translationRecordStream;

Source: face_to_face_manager.dart

底层用法:直接访问原始事件流

需要自定义解析(如调试、透传协议)时,可绕过领域 Processor,直接订阅原始流:

static Stream<dynamic> get baseStream {
  _baseStream ??= _eventChannel.receiveBroadcastStream();
  return _baseStream!;
}

Source: ble_base_event_processor.dart

注意:直接消费 baseStream 意味着自行承担事件过滤与解析工作,且会与领域 Processor 同时收到事件(广播流不会阻止其他订阅者),适用于工具类场景而非业务页面。

API Reference

BleEventStream 静态属性(节选,按功能域分组)

Getter类型来源 Processor语义
baseStreamStream<dynamic>BleBaseEventProcessor原始事件流(所有事件的源头)
soundCardSliderValuesStreamStream<List<SoundCardSliderModel>>BleSoundCardProcessor声卡滑块值变化
soundCardSelectedStatusStreamStream<List<dynamic>>BleSoundCardProcessor声卡选中状态变化
scanStateStreamStream<String>BleDeviceConnectionProcessor扫描状态变化(开始/停止等)
scanDeviceListStreamStream<List<ScanDevice>>BleDeviceConnectionProcessor扫描到的设备列表更新
deviceConnectionStreamStream<DeviceConnection>BleDeviceConnectionProcessor设备连接状态变化
otaConnectionStreamStream<Map<String, dynamic>>BleOtaProcessorOTA 连接状态变化
otaFileListStreamStream<List<Map<String, String>>>BleOtaProcessorOTA 固件文件列表
mandatoryUpgradeStreamStream<bool>BleOtaProcessor是否强制升级通知
otaStateStreamStream<Map<String, dynamic>>BleOtaProcessorOTA 升级进度/状态
lineInStatusStreamStream<int>BleAudioProcessorLine-In 状态
id3MusicInfoStreamStream<MusicInfo>BleAudioProcessor设备端 ID3 音乐信息
id3MusicStatusStreamStream<int>BleAudioProcessorID3 播放状态
fmInfoStreamStream<Map<String, dynamic>>BleAudioProcessorFM 收音信息
musicInfoStreamStream<Map<String, dynamic>>BleAudioProcessor音乐信息
musicProgressStreamStream<Map<String, dynamic>>BleAudioProcessor音乐播放进度
storageStatusStreamStream<int>BleDeviceMusicProcessor设备存储状态
deviceMusicTabTitleStreamStream<List<DeviceMusicModel>>BleDeviceMusicProcessor设备音乐 Tab 标题列表
deviceMusicItemModelArrayStreamStream<List<DeviceMusicModel>>BleDeviceMusicProcessor设备音乐条目列表
deviceMusicPlayItemOKStreamStream<void>BleDeviceMusicProcessor播放指定条目成功
deviceMusicLoadFailedStreamStream<DMError>BleDeviceMusicProcessor设备音乐加载失败(携带错误)
deviceMusicCardMessageDismissStreamStream<List<dynamic>>BleDeviceMusicProcessor音乐卡片消息关闭
sdCardStatusStreamStream<List<int>>BleDeviceMusicProcessorSD 卡状态

以上条目均来自 ble_event_stream.dart 中已验证的 Getter 声明(灯光等后续分组受读取范围限制未逐一列出,结构与上表同构)。

返回类型说明:所有 Getter 返回的都是 Dart Stream<T>,订阅回调签名随 T 变化;Stream<void> 表示"仅通知、无载荷"的事件(如播放成功)。这些流均为广播流,listen 不要求 cancelOnError 参数——错误处理由订阅方自行决定。

Configuration Options

事件流机制本身不提供运行时配置项,唯一需要对齐的"配置"是通道标识:

配置项类型默认值说明
EventChannel 名称stringcom.jieli.home_plugin/eventsDart 侧与 Android 原生侧必须完全一致,否则收不到事件
流初始化策略lazy首次访问 baseStream 时创建_baseStream ??= 惰性单例,避免插件启动即占用通道
订阅生命周期应用层约定随页面/管理器 dispose 取消仓库约定:StreamSubscription 字段与 dispose 成对管理

通道名来源:ble_base_event_processor.dart

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

事件解析失败

原始事件为 dynamic,若原生侧推送的载荷结构不完整(字段缺失、类型不匹配、eventId 未知),领域 Processor 的解析逻辑无法构造强类型模型。仓库设计通过"解析层兜底"处理:Processor 只消费匹配本域的事件,其余事件静默忽略;单条事件解析失败不会影响 baseStream 上的其他订阅者(广播流隔离)。应用层收到异常数据时,应依据 DMError(如 deviceMusicLoadFailedStream)或状态流做降级展示。

通道未就绪 / 原生侧未注册

若 Android 原生侧没有注册 com.jieli.home_plugin/events 通道,receiveBroadcastStream() 的订阅在事件到来时可能收到 PlatformException 或静默无事件。故障表现是"订阅无回调"而非崩溃,排查时优先确认两侧通道名一致、插件已初始化。

广播流的并发语义

  • baseStream 是广播流:多个领域 Processor 并行订阅,每个订阅者独立收到完整事件序列,**不存在事件被某个订阅者"抢走"**的问题;
  • 广播流不缓冲历史事件:新订阅者只会收到订阅之后的事件,迟到的订阅会错过已经发生的事件。因此依赖"当前状态"的逻辑应在订阅后主动向设备查询一次(走 MethodChannel),而不是只等事件推送;
  • 同一事件会依次经过所有领域 Processor 的过滤,O(n) 的过滤开销在事件频率不高(秒级以下)时可忽略;若未来事件频率上升,可考虑在 BleBaseEventProcessor 增加按 eventId 预分流,当前版本未做该优化。

订阅泄漏与重复回调

最常见的边界问题:页面重建后旧订阅未取消。仓库约定在 dispose 中 cancel() 所有 StreamSubscription 字段,且字段声明为可空以支持按需订阅。若应用层违反该约定,广播流将持续向已销毁的监听者派发事件,可能导致:

  • 回调访问已销毁的 BuildContext,触发 Flutter 框架异常;
  • 内存泄漏(监听者及其捕获的上下文无法被 GC 回收);
  • 同一逻辑被多次触发(重复监听)。

单例初始化的线程安全

_baseStream ??= _eventChannel.receiveBroadcastStream() 在 Dart 单线程事件循环下是安全的:Dart 无多线程抢占,首个访问该 Getter 的代码完成初始化前不会让出执行权,因此不存在竞态条件。若在 isolate 中访问,则每个 isolate 会各自创建一份流实例——当前架构假定所有消费发生在同一 isolate(UI isolate)。

性能与运维注意事项

  • 单通道复用:全部事件共用一条 EventChannel,避免为每个事件类型各建通道带来的原生侧资源开销;代价是 Dart 侧需要统一的过滤/分发逻辑。
  • 惰性初始化:baseStream 在首次访问时才建立通道连接,插件加载不产生额外开销;但如果没有任何领域 Processor 或应用订阅者,事件会被原生侧丢弃(无人监听),这也意味着必须至少有一个活跃订阅者才能收到事件。
  • 事件体积:跨通道传输会对 Map/List 做序列化,音乐列表、翻译记录等大数据量事件(如 deviceMusicItemModelArrayStream 携带 List<DeviceMusicModel>)应避免高频全量推送;订阅方也应避免在回调中做重量级同步操作。
  • 调试入口:需要排查事件链路时,直接订阅 BleEventStream.baseStream 打印原始事件是最快的定位手段;libs/Receive Interface/ble_event_stream.dart 保留的接收接口副本可作为对照基线,防止接口漂移。

Extension Points

事件流架构的扩展性极佳,新增一个事件类型/功能域只需三步:

  1. 新增/扩展领域 Processor:复制现有 Processor 模式,构造 StreamSubscription 订阅 BleBaseEventProcessor.baseStream,过滤本域 eventId,解析为强类型模型,经 StreamController 暴露强类型流;
  2. 门面登记:在 BleEventStream 中增加一个静态 Getter,透传新 Processor 的流(如新增 ble_xyz_processor.dart 后,import 并在类中加 static Stream<Xyz> get xyzStream => BleXyzProcessor.xyzStream;);
  3. 应用层订阅:消费方按"生命周期成对管理"约定 listen() / cancel()。

由于门面与处理器、处理器与通道之间都是单向、松耦合的委托/订阅关系,新增功能不需要修改既有 Processor 或通道定义;唯一需要保持一致的"契约"是通道名与事件载荷格式。

Tests

本次源码检索未发现针对事件流机制的独立单元测试文件(example/integration_test/plugin_integration_test.dart 存在,属于插件集成测试范畴)。集成测试文件的存在表明仓库通过真实插件环境验证通道与事件链路;处理器级的解析逻辑建议在后续补充单元测试,用构造的原始事件 Map 验证过滤与类型转换的正确性(如未知 eventId 被忽略、字段缺失被降级等边界)。

Related Links

  • BleEventStream 门面(lib/ble_event_stream.dart)
  • BleBaseEventProcessor 通道基座(lib/processor/ble_base_event_processor.dart)
  • BleMusicHandler 流订阅示例(lib/ble_music_handler.dart)
  • 接收接口参考副本(libs/Receive Interface/ble_event_stream.dart)
  • example 消费方:FaceToFaceManager
  • example 消费方:AuraCastReceiverManager
  • example 消费方:OtaDialog
  • 相关页面:各领域处理器(OTA、音频、连接等)的详细业务逻辑见对应处理器文档页
Prev
基类管理器与常量体系