事件流与接收通知机制
本文档阐述 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)**模式:
- 通道层:
BleBaseEventProcessor持有唯一的EventChannel,并通过receiveBroadcastStream()将原生事件转为 DartStream<dynamic>(原始事件通常为Map/List/ 基础类型)。 - 解析层:每个功能域对应一个
Processor(如BleAudioProcessor),它们订阅baseStream,按事件类型或字段过滤、解析,并重新暴露为强类型流(如Stream<MusicInfo>、Stream<DeviceConnection>)。 - 门面层:
BleEventStream将所有处理器暴露的流以静态 Getter 汇总,应用层只需BleEventStream.xxxStream.listen(...)即可,无需感知底层通道细节。 - 消费层:
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 |
BleOtaProcessor | OTA 升级 | 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 | 闹钟 | 闹钟相关流 |
BleAuraCastProcessor | Auracast 广播 | Auracast 广播/录制状态流 |
BleChargingCaseProcessor | 充电仓 | 充电仓状态流 |
BleCustomCmdProcessor | 自定义指令 | 透传指令响应流 |
BleDoubleDeviceProcessor | 双设备 | 双设备状态流 |
BleSpdifProcessor / BlePcSlaveProcessor | SPDIF / 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
生命周期约定:
- 在初始化阶段(
initState/ 构造后)调用BleEventStream.xxxStream.listen(回调),把返回的StreamSubscription赋给上述字段; - 在销毁阶段(
dispose)对每个订阅调用cancel(); - 订阅字段声明为可空(
?),允许"按需订阅"——例如 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() 订阅
分步说明
- 原生事件产生:Android 侧 JieLi BLE SDK 在任何状态变化(扫描结果、设备连接/断开、ID3 音乐信息、OTA 进度、音量变化等)时触发回调。
- 编码与推送:平台插件将回调数据编码为 Dart 可跨通道传输的结构(
Map/List/基础类型),调用EventSink.success(...)推送到EventChannel。事件中通常携带eventId用于标识事件类型。 - 原始流广播:
BleBaseEventProcessor.baseStream是唯一接收者,receiveBroadcastStream()将通道事件转为广播流,同时分发给所有订阅者。 - 领域分流:每个领域 Processor 的订阅回调被触发,先做事件过滤——只处理本域的
eventId,其余事件直接忽略返回,避免越界消费。 - 类型转换:Processor 从原始
Map中提取字段,构造强类型模型(如MusicInfo、DeviceConnection、SoundCardSliderModel)。此步骤同时是校验点:字段缺失或类型不符的事件在此被丢弃或降级处理。 - 门面透传:
BleEventStream.xxxStream静态 Getter 把转换后的流暴露给应用层,应用层拿到的是编译期可检查的强类型流。 - 订阅消费:Manager/Dialog 在生命周期内
listen(),回调中更新 UI 或业务状态;销毁时cancel()释放订阅。
数据模型与事件载体
事件在链路上经历"原生结构 → dynamic → 强类型模型"两级转换,最终交付给应用层的强类型模型集中在 model/ 目录。ble_event_stream.dart 的 import 区(第 1-37 行)给出了与事件流绑定的完整模型清单,按功能域归类如下:
| 功能域 | 强类型模型 | 对应流 |
|---|---|---|
| 设备连接 | DeviceConnection、ScanDevice | deviceConnectionStream、scanDeviceListStream |
| 音乐信息 | MusicInfo、DeviceMusicModel | id3MusicInfoStream、deviceMusicTabTitleStream 等 |
| 声卡 | SoundCardSliderModel | soundCardSliderValuesStream |
| 翻译对讲 | TranslationRecord、TranslationSessionRecord | 对讲相关流(face_to_face_manager 消费) |
| Auracast | AuraCastBroadcastModel、AuraCastRecordStateModel | _broadcastListSubscription 等 |
| 双设备 | DoubleDeviceModel | 双设备流 |
| SPDIF | SpdifAudioInfo、SpdifPlayStatusInfo | SPDIF 音频/播放状态流 |
| 音量 | VolumeInfo、VolumeCtrlInfo | 音量相关流 |
| PC 从机 | PcSlavePlayStatusInfo | PC 从机播放状态流 |
| 通用 | 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 | 语义 |
|---|---|---|---|
baseStream | Stream<dynamic> | BleBaseEventProcessor | 原始事件流(所有事件的源头) |
soundCardSliderValuesStream | Stream<List<SoundCardSliderModel>> | BleSoundCardProcessor | 声卡滑块值变化 |
soundCardSelectedStatusStream | Stream<List<dynamic>> | BleSoundCardProcessor | 声卡选中状态变化 |
scanStateStream | Stream<String> | BleDeviceConnectionProcessor | 扫描状态变化(开始/停止等) |
scanDeviceListStream | Stream<List<ScanDevice>> | BleDeviceConnectionProcessor | 扫描到的设备列表更新 |
deviceConnectionStream | Stream<DeviceConnection> | BleDeviceConnectionProcessor | 设备连接状态变化 |
otaConnectionStream | Stream<Map<String, dynamic>> | BleOtaProcessor | OTA 连接状态变化 |
otaFileListStream | Stream<List<Map<String, String>>> | BleOtaProcessor | OTA 固件文件列表 |
mandatoryUpgradeStream | Stream<bool> | BleOtaProcessor | 是否强制升级通知 |
otaStateStream | Stream<Map<String, dynamic>> | BleOtaProcessor | OTA 升级进度/状态 |
lineInStatusStream | Stream<int> | BleAudioProcessor | Line-In 状态 |
id3MusicInfoStream | Stream<MusicInfo> | BleAudioProcessor | 设备端 ID3 音乐信息 |
id3MusicStatusStream | Stream<int> | BleAudioProcessor | ID3 播放状态 |
fmInfoStream | Stream<Map<String, dynamic>> | BleAudioProcessor | FM 收音信息 |
musicInfoStream | Stream<Map<String, dynamic>> | BleAudioProcessor | 音乐信息 |
musicProgressStream | Stream<Map<String, dynamic>> | BleAudioProcessor | 音乐播放进度 |
storageStatusStream | Stream<int> | BleDeviceMusicProcessor | 设备存储状态 |
deviceMusicTabTitleStream | Stream<List<DeviceMusicModel>> | BleDeviceMusicProcessor | 设备音乐 Tab 标题列表 |
deviceMusicItemModelArrayStream | Stream<List<DeviceMusicModel>> | BleDeviceMusicProcessor | 设备音乐条目列表 |
deviceMusicPlayItemOKStream | Stream<void> | BleDeviceMusicProcessor | 播放指定条目成功 |
deviceMusicLoadFailedStream | Stream<DMError> | BleDeviceMusicProcessor | 设备音乐加载失败(携带错误) |
deviceMusicCardMessageDismissStream | Stream<List<dynamic>> | BleDeviceMusicProcessor | 音乐卡片消息关闭 |
sdCardStatusStream | Stream<List<int>> | BleDeviceMusicProcessor | SD 卡状态 |
以上条目均来自 ble_event_stream.dart 中已验证的 Getter 声明(灯光等后续分组受读取范围限制未逐一列出,结构与上表同构)。
返回类型说明:所有 Getter 返回的都是 Dart Stream<T>,订阅回调签名随 T 变化;Stream<void> 表示"仅通知、无载荷"的事件(如播放成功)。这些流均为广播流,listen 不要求 cancelOnError 参数——错误处理由订阅方自行决定。
Configuration Options
事件流机制本身不提供运行时配置项,唯一需要对齐的"配置"是通道标识:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| EventChannel 名称 | string | com.jieli.home_plugin/events | Dart 侧与 Android 原生侧必须完全一致,否则收不到事件 |
| 流初始化策略 | lazy | 首次访问 baseStream 时创建 | _baseStream ??= 惰性单例,避免插件启动即占用通道 |
| 订阅生命周期 | 应用层约定 | 随页面/管理器 dispose 取消 | 仓库约定:StreamSubscription 字段与 dispose 成对管理 |
失败模式、边界情况与并发
事件解析失败
原始事件为 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
事件流架构的扩展性极佳,新增一个事件类型/功能域只需三步:
- 新增/扩展领域 Processor:复制现有 Processor 模式,构造
StreamSubscription订阅BleBaseEventProcessor.baseStream,过滤本域eventId,解析为强类型模型,经StreamController暴露强类型流; - 门面登记:在
BleEventStream中增加一个静态 Getter,透传新 Processor 的流(如新增ble_xyz_processor.dart后,import 并在类中加static Stream<Xyz> get xyzStream => BleXyzProcessor.xyzStream;); - 应用层订阅:消费方按"生命周期成对管理"约定
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、音频、连接等)的详细业务逻辑见对应处理器文档页