插件架构与原生平台桥接
本文档详细解析 JieLi Home(JieLi_Home_Demo)Flutter 插件中 Dart 与 Android/iOS 原生平台之间的桥接架构:MethodChannel 方法调用通道、EventChannel 事件推送通道、方法分组分发机制、原生侧处理器/管理器分层,以及通道常量约定。
Purpose and Scope
本页覆盖「插件架构与原生平台桥接」这一完整能力,包括:
- Dart 侧桥接入口:
BleBaseManager(方法通道封装)与BleBaseEventProcessor(事件通道封装); - Android 原生侧:
MethodChannelHandler、EventChannelHandler及其背后的 Manager / Processor 分层、通道常量定义; - iOS 原生侧:与 Android 对称的
MethodChannelHandler.swift、EventChannelHandler.swift及常量定义; - 桥接协议:通道名称、方法名分组、事件消息信封格式(
type+value)、线程模型与失败处理。
以下主题不属于本页范围,由其他目录页单独介绍:蓝牙 SDK 各业务能力的具体协议细节(EQ、OTA、FM、闹钟等)、UI 页面实现、以及 SDK 版本管理流程。本文仅关注这些能力如何通过通道抵达原生层以及原生层如何组织分发。
概述
JieLi_Home_Demo 是一个基于 Flutter 的蓝牙音频设备控制插件,核心设计决策是:所有与蓝牙 SDK 的交互都发生在原生平台层(Android/iOS),Dart 侧不直接接触蓝牙细节。为此,插件采用 Flutter 标准的双通道桥接模型:
| 通道 | 名称 | 方向 | 用途 |
|---|---|---|---|
| MethodChannel | com.jieli.home_plugin/methods | Dart → Native | 请求-响应式方法调用(扫描、连接、EQ 设置、OTA 等) |
| EventChannel | com.jieli.home_plugin/events | Native → Dart | 持续事件流推送(设备状态变化、电量、进度等) |
这种「请求走方法通道、通知走事件通道」的拆分是 Flutter 平台通道的最佳实践:方法调用天然匹配一次性命令,而事件流避免了对同一状态反复轮询,让原生蓝牙回调(异步、高频、多源)能直接映射为 Dart 流式监听。
关键概念:
- 通道(Channel):Dart 与原生平台之间的命名通信管道,两侧使用相同通道名建立连接;
- 方法组(Method Group):Android 侧用
Set<String>将几十个方法名按业务域分组(连接、EQ、媒体、OTA、灯光、FM、闹钟、声卡等),实现「按方法名路由到对应 Manager」; - 事件处理器(Processor):每个业务域一个 Processor,监听各自 SDK 回调并通过共享的
EventSink上报; - 信封格式(Envelope):所有事件统一包装为
{ "type": String, "value": Map },Dart 侧据此分发。
架构
整体桥接架构如下图所示,Dart 侧与 Android/iOS 原生侧通过两条命名通道连接:
flowchart TD
subgraph sg_Dart["Dart 层 (Flutter)"]
BM["BleBaseManager<br/>MethodChannel('com.jieli.home_plugin/methods')"]
BP["BleBaseEventProcessor<br/>EventChannel('com.jieli.home_plugin/events')"]
Pages["业务页面<br/>devices_page / update_page"]
end
subgraph sg_Android["Android 原生层 (Kotlin)"]
MCH["MethodChannelHandler<br/>MethodCallHandler"]
ECH["EventChannelHandler<br/>StreamHandler"]
MGR["业务 Managers<br/>Alarm / SoundCard / AuraCast / ChargingCase / Translate"]
PROC["事件 Processors<br/>Device / Music / OTA / Light / FM / LineIn / Storage / Alarm"]
BTSDK["杰理蓝牙 SDK"]
end
subgraph sg_iOS["iOS 原生层 (Swift)"]
IOS_MCH["MethodChannelHandler.swift"]
IOS_ECH["EventChannelHandler.swift"]
IOS_SDK["CoreBluetooth / 杰理 SDK"]
end
Pages --> BM
Pages --> BP
BM -->|"invokeMethod 请求/响应"| MCH
MCH -->|"方法组路由"| MGR
MGR --> BTSDK
ECH -->|"EventSink.success 推送"| BP
PROC --> ECH
BTSDK -->|"原生回调"| PROC
BM -->|"同名通道"| IOS_MCH
BP -->|"同名通道"| IOS_ECH
IOS_MCH --> IOS_SDK
IOS_SDK --> IOS_ECH
组件职责
BleBaseManager(Dart):方法通道的统一封装,提供invokeMethod/invokeMethodWithDefault泛型入口及若干便捷方法(getSdkVersion、getAppVersion、popAllActivity)。所有 Dart 侧方法调用都经由它转发,是 Dart 侧的唯一方法出口。BleBaseEventProcessor(Dart):事件通道的监听封装,订阅com.jieli.home_plugin/events流,接收原生侧推送的设备事件。MethodChannelHandler(Android):实现MethodChannel.MethodCallHandler,通过MethodGroups将方法名按业务域分组,when分发到对应的 Manager(如AlarmManager、SoundCardManager、AuraCastManager、ChargingCaseManager、TranslateManager)。EventChannelHandler(Android):实现EventChannel.StreamHandler,在onListen时初始化并启动全部子 Processor,onCancel时停止;内部用主线程Handler包装EventSink推送,保证事件在主线程投递。- Processors(Android):
DeviceProcessor、MusicProcessor、OTAProcessor、LightProcessor、FMProcessor、LineInProcessor、StorageProcessor、AlarmProcessor,各自监听 SDK 回调并构造事件信封。 - iOS 侧:提供与 Android 同名的常量与对称的 Handler 结构,保证同一套 Dart 代码在双端无需分支。
桥接实现详解
Dart 侧:方法通道封装(BleBaseManager)
BleBaseManager 是 Dart 侧访问原生能力的统一入口。它把通道定义、调用封装和默认值策略集中在一处,业务代码只需调用静态方法,无需感知通道细节:
import 'package:flutter/services.dart';
import 'constant/ble_method_constants.dart';
/// Basic Bluetooth Manager, provides common methods
class BleBaseManager {
static const MethodChannel _methodChannel = MethodChannel(
'com.jieli.home_plugin/methods',
);
/// Generic method invocation
static Future<dynamic> invokeMethod(
String methodName, {
Map<String, dynamic>? arguments,
}) async {
try {
return await _methodChannel.invokeMethod(methodName, arguments);
} on PlatformException {
rethrow;
}
}
/// Generic method invocation with default value
static Future<T> invokeMethodWithDefault<T>(
String methodName,
T defaultValue, {
Map<String, dynamic>? arguments,
}) async {
try {
final result = await _methodChannel.invokeMethod(methodName, arguments);
return (result ?? defaultValue) as T;
} on PlatformException {
rethrow;
}
}
/// Get SDK version number
static Future<String> getSdkVersion() async {
return await invokeMethodWithDefault(
BleMethodConstants.methodGetSdkVersion,
'V?.?.?(?)',
);
}
/// Get APP version number
static Future<String> getAppVersion() async {
return await invokeMethodWithDefault(
BleMethodConstants.methodGetAppVersion,
'V?.?.?(?)',
);
}
/// Close all Activities
static Future<void> popAllActivity() async {
await invokeMethod(BleMethodConstants.methodPopAllActivity);
}
}
Source: ble_base_manager.dart
设计意图分析:
- 通道名集中定义:通道名
com.jieli.home_plugin/methods以字符串常量形式固定在 Dart 侧,与 Android/iOS 原生侧必须完全一致。采用com.jieli.<插件名>/<通道类型>的反向域名约定,避免与其他插件冲突。 invokeMethodWithDefault<T>泛型降级策略:很多查询类接口(如版本号)在原生侧异常或返回null时,业务不希望崩溃,因此提供默认值回退('V?.?.?(?)'表示"未知版本")。这体现了插件对外部环境的宽容假设——不同固件/系统的 SDK 可能不支持某些查询。PlatformException原样重抛:桥接层的职责是透传而非吞掉错误,上层业务根据异常码决定降级策略。- 方法名集中管理:方法名来自
constant/ble_method_constants.dart,Dart 与原生各自维护一份同名常量,保证调用双方拼写一致。
Dart 侧:事件通道监听(BleBaseEventProcessor)
事件流由原生侧主动推送,Dart 侧通过 EventChannel 建立长连接监听:
static const EventChannel _eventChannel = EventChannel('com.jieli.home_plugin/events');
Source: ble_base_event_processor.dart
事件通道只在 Dart 侧存在一个监听者(单例式静态引用),所有原生事件(设备连接状态、OTA 进度、音乐信息等)都经由这一条流到达 Dart,由 BleBaseEventProcessor 按 type 分发到各业务处理器。
Android 侧:方法分发核心(MethodChannelHandler)
MethodChannelHandler 实现 MethodChannel.MethodCallHandler,是 Android 侧方法通道的总入口。其核心设计是方法组路由表:用 companion object 中的 Set<String> 常量把几十个方法名按业务域分组,onMethodCall 时根据方法名定位所属分组并路由到对应 Manager:
class MethodChannelHandler(
private val activity: MainActivity,
private val eventChannelHandler: EventChannelHandler? = null
) : MethodChannel.MethodCallHandler {
private companion object MethodGroups {
val DEVICE_CONNECT = setOf(
MethodChannelConstants.METHOD_START_SCAN,
MethodChannelConstants.METHOD_STOP_SCAN,
MethodChannelConstants.METHOD_CONNECT_DEVICE,
MethodChannelConstants.METHOD_DISCONNECT_BT_DEVICE
)
val CONFIGURATION = setOf(
MethodChannelConstants.METHOD_IS_BLE_WAY,
MethodChannelConstants.METHOD_SET_BLE_WAY,
MethodChannelConstants.METHOD_IS_USE_DEVICE_AUTH,
MethodChannelConstants.METHOD_SET_USE_DEVICE_AUTH,
MethodChannelConstants.METHOD_IS_HID_DEVICE,
MethodChannelConstants.METHOD_SET_HID_DEVICE,
MethodChannelConstants.METHOD_IS_USE_CUSTOM_RECONNECT_WAY,
MethodChannelConstants.METHOD_SET_USE_CUSTOM_RECONNECT_WAY,
MethodChannelConstants.METHOD_GET_BLE_REQUEST_MTU,
MethodChannelConstants.METHOD_SET_BLE_REQUEST_MTU,
MethodChannelConstants.METHOD_GET_SDK_VERSION,
MethodChannelConstants.METHOD_GET_APP_VERSION,
MethodChannelConstants.METHOD_POP_ALL_ACTIVITY
)
val EQ = setOf(
MethodChannelConstants.METHOD_GET_EQ_DATA,
MethodChannelConstants.METHOD_SET_EQ_MODE,
MethodChannelConstants.METHOD_SET_EQ_VALUES,
MethodChannelConstants.METHOD_RESET_EQ_MODE,
MethodChannelConstants.METHOD_ADVANCED_EQ,
MethodChannelConstants.METHOD_ADVANCED_SET_EQ
)
val MEDIA = setOf(
MethodChannelConstants.METHOD_CHANGE_AUX_MODE,
MethodChannelConstants.METHOD_AUX_PLAY_STATE,
MethodChannelConstants.METHOD_OPEN_ID3_PUSH,
MethodChannelConstants.METHOD_ID3_LAST_SONG,
MethodChannelConstants.METHOD_ID3_PLAY_PAUSE,
MethodChannelConstants.METHOD_ID3_NEXT_SONG,
MethodChannelConstants.METHOD_FIND_DEVICE,
MethodChannelConstants.METHOD_STOP_VOICE
)
// ... OTA / LIGHT / FM / ALARM / SOUND_CARD / DEVICE_SETTINGS 等分组
}
}
Source: MethodChannelHandler.kt
设计意图分析:
- 按业务域分组的动机:蓝牙 SDK 的 API 面极大(连接、配置、EQ、媒体、OTA、灯光、FM、闹钟、声卡、翻译等),若用单一
when分支会形成数百行的巨型函数。分组路由让每个业务域的 Manager 只关心自己的方法集合,同时保留了「方法名 → 分组 → Manager」的可读映射,便于新人快速定位。 activity依赖注入:Handler 持有MainActivity引用,因为大量方法需要 Activity 上下文(文件选择、OTA 存储检查、页面跳转等)。eventChannelHandler反向依赖:部分方法(如 OTA 进度、ID3 推送开关)需要触发事件上报,Handler 通过构造函数持有事件处理器引用,实现"方法调用触发生成事件"的闭环。- 导入的 Manager 类型(
AuraCastManager、ChargingCaseManager、AlarmManager、SoundCardManager、TranslateManager)表明 SDK 能力被拆分为独立管理类,方法分发最终落点到这些类。
Android 侧:事件推送核心(EventChannelHandler)
EventChannelHandler 实现 EventChannel.StreamHandler,管理事件流的生命周期,并作为所有子 Processor 的事件汇聚点:
class EventChannelHandler(private val activity: Activity) : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
private val handler = Handler(Looper.getMainLooper())
// 子处理器实例
private lateinit var deviceProcessor: DeviceProcessor
private lateinit var musicProcessor: MusicProcessor
private lateinit var otaProcessor: OTAProcessor
private lateinit var lightProcessor: LightProcessor
private lateinit var fmProcessor: FMProcessor
private lateinit var lineInProcessor: LineInProcessor
lateinit var storageProcessor: StorageProcessor
lateinit var alarmProcessor: AlarmProcessor
override fun onListen(arguments: Any?, sink: EventChannel.EventSink) {
eventSink = sink
initProcessors()
startProcessors()
}
override fun onCancel(arguments: Any?) {
eventSink = null
stopProcessors()
}
private fun initProcessors() {
deviceProcessor = DeviceProcessor(activity, eventSink, handler)
musicProcessor = MusicProcessor(activity, eventSink, handler)
otaProcessor = OTAProcessor(activity, eventSink, handler)
lightProcessor = LightProcessor(activity, eventSink, handler)
fmProcessor = FMProcessor(activity, eventSink, handler)
lineInProcessor = LineInProcessor(eventSink, handler)
storageProcessor = StorageProcessor(activity, eventSink, handler)
alarmProcessor = AlarmProcessor(activity, eventSink, handler)
}
private fun startProcessors() {
deviceProcessor.startListening()
musicProcessor.startListening()
// ... 其余 Processor 依次 startListening()
}
private fun stopProcessors() {
deviceProcessor.stopListening()
// ... 其余 Processor 依次 stopListening()
}
internal fun sendEvent(type: String, data: Map<String, Any>) {
handler.post {
eventSink?.success(
mapOf(
EventChannelConstants.KEY_TYPE to type,
EventChannelConstants.KEY_VALUE to data
)
)
}
}
}
Source: EventChannelHandler.kt
设计意图分析:
onListen/onCancel生命周期:Dart 侧EventChannel.receiveBroadcastStream()被监听时触发onListen,取消订阅时触发onCancel。事件推送的生命周期完全跟随 Dart 监听者的订阅状态,避免无监听者时原生侧空转发送。- 共享 EventSink + 主线程 Handler:所有 Processor 持有同一个
eventSink引用;sendEvent通过handler.post切回主线程再调用sink.success(...)。这保证了事件在 UI 线程按顺序投递(Flutter 平台通道要求),同时让蓝牙 SDK 的回调线程(可能为任意后台线程)无需关心线程切换。 - 统一信封
{type, value}:所有事件都包装为KEY_TYPE → String、KEY_VALUE → Map的结构。type 作为事件分类标识,value 承载业务数据。这一约定让 Dart 侧可以单流监听 + 按 type 分发,而不是为每类事件建立独立通道。 - Processor 的 start/stop 对称生命周期:每个 Processor 提供
startListening()/stopListening(),统一管理 SDK 回调注册与注销,防止泄漏。
核心流程
方法调用(MethodChannel 请求-响应)
sequenceDiagram
participant D as Dart (BleBaseManager)
participant F as Flutter Engine
participant A as Android (MethodChannelHandler)
participant M as 业务 Manager (如 AlarmManager)
participant S as 蓝牙 SDK
D->>D: invokeMethodWithDefault('save_alarm', args)
D->>F: 平台通道消息 (method + args)
F->>A: 分发到 MethodCallHandler.onMethodCall
A->>A: 按 MethodGroups 匹配方法所属分组
A->>M: 调用对应 Manager 方法
M->>S: 执行 SDK 操作
S-->>M: 结果 / 回调
M-->>A: 返回结果
A-->>F: result.success(result) / result.error(...)
F-->>D: Future 完成 / PlatformException
D->>D: null 时返回默认值,异常透传
事件推送(EventChannel 单向流)
sequenceDiagram
participant S as 蓝牙 SDK 回调线程
participant P as 子 Processor (如 DeviceProcessor)
participant E as EventChannelHandler
participant F as Flutter Engine
participant D as Dart (BleBaseEventProcessor)
D->>D: 订阅 receiveBroadcastStream()
D->>F: 建立事件监听
F->>E: onListen(sink)
E->>E: initProcessors() + startProcessors()
S-->>P: 设备状态变化回调
P->>E: sendEvent(type, data)
E->>E: handler.post 切回主线程
E->>F: sink.success({type, value})
F-->>D: 事件流推送
Note over D: 按 type 分发到业务处理器
D->>F: 取消订阅
F->>E: onCancel()
E->>E: stopProcessors()
使用示例
示例 1:业务页面通过通道调用原生方法
devices_page.dart 展示了业务页面如何直接使用 MethodChannel(此页面使用独立的通道名,说明示例 APP 中部分页面直接实例化通道而非走 BleBaseManager):
static const MethodChannel _methodChannel = MethodChannel(_methodChannelName);
Source: devices_page.dart
update_page.dart 则使用与插件一致的标准通道名 com.jieli.home_plugin/methods,用于 OTA 升级相关操作:
// 方法通道
static const MethodChannel _methodChannel = MethodChannel(
'com.jieli.home_plugin/methods',
);
Source: update_page.dart
对比这两个示例可见:插件库内部(
lib/)统一走BleBaseManager封装;而示例 APP 页面为了演示目的也可以直接使用MethodChannel。生产代码建议始终通过封装类调用,以集中处理默认值与异常。
示例 2:版本查询(默认值降级模式)
/// Get SDK version number
static Future<String> getSdkVersion() async {
return await invokeMethodWithDefault(
BleMethodConstants.methodGetSdkVersion,
'V?.?.?(?)',
);
}
Source: ble_base_manager.dart
当原生侧返回 null 或未实现该方法时,调用方得到占位版本号 V?.?.?(?) 而不是崩溃——这是对"SDK 版本能力差异"的防御性设计。
示例 3:事件通道订阅(处理器模式)
事件侧的使用模式是在 BleBaseEventProcessor 中静态持有通道并在初始化时订阅:
static const EventChannel _eventChannel = EventChannel('com.jieli.home_plugin/events');
Source: ble_base_event_processor.dart
原生侧对应地由 EventChannelHandler.onListen 启动全部 Processor,事件以 {type, value} 信封到达 Dart 后按 type 分发。
配置选项与常量约定
通道常量(Android 端)
| 常量对象 | 所在文件 | 作用 |
|---|---|---|
MethodChannelConstants | android/.../data/constant/MethodChannelConstants.kt | 定义全部方法名常量(METHOD_*),如 METHOD_START_SCAN、METHOD_CONNECT_DEVICE、METHOD_SET_EQ_VALUES、METHOD_START_OTA 等 |
EventChannelConstants | android/.../data/constant/EventChannelConstants.kt | 定义事件信封键:KEY_TYPE、KEY_VALUE 及事件类型常量 |
通道常量(iOS 端)
| 常量对象 | 所在文件 | 作用 |
|---|---|---|
MethodChannelConstants | ios/Classes/Constant/MethodChannelConstants.swift | 与 Android 同名的方法名常量,保证跨端方法名一致 |
EventChannelConstants | ios/Classes/Constant/EventChannelConstants.swift | 与 Android 同名的事件常量 |
双端常量同名是跨平台桥接的关键约束:Dart 侧发出的方法名字符串,必须在 Android 与 iOS 原生侧都能精确匹配,任何一端拼写偏差都会导致
MissingPluginException。
方法分组总览(Android MethodChannelHandler)
| 分组 | 代表方法 | 说明 |
|---|---|---|
DEVICE_CONNECT | METHOD_START_SCAN、METHOD_CONNECT_DEVICE、METHOD_DISCONNECT_BT_DEVICE | 扫描与连接生命周期 |
CONFIGURATION | METHOD_IS_BLE_WAY、METHOD_SET_USE_DEVICE_AUTH、METHOD_GET_BLE_REQUEST_MTU、METHOD_GET_SDK_VERSION 等 | 连接方式、鉴权、MTU 等配置查询/设置 |
EQ | METHOD_GET_EQ_DATA、METHOD_SET_EQ_MODE、METHOD_ADVANCED_SET_EQ | 均衡器控制 |
MEDIA | METHOD_CHANGE_AUX_MODE、METHOD_ID3_PLAY_PAUSE、METHOD_FIND_DEVICE | 音频媒体与 ID3 信息 |
OTA | METHOD_PICK_FILE、METHOD_READ_FILE_LIST、METHOD_START_OTA | 固件升级流程 |
LIGHT | METHOD_FUNCTION_COMMON、METHOD_SET_LIGHT_PARAMS | 灯光控制 |
FM | METHOD_CHANGE_FM_MODE、METHOD_FM_SEARCH_ALL、METHOD_FM_SELECT_FREQUENCY | FM 收音机 |
ALARM | METHOD_GET_ALL_ALARM_LIST、METHOD_SAVE_ALARM、METHOD_SET_SELECT_RING | 闹钟管理 |
SOUND_CARD | METHOD_GET_SOUND_CARD_EQ、METHOD_SET_SOUND_CARD_EFFECT | 声卡模式 |
DEVICE_SETTINGS | METHOD_CURRENT_DEVICE_TYPE、METHOD_GET_DEVICE_INFO、METHOD_UPDATE_KEY_FUNCTION | 设备信息与按键设置 |
API 参考
Dart 侧 BleBaseManager
static Future<dynamic> invokeMethod(String methodName, {Map<String, dynamic>? arguments})
通用方法调用入口,透传原生返回值。
- 参数:
methodName(String):原生方法名(来自BleMethodConstants)。arguments(Map<String, dynamic>?,可选):方法参数。
- 返回:
Future<dynamic>,原生侧result.success的值。 - 抛出:
PlatformException(原生侧result.error或通道缺失时)。
static Future<T> invokeMethodWithDefault<T>(String methodName, T defaultValue, {Map<String, dynamic>? arguments})
带默认值的方法调用,原生返回 null 时回退到 defaultValue。
- 参数:
methodName(String):原生方法名。defaultValue(T):结果为null时的回退值。arguments(Map<String, dynamic>?,可选):方法参数。
- 返回:
Future<T>,result ?? defaultValue强转T。 - 抛出:
PlatformException。
static Future<String> getSdkVersion()
查询原生 SDK 版本号,未实现时返回 'V?.?.?(?)'。
static Future<String> getAppVersion()
查询 App 版本号,未实现时返回 'V?.?.?(?)'。
static Future<void> popAllActivity()
关闭原生侧所有 Activity(方法名 methodPopAllActivity)。
Android 侧 Handler
class MethodChannelHandler(activity: MainActivity, eventChannelHandler: EventChannelHandler?) : MethodChannel.MethodCallHandler
方法通道处理器。实现 onMethodCall(call: MethodCall, result: MethodChannel.Result),按方法组路由到 Manager。
class EventChannelHandler(activity: Activity) : EventChannel.StreamHandler
事件通道处理器。实现 onListen(arguments, sink) / onCancel(arguments),管理子 Processor 生命周期;internal fun sendEvent(type: String, data: Map<String, Any>) 通过主线程 Handler 推送统一信封。
失败模式、边界情况与并发
通道缺失(MissingPluginException)
若 Dart 侧调用一个原生侧未注册/未实现的方法,invokeMethod 抛出 PlatformException(code: 'MissingPluginException')。BleBaseManager 的设计选择是重抛而非吞掉,让上层业务按需降级;invokeMethodWithDefault 则只对"结果为 null"降级,不拦截异常——这意味调用方需要区分"功能未实现(应隐藏 UI)"与"功能执行失败(应提示重试)"。
跨端方法名不一致
方法名在 Dart(BleMethodConstants)、Android(MethodChannelConstants.kt)、iOS(MethodChannelConstants.swift)三处各维护一份。任何一端拼写不一致都会静默表现为通道缺失。缓解手段是本文档中强调的同名常量约定与 test/jl_home_method_channel_test.dart 中的通道回归测试(注释示例可见测试曾校验 MethodChannel('jl_home') 的调用,说明通道名演进过程需要测试守护)。
事件流生命周期竞态
EventChannelHandler 的 eventSink 是可变字段:onCancel 置空、onListen 重新赋值。若 SDK 回调恰好发生在取消订阅之后、重新订阅之前,sendEvent 中 eventSink?.success(...) 的空安全调用会静默丢弃事件。这是可接受的竞态——事件流是"尽力而为"的实时通知,不承载需要持久化的状态;Dart 侧在重新订阅后应主动通过方法通道拉取一次全量状态(查询类方法 + 事件流组合是典型的状态同步模式)。
线程模型
- 方法通道:
MethodChannelHandler.onMethodCall在 Flutter 的平台线程(platform thread)执行,Manager 内部的耗时 SDK 调用需自行切换线程,避免阻塞平台线程。 - 事件通道:蓝牙 SDK 回调可能来自任意后台线程;
EventChannelHandler用Handler(Looper.getMainLooper())统一post到主线程再调用sink.success,确保 Flutter 平台通道要求的线程亲和性,同时让事件顺序可预测。
主线程 Handler 的背压问题
如果蓝牙 SDK 高频回调(如电量、进度),所有事件都经主线程 Handler 串行投递,主线程可能成为瓶颈。当前实现没有做节流/合并,属于简单可靠优先的取舍;在事件频率极高的场景可考虑在 Processor 层增加去重或节流。
性能与运维注意事项
- 单一事件通道 vs 多通道:所有事件共用一条
events通道,减少通道建立开销,代价是 Dart 侧需要按type分发。当前事件量级下这是合理设计。 - Processor 生命周期管理:
onListen启动、onCancel停止的对称设计保证无监听者时不持有 SDK 回调注册,避免内存泄漏与无效回调开销。Dart 侧应确保在页面销毁时取消事件订阅。 - 避免主线程阻塞:方法通道回调在平台线程执行,任何可能耗时超过数毫秒的原生操作(文件读取、OTA 校验、扫描)都应异步化;
MethodChannelHandler持有的MainActivity引用也提示了某些方法涉及 UI 交互(如METHOD_PICK_FILE),这类方法天然在主线程上下文工作。 - 跨平台一致性:iOS 与 Android 必须保持相同的通道名、方法名、事件信封结构,任何单端改动都会破坏双端行为一致性——这是桥接层变更评审时的首要检查点。
扩展点
新增一个业务域(如"语音翻译")
- Dart 侧:在
ble_method_constants.dart增加方法名常量;在BleBaseManager(或对应的领域 Manager 类)增加封装方法; - Android 侧:在
MethodChannelConstants.kt增加同名常量;在MethodChannelHandler的MethodGroups中新增分组Set<String>并在onMethodCall中路由;若需要主动上报事件,新增 Processor 并注册到EventChannelHandler.initProcessors()/startProcessors()/stopProcessors(); - iOS 侧:在 Swift 常量文件增加同名常量,在
MethodChannelHandler.swift/EventChannelHandler.swift中镜像实现; - 测试:在
test/jl_home_method_channel_test.dart风格的方法通道测试中覆盖新方法名。
事件类型扩展
新事件只需在 EventChannelConstants(双端)定义新 type 常量,Processor 通过 sendEvent(type, data) 推送,Dart 侧 BleBaseEventProcessor 增加对应分发分支。无需新增通道。
Manager/Processor 可插拔性
EventChannelHandler 中 Processor 通过构造函数注入 activity、eventSink、handler,耦合度低,可独立单元测试。新增 Processor 只要实现 startListening()/stopListening() 约定即可接入事件总线。
测试
仓库中已有 test/jl_home_method_channel_test.dart 用于方法通道回归验证。测试采用 Flutter 官方 TestDefaultBinaryMessengerBinding 的 mock 通道模式(文件中保留了 MethodChannel('jl_home') 与 MethodChannelJlHome 的注释示例,展示早期通道名 jl_home 到当前 com.jieli.home_plugin/methods 的演进)。
测试关注点建议:
- 方法名断言:mock 通道捕获
method参数,断言与常量一致,防止 Dart 与原生方法名漂移; - 默认值降级:mock 返回
null,断言invokeMethodWithDefault返回默认值; - 异常透传:mock 抛出
PlatformException,断言调用方收到异常而非静默吞掉。
相关链接
- BleBaseManager(Dart 方法通道封装)
- BleBaseEventProcessor(Dart 事件通道封装)
- MethodChannelHandler.kt(Android 方法分发)
- EventChannelHandler.kt(Android 事件汇聚)
- MethodChannelConstants.kt(Android 方法常量)
- EventChannelConstants.kt(Android 事件常量)
- EventChannelHandler.swift(iOS 事件处理器)
- MethodChannelHandler.swift(iOS 方法处理器)
- 通道测试
相关目录页建议:蓝牙 SDK 各业务域的协议细节(EQ/OTA/FM/闹钟/声卡)参见对应业务能力文档;示例 APP 页面实现参见 UI 相关页面文档。