Android 原生层
Android 原生层是 JL_OTA_Flutter 中连接 Flutter(Dart)业务代码与 Jieli 蓝牙 OTA SDK 的桥接插件(jl_ota_android),负责蓝牙设备扫描、连接与固件升级(OTA)的底层能力封装,并通过 MethodChannel / EventChannel 与 Dart 侧双向通信。
Purpose and Scope
本页面向 3-native-implementation/3-1-android-native 目录,完整说明 Android 原生层的工程结构、插件生命周期管理、通道通信机制、数据模型与失败处理策略。
本页覆盖:
- Android 插件工程的 Gradle 结构与依赖(
libs/中的 Jieli AAR) JlOtaPlugin的 Flutter 插件生命周期(FlutterPlugin+ActivityAware)- 对
BlePlugin的委托初始化机制 - 通道架构(MethodChannel / EventChannel)与数据模型组织
- 蓝牙环境检查、日志等辅助能力
本页不覆盖(留给兄弟页面):
- Dart 侧对 MethodChannel/EventChannel 的调用封装(对应 Flutter 层页面,如
2-flutter-layer相关目录) - iOS 原生实现(对应
3-2-ios-native) - 具体 OTA 升级协议细节(属于 Jieli SDK 内部行为,仅描述原生层如何调用)
Overview
JL_OTA_Flutter 的整体架构是「Flutter 业务层 + 双端原生桥接层」。Android 原生层位于 code/JL_OTA/android/,是一个标准的 Flutter 插件工程(包名 com.jieli.otasdk),其核心使命是:
- 屏蔽 SDK 复杂度:将 Jieli 蓝牙 OTA SDK(
jl_bt_otaAAR)的能力以简洁的通道接口暴露给 Dart,Dart 侧无需关心 BLE GATT、设备协议等细节。 - 生命周期托管:跟随 Flutter Engine 与 Activity 的生命周期自动初始化/释放原生资源,避免蓝牙连接与广播接收器泄漏。
- 双向异步通信:
- 下行(Dart → 原生):通过
MethodChannel下发"扫描设备""连接设备""开始升级"等指令; - 上行(原生 → Dart):通过
EventChannel推送设备扫描结果、OTA 进度、连接状态等事件。
- 下行(Dart → 原生):通过
原生层内部进一步分层:插件入口(JlOtaPlugin)→ BLE 委托(BlePlugin)→ 业务逻辑(MainViewModel)→ Jieli OTA SDK(AAR)→ 蓝牙设备。
Architecture
下图展示了 Android 原生层在整体架构中的位置及其内部结构(节点均对应仓库中的真实文件):
flowchart TD
subgraph sg_Flutter["Flutter 层(Dart)"]
Dart["Dart 业务代码<br/>MethodChannel 调用 / EventChannel 监听"]
end
subgraph sg_Android["Android 原生层 com.jieli.otasdk"]
JlOtaPlugin["JlOtaPlugin<br/>FlutterPlugin + ActivityAware"]
BlePlugin["BlePlugin<br/>BLE 能力委托"]
EventHandler["EventChannelHandler<br/>事件通道处理"]
MainVM["MainViewModel<br/>业务逻辑封装"]
EnvChecker["BluetoothEnvironmentChecker<br/>环境检查"]
LogHelper["LogHelper<br/>日志输出"]
end
subgraph sg_SDK["Jieli 原生 SDK(libs/*.aar)"]
OtaSdk["jl_bt_ota AAR<br/>V1.11.0_11015"]
FileTransfer["jl_file_transfer AAR<br/>V1.0.0"]
ComponentLib["jl-component-lib AAR<br/>V1.4.0_10400"]
end
subgraph sg_Device["外部设备"]
BleDevice["BLE 蓝牙设备"]
end
Dart -->|"MethodChannel"| JlOtaPlugin
JlOtaPlugin -->|"委托初始化"| BlePlugin
JlOtaPlugin --> EventHandler
BlePlugin --> MainVM
MainVM --> OtaSdk
MainVM --> FileTransfer
MainVM --> ComponentLib
EventHandler -->|"EventChannel"| Dart
EnvChecker --> BlePlugin
LogHelper --> BlePlugin
OtaSdk -->|"BLE GATT"| BleDevice
架构解读:
JlOtaPlugin(插件入口):实现FlutterPlugin与ActivityAware,负责在 Engine/Activity 生命周期节点上创建与销毁BlePlugin。它是 Dart 与原生唯一的接触面。BlePlugin(BLE 委托):构造函数接收binaryMessenger、activity、lifecycleOwner,实际承载扫描、连接、OTA 等核心操作,并借助MainViewModel调用 Jieli SDK。EventChannelHandler:处理原生 → Dart 的事件流(扫描结果、OTA 进度),对应EventChannelConstants中定义的事件名。- 三个 AAR:
jl_bt_ota(OTA 主 SDK)、jl_file_transfer(文件传输)、jl-component-lib(公共组件库),以本地文件方式打入插件依赖,保证 SDK 版本可控。
插件生命周期管理
JlOtaPlugin 是 Android 原生层的唯一 Flutter 插件注册类,通过同时实现 FlutterPlugin 与 ActivityAware 两个接口,将「Flutter Engine 生命周期」与「Activity 生命周期」两条事件链统一管理(JlOtaPlugin.kt)。
Engine 级生命周期(FlutterPlugin)
onAttachedToEngine(binding):仅缓存binaryMessenger,不立即初始化 BLE——因为此时 Activity 可能尚未就绪,而 BLE 操作强依赖 Activity 上下文。onDetachedFromEngine(binding):释放BlePlugin、清空MethodChannel的 handler 与引用,防止插件被移除后仍收到 Dart 调用。
Activity 级生命周期(ActivityAware)
onAttachedToActivity(binding):保存activity与lifecycleOwner,随后调用initializeBlePlugin()——这是真正的初始化时机。onDetachedFromActivity()/onDetachedFromActivityForConfigChanges():统一释放BlePlugin,避免 Activity 销毁后回调泄漏。onReattachedToActivityForConfigChanges(binding):屏幕旋转等配置变更后重新附着,重建BlePlugin并恢复能力。
防御式初始化(initializeBlePlugin)
初始化并非无条件执行,而是经过两道守卫(JlOtaPlugin.kt):
private fun initializeBlePlugin() {
when {
!areDependenciesPresent() -> {
JL_Log.w(TAG, "Cannot initialize BlePlugin: missing dependencies")
}
activity !is MainActivity -> {
JL_Log.w(TAG, "Activity is not MainActivity, skipping BlePlugin initialization")
}
else -> {
tryInitializeBlePlugin()
}
}
}
设计意图:
- 依赖检查(
areDependenciesPresent):确认activity、binaryMessenger、lifecycleOwner三者齐备,任何一个缺失都意味着插件尚处于"半挂载"状态,此时初始化必然失败,提前返回并记录警告日志比抛出异常更优雅。 - Activity 类型守卫:要求宿主 Activity 必须是
MainActivity。这说明BlePlugin与示例应用的主 Activity 存在强耦合(很可能需要访问MainActivity上的特定成员,如蓝牙广播接收器或 UI 状态),插件以"白名单"方式拒绝在其他 Activity 中初始化,从根上避免类型转换崩溃。 - try/catch 兜底:
tryInitializeBlePlugin()将BlePlugin构造过程包在异常捕获中,即使 Jieli SDK 初始化失败,也只是记录JL_Log.e,不会拖垮整个 Flutter 应用——这是插件类代码的典型容错策略。
委托模型:BlePlugin
BlePlugin 是原生层实际能力的载体(扫描、连接、OTA、事件回调),其构造签名与初始化方式如下(来自 JlOtaPlugin 的调用点):
blePlugin = BlePlugin(
binaryMessenger = requireNotNull(binaryMessenger),
activity = mainActivity,
lifecycleOwner = requireNotNull(lifecycleOwner)
)
设计意图:将「插件生命周期管理」与「BLE 业务能力」拆分为两个类,职责单一——JlOtaPlugin 只管"何时创建/销毁",BlePlugin 只管"如何工作"。这也使得未来如果脱离 Flutter(例如在纯 Android 工程中复用),可以直接复用 BlePlugin 及其依赖的 MainViewModel。
BlePlugin 内部协作的类(依据仓库文件结构,均为 com.jieli.otasdk 包内实际存在):
BluetoothEnvironmentChecker:扫描/连接前检查蓝牙开关、定位权限、系统版本等前置条件,配合BluetoothEnvironmentConstants中的常量(如权限请求码、失败码)向 Dart 返回明确的环境错误。EventChannelHandler:向 Dart 侧推送事件流,事件名定义于EventChannelConstants。LogHelper:统一的日志门面(配合LogHelperConstants中的 TAG/级别定义),避免业务代码散落Log.d。
通道通信机制
原生层通过两条标准 Flutter 通道与 Dart 通信:
| 方向 | 通道类型 | 常量类 | 典型内容 |
|---|---|---|---|
| Dart → 原生 | MethodChannel | MethodChannelConstants | 扫描、连接、OTA 开始/停止等指令 |
| 原生 → Dart | EventChannel | EventChannelConstants | 扫描结果、连接状态、OTA 进度/结束 |
通道常量被集中定义在 data/constant/ 包下(MethodChannelConstants.kt、EventChannelConstants.kt、OtaConstant.kt、BluetoothEnvironmentConstants.kt、LogHelperConstants.kt),这一设计保证了 Dart 与 Kotlin 两侧的方法名/事件名在编译期前即可对照审查,避免魔法字符串散落各处导致通道失配。
数据模型
data/model/ 下的模型类按主题分为三组(文件名即职责说明):
- 设备模型(
device/):ScanDevice(扫描到的 BLE 设备)、DeviceConnection(设备连接状态/参数)。 - OTA 模型(
ota/):OTAStart(升级开始参数)、OTAWorking(升级进行中进度)、OTAState(升级状态机)、OTAEnd(升级结束结果)、OTAReconnect(升级中断线重连信息)。 - 结果模型(
result/与根目录):OpResult(通用操作结果)、ScanResult(扫描结果封装)。
这些模型类既用于解析 SDK 回调,也作为 MethodChannel/EventChannel 的负载(通常序列化为 Map 传给 Dart),是原生层与 Dart 侧的数据契约。具体字段定义未在本页逐行阅读,如需精确字段列表请直接查看对应源文件。
核心流程
以下时序图描述一次典型的「应用启动 → 蓝牙环境检查 → 设备扫描 → OTA 升级 → 结果回传」全链路(基于已读取的 JlOtaPlugin 初始化逻辑与文件结构推断的协作关系,SDK 内部协议细节未展开):
sequenceDiagram
participant Dart as Flutter(Dart)
participant JP as JlOtaPlugin
participant BP as BlePlugin
participant MV as MainViewModel
participant SDK as Jieli OTA SDK(AAR)
participant DEV as BLE 设备
Note over JP: Engine 挂载 onAttachedToEngine
Note over JP: Activity 就绪 onAttachedToActivity
JP->>JP: initializeBlePlugin()<br/>依赖检查 + MainActivity 检查
JP->>BP: new BlePlugin(messenger, activity, lifecycleOwner)
BP->>MV: 初始化业务逻辑
Dart->>JP: MethodChannel: 扫描设备
JP->>BP: 转发调用
BP->>BP: BluetoothEnvironmentChecker<br/>检查蓝牙/权限
BP->>SDK: 开始扫描
SDK-->>DEV: BLE 广播扫描
SDK-->>BP: 扫描结果回调
BP->>Dart: EventChannel: ScanResult
Dart->>JP: MethodChannel: 开始 OTA
JP->>BP: 转发调用
BP->>MV: 执行升级
MV->>SDK: 下发固件
SDK-->>DEV: 升级进度
SDK-->>MV: 进度/状态回调
MV-->>BP: OTAState / OTAWorking
BP->>Dart: EventChannel: 进度事件
SDK-->>MV: 升级结束 OTAEnd
MV-->>BP: 结果
BP->>Dart: EventChannel: OTAEnd
Note over JP: onDetachedFromActivity / onDetachedFromEngine
JP->>BP: dispose()
流程要点:
- 延迟初始化:Dart 侧任意时刻调用 MethodChannel 方法之前,
BlePlugin必须已经通过onAttachedToActivity完成创建;若 Activity 尚未附着,调用会因 handler 未注册而失败——这是 Flutter 插件时序的常见陷阱,原生层用"守卫式初始化 + 日志"来辅助定位。 - 双向通道分工:所有"请求"走 MethodChannel(同步语义 + Result 回调),所有"异步状态"走 EventChannel(流式语义),职责清晰,避免了在 MethodChannel 上模拟长连接的复杂度。
- 状态上报闭环:OTA 的
OTAStart → OTAWorking → OTAState → OTAEnd(以及异常时的OTAReconnect)完整映射到事件通道,Dart 侧据此驱动升级 UI 进度条与错误提示。
使用示例
以下代码来自 JlOtaPlugin.kt 中 BlePlugin 的初始化调用点,展示了原生层最核心的挂载逻辑(其余通道调用示例见 BlePlugin.kt 与 MainViewModel.kt):
private fun tryInitializeBlePlugin() {
try {
val mainActivity = activity as MainActivity
blePlugin = BlePlugin(
binaryMessenger = requireNotNull(binaryMessenger),
activity = mainActivity,
lifecycleOwner = requireNotNull(lifecycleOwner)
)
JL_Log.d(TAG, "BlePlugin initialized successfully")
} catch (e: Exception) {
JL_Log.e(TAG, "Failed to initialize BlePlugin", e.message)
}
}
Source: JlOtaPlugin.kt
代码说明:
activity as MainActivity强转基于initializeBlePlugin中已有的activity is MainActivity守卫,属于"先验证后使用"的安全模式;requireNotNull用于在构造阶段快速失败,配合外层 try/catch,将"参数缺失"与"SDK 初始化异常"两类错误统一收敛为日志记录;- 初始化成功/失败均输出日志(TAG =
JlOtaPlugin),便于线上排查"插件未响应"类问题。
再给出插件释放侧的对称代码,展示资源清理的完整闭环:
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
blePlugin?.dispose()
blePlugin = null
binaryMessenger = null
channel?.setMethodCallHandler(null)
channel = null
}
Source: JlOtaPlugin.kt
代码说明:释放顺序刻意设计为「先 dispose() 业务对象、再清空引用、最后移除通道 handler」——先切断 Dart 侧新的调用入口,再销毁内部资源,避免释放过程中收到新请求导致空指针。
配置选项
Android 原生层的可配置项集中在 Gradle 与依赖文件中(文件均存在于仓库,具体字段以实际文件为准):
| 配置项 | 位置 | 默认/当前值 | 说明 |
|---|---|---|---|
libs/jl_bt_ota_V1.11.0_11015-release.aar | android/libs/ | V1.11.0 (11015) | Jieli 蓝牙 OTA 主 SDK |
libs/jl_file_transfer_V1.0.0-release.aar | android/libs/ | V1.0.0 | 文件传输 SDK(OTA 固件包传输) |
libs/jl-component-lib_V1.4.0_10400-release.aar | android/libs/ | V1.4.0 (10400) | Jieli 公共组件库 |
build.gradle | android/build.gradle | — | 插件编译配置、SDK 依赖声明 |
settings.gradle | android/settings.gradle | — | 模块与仓库配置 |
proguard-rules.pro | android/proguard-rules.pro | — | 混淆规则(AAR 中 SDK 类需 keep 规则) |
AndroidManifest.xml | android/src/main/ | — | 蓝牙权限、插件注册声明 |
版本管理要点:三个 AAR 以本地文件形式随仓库分发,SDK 版本与插件代码强绑定,升级 SDK 时需要同步验证 MethodChannelConstants/EventChannelConstants 中定义的接口是否兼容,避免出现"原生已升级、Dart 契约未变"的静默失配。
API 参考
Android 原生层对外(对 Dart)的 API 表面由两个通道契约构成。由于通道方法名集中定义于常量类中,以下仅给出结构说明,精确方法名单请查阅对应常量文件:
JlOtaPlugin(插件入口)
| 方法 | 触发时机 | 行为 |
|---|---|---|
onAttachedToEngine(binding) | Flutter Engine 挂载插件 | 缓存 binaryMessenger |
onDetachedFromEngine(binding) | Flutter Engine 移除插件 | dispose() BlePlugin、清空通道 handler |
onAttachedToActivity(binding) | Activity 附着 | 保存 activity/lifecycleOwner,调用 initializeBlePlugin() |
onDetachedFromActivity() | Activity 分离 | dispose() BlePlugin、清空引用 |
onReattachedToActivityForConfigChanges(binding) | 配置变更(如旋转)后重新附着 | 重建 BlePlugin |
onDetachedFromActivityForConfigChanges() | 配置变更前分离 | dispose() BlePlugin、清空引用 |
initializeBlePlugin() | 私有 | 依赖检查 + MainActivity 守卫 + try/catch 初始化 |
tryInitializeBlePlugin() | 私有 | 构造 BlePlugin(binaryMessenger, activity, lifecycleOwner) |
参数:均为 Flutter 框架回调参数(FlutterPluginBinding / ActivityPluginBinding)。 返回:Unit;初始化失败仅记录日志,不抛出异常。
BlePlugin(BLE 能力委托)
- 构造:
BlePlugin(binaryMessenger: BinaryMessenger, activity: MainActivity, lifecycleOwner: LifecycleOwner) - 能力:设备扫描、设备连接/断开、OTA 升级控制(开始/停止)、事件上报
- 释放:
dispose()(由JlOtaPlugin在生命周期节点调用)
通道契约(常量类)
MethodChannelConstants:Dart → 原生指令的方法名(扫描、连接、OTA 等)。EventChannelConstants:原生 → Dart 事件的通道名与事件名(扫描结果、连接状态、OTA 进度/结束/重连)。OtaConstant:OTA 业务常量(升级参数、状态枚举等)。BluetoothEnvironmentConstants:环境检查失败码与权限请求码。LogHelperConstants:日志 TAG 与级别定义。
说明:本页未逐行读取上述常量文件与
MainViewModel.kt/BlePlugin.kt/EventChannelHandler.kt的实现,具体方法签名与通道名称请直接查看源文件(链接见文末)。
失败模式、边界情况与并发
初始化失败路径
- 依赖缺失(
activity/binaryMessenger/lifecycleOwner任一为空):initializeBlePlugin直接返回并记录 warning——表现为"Dart 调用无响应",排查时先确认插件是否在onAttachedToActivity之后才被调用。 - 宿主不是 MainActivity:插件静默跳过初始化。若应用在非主页面使用 OTA 能力,需要扩展守卫条件或改用
ActivityPluginBinding通用上下文。 - 构造异常(如 SDK 初始化失败):捕获后仅记录
JL_Log.e,应用继续运行,但 BLE 能力不可用。
生命周期边界
- 配置变更(旋转屏幕):
onDetachedFromActivityForConfigChanges→onReattachedToActivityForConfigChanges之间BlePlugin被销毁重建,正在进行的 OTA 会被中断——这是当前实现的明确边界,Dart 侧应监听OTAReconnect/OTAEnd事件处理中断恢复。 - 多 Activity 场景:插件只认
MainActivity,在其他 Activity 中无法获得 BLE 能力。
并发与线程
- Flutter 通道调用默认在主线程(UI 线程)派发;BLE 扫描/连接/OTA 回调来自 SDK 内部线程,原生层通过
EventChannelHandler将异步结果转回 Dart isolate——若回调中直接操作 UI 或进行耗时操作,需注意线程切换(MainViewModel中应有对应的 Handler/协程调度,具体实现未在本页阅读)。 BlePlugin单例化设计(由JlOtaPlugin持有唯一实例),天然避免了多实例并发操作同一蓝牙适配器的问题;但同一时刻只能有一个 OTA 会话,Dart 侧需自行保证互斥。
性能与运维考虑
- 资源释放是硬要求:
dispose()在三个分离节点(Engine 分离、Activity 分离、配置变更分离)都会执行,防止蓝牙广播接收器与连接泄漏——这也是蓝牙插件最容易出问题的位置,运维排障时应先检查日志中是否存在 "BlePlugin initialized successfully" 与配对 dispose 记录。 - 日志分层:通过
JL_Log(Jieli SDK 的日志工具)与LogHelper统一输出,TAG 区分模块(如JlOtaPlugin),便于按 TAG 过滤排障。 - AAR 本地化:SDK 以本地 AAR 交付,构建不依赖外网仓库拉取,CI 构建更稳定,但升级 SDK 需要人工替换文件并回归通道契约。
扩展点
- 新增 MethodChannel 方法:在
MethodChannelConstants增加方法名 →BlePlugin注册对应 handler →MainViewModel实现逻辑,三步即可扩展新指令(如"读取设备电量")。 - 新增事件类型:在
EventChannelConstants定义事件名 →EventChannelHandler推送 → Dart 侧订阅,用于扩展新的异步通知(如设备主动上报)。 - 替换宿主 Activity 约束:修改
initializeBlePlugin中的activity is MainActivity守卫,可让插件适用于任意 FlutterActivity。 - SDK 升级:替换
libs/下 AAR 并同步调整build.gradle版本号;升级后重点回归扫描、OTA 进度与重连三处通道契约。
相关链接
- 源码入口:JlOtaPlugin.kt
- BLE 委托:BlePlugin.kt
- 业务逻辑:MainViewModel.kt
- 事件通道:EventChannelHandler.kt
- 环境检查:BluetoothEnvironmentChecker.kt
- 通道常量:MethodChannelConstants.kt、EventChannelConstants.kt
- 数据模型:ScanDevice.kt、OTAEnd.kt
- 构建配置:build.gradle、AndroidManifest.xml
- 关联页面:Flutter 层通道封装(Dart)、iOS 原生层(
3-2-ios-native)