充电仓与彩屏仓管理
充电仓与彩屏仓管理是 JL_Home 中负责 TWS 耳机充电仓设备能力接入的功能模块:Flutter 侧通过 BleChargingCaseManager 提供发送接口,Android 侧由 ChargingCaseManager 统一分发处理,涵盖充电仓状态同步、屏保亮度调节、屏保资源管理(刷新/切换/删除/上传转换)以及彩屏仓的消息推送开关与天气显示等能力。
Purpose and Scope
本页面完整介绍「充电仓与彩屏仓管理」这一能力在 Flutter 与 Android 双端的实现机制:
- Flutter 侧发送接口
BleChargingCaseManager提供的全部方法及其参数语义; - Android 侧
ChargingCaseManager的方法分发、参数校验、错误码约定; - 充电仓三大子模块
BrightnessManager、ResourceManager、UploadManager的职责划分; - 彩屏仓消息推送(短信/微信/QQ/钉钉/飞书/天气)开关控制与消息推送状态流;
- 基于
ChargingCaseSettingViewModel的设备状态同步与观察者机制; - 图片转换上传(屏保自定义)的完整调用链与取消上传逻辑。
本页面不覆盖:蓝牙连接/扫描与 RCSP 协议层本身的实现(属于蓝牙连接管理页)、其他设备类型(耳机本体、手表等)的功能管理。屏保图片的底层编解码格式(如特定色深转换)仅在有证据处提及。
Overview
在 JL_Home 架构中,充电仓(Charging Case)与彩屏仓(Color Screen Case)都是通过蓝牙 RCSP 协议与手机 App 交互的外设。彩屏仓本质上是带彩屏的充电仓:除了基础的电池/状态信息外,还支持屏保(screen saver)自定义、亮度调节,以及消息推送(将手机短信、微信、QQ、钉钉、飞书等通知推送到仓体彩屏显示)和天气显示。
整个功能采用 Flutter ↔ MethodChannel/EventChannel ↔ Android SDK ↔ RCSP SDK 的分层结构:
- Flutter 层:
libs/Send Interface/ble_charging_case_manager.dart中的BleChargingCaseManager是唯一的发送入口,所有方法都是静态方法,通过BleBaseManager.invokeMethod发起 MethodChannel 调用; - Android SDK 层:
ChargingCaseManager接收方法调用,解析参数、校验合法性,再委托给BrightnessManager、ResourceManager、UploadManager三个子模块执行具体业务; - 设备状态层:
ChargingCaseSettingViewModel(单例)通过 RCSP SDK 同步设备端充电仓信息,并通过LiveData观察者把亮度、屏保等状态变化回传给 Flutter(EventChannel); - 接收层:Flutter 侧
BleChargingCaseProcessor处理来自 Android 的事件,其中messagePushStateStream专门承载彩屏仓消息推送状态。
Architecture
flowchart TD
subgraph sg_Flutter["Flutter 层"]
UI["充电仓/彩屏仓设置页"]
SendApi["BleChargingCaseManager<br/>(libs/Send Interface)"]
RecvApi["BleChargingCaseProcessor<br/>messagePushStateStream"]
EventStream["BleEventStream<br/>(libs/Receive Interface)"]
end
subgraph sg_Bridge["跨端桥接"]
MC["MethodChannel<br/>invokeMethod / Result"]
EC["EventChannel<br/>设备状态推送"]
end
subgraph sg_Android["Android SDK 层"]
CM["ChargingCaseManager"]
Handler["ChargingCaseHandler<br/>(参数解析)"]
BM["BrightnessManager<br/>亮度调节"]
RM["ResourceManager<br/>屏保资源管理"]
UM["UploadManager<br/>图片转换上传"]
VM["ChargingCaseSettingViewModel<br/>(单例)"]
InfoChange["ChargingCaseInfoChange<br/>(信息变更模型)"]
end
subgraph sg_SDK["RCSP 设备层"]
RCSP["RCSPController"]
Device["蓝牙耳机/充电仓设备"]
end
UI --> SendApi
SendApi -->|"invokeMethod"| MC
MC -->|"handleChargingCase"| CM
CM --> Handler
CM --> BM
CM --> RM
CM --> UM
BM --> VM
RM --> VM
UM --> VM
VM -->|"ChargingCaseInfo 变更"| InfoChange
InfoChange -->|"observeForever"| CM
VM -->|"syncDeviceState"| RCSP
RCSP -->|"BLE/RCSP 协议"| Device
CM -->|"success/error 回调"| MC
MC -->|"Result"| SendApi
VM -->|"EventChannel 推送"| EC
EC --> EventStream
EventStream -->|"静态流"| RecvApi
架构说明:发送路径(蓝色实线)从 Flutter UI 经 BleChargingCaseManager 走 MethodChannel 到达 ChargingCaseManager,由后者按方法名分发到三个子模块;状态路径(虚线)则由 ChargingCaseSettingViewModel 从 RCSP 设备同步信息,通过 LiveData 观察者回调 ChargingCaseManager,再经 EventChannel 把消息推送状态推回 Flutter 的 BleChargingCaseProcessor。这种「命令走 MethodChannel、事件走 EventChannel」的双通道设计,是 Flutter 插件与原生 SDK 通信的标准模式,保证请求有明确的返回值语义,而异步设备事件不被请求-响应模型阻塞。
双端通信机制
MethodChannel 方法调用约定
Flutter 侧所有充电仓/彩屏仓操作都收敛到 BleChargingCaseManager 的静态方法,内部统一通过 BleBaseManager.invokeMethod(需要返回值)或 invokeMethodWithDefault(失败时返回默认值)发送:
Source: ble_charging_case_manager.dart
/// Ble charging case manager
class BleChargingCaseManager {
static Future<bool> enterMessagePage() async {
return await BleBaseManager.invokeMethodWithDefault(
BleMethodConstants.methodEnterMessagePage,
false,
);
}
设计意图:invokeMethodWithDefault 适用于「触发型」指令(如进入消息页、刷新资源),这类调用不需要关心原生侧是否成功,失败时直接落到默认值 false,避免 Flutter 侧为每个调用写 try/catch;而 invokeMethod 适用于需要业务结果的操作(如切换屏保资源、删除资源、上传转换),返回值类型由各方法的泛型推导决定(Future<bool>)。
方法清单总览
| Flutter 方法 | 对应 MethodChannel 常量 | 方向/语义 | 返回值 |
|---|---|---|---|
enterMessagePage() | methodEnterMessagePage | 彩屏仓:进入消息推送页面 | bool(默认 false) |
setTotalSwitchState | methodSetTotalSwitchState | 彩屏仓:消息推送总开关 | void |
setSmsSwitchState | methodSetSmsSwitchState | 彩屏仓:短信推送开关 | void |
setWechatSwitchState | methodSetWechatSwitchState | 彩屏仓:微信推送开关 | void |
setQQSwitchState | methodSetQQSwitchState | 彩屏仓:QQ 推送开关 | void |
setDingTalkSwitchState | methodSetDingTalkSwitchState | 彩屏仓:钉钉推送开关 | void |
setLarkSwitchState | methodSetLarkSwitchState | 彩屏仓:飞书推送开关 | void |
setWeatherState | methodSetWeatherState | 彩屏仓:天气显示开关 | void |
setBrightness | methodChargingCaseBrightness | 充电仓:屏保亮度 | void |
getChargingState() | methodGetChargingCaseState | 充电仓:拉取并同步设备状态 | void(默认 false) |
refreshResources() | methodRefreshChargingCaseResources | 充电仓:刷新屏保资源 | void(默认 false) |
changeChargingCaseIndex | methodChangeChargingCaseResource | 充电仓:切换当前屏保 | bool |
deleteResources | methodDeleteChargingCaseResources | 充电仓:批量删除屏保 | bool |
convertUploadFile | methodChargingCaseInfo | 充电仓:图片转换并上传为屏保 | bool |
cancelUpload() | methodCancelUpload | 充电仓:取消进行中的上传 | bool(默认 false) |
Android 侧实现详解
ChargingCaseManager:统一分发入口
Android 侧核心类 ChargingCaseManager(位于 code/JieLi_Home_Demo/android/src/main/kotlin/com/jieli/bt/sdk/data/manager/chargingcase/)持有三个模块管理器与一个单例 ViewModel:
Source: ChargingCaseManager.kt
class ChargingCaseManager {
private var eventChannelHandler: EventChannelHandler? = null
private var btRcspEventCallback: BTRcspEventCallback? = null
private val controller = RCSPController.getInstance()
private val chargingCaseSettingViewModel = ChargingCaseSettingViewModel.getInstance()
// Module managers
private lateinit var brightnessManager: BrightnessManager
private lateinit var resourceManager: ResourceManager
private lateinit var uploadManager: UploadManager
// Observer for charging case information changes
private val mCaseInfoObserver = Observer<ChargingCaseInfoChange> { infoChange ->
infoChange.let {
val info = infoChange.chargingCaseInfo
when (infoChange.func) {
ChargingCaseInfo.FUNC_BRIGHTNESS -> brightnessManager.handleBrightnessUpdate(info)
ChargingCaseInfo.FUNC_CURRENT_SCREEN_SAVER -> resourceManager.loadCurrentResources()
}
}
}
设计要点:
- 单例 ViewModel 共享状态:
ChargingCaseSettingViewModel.getInstance()是进程级单例,持有设备端ChargingCaseInfo的最新快照,所有子模块都读写同一份数据,避免多实例状态漂移; - 观察者驱动:
mCaseInfoObserver监听ChargingCaseInfoChange(携带变更功能码func+ 最新chargingCaseInfo),当设备端亮度(FUNC_BRIGHTNESS)或当前屏保(FUNC_CURRENT_SCREEN_SAVER)变化时,自动分发到对应子模块——这是「设备主动变化 → App 被动响应」的异步路径; - 延迟初始化:三个子模块在首次收到方法调用时通过
initManagers()创建,lateinit+isInitialized双重保障避免重复实例化。
方法分发:handleChargingCase
所有来自 Flutter 的充电仓调用统一进入 handleChargingCase,按 call.method 字符串分发:
Source: ChargingCaseManager.kt
fun handleChargingCase(
call: MethodCall,
result: MethodChannel.Result? = null,
eventChannelHandler: EventChannelHandler? = null
) {
this.eventChannelHandler = eventChannelHandler
initManagers(eventChannelHandler)
when (call.method) {
MethodChannelConstants.METHOD_GET_CHARGING_CASE_STATE -> getChargingState()
MethodChannelConstants.METHOD_CHARGING_CASE_BRIGHTNESS -> handleSetBrightness(call, result)
MethodChannelConstants.METHOD_REFRESH_CHARGING_CASE_RESOURCES -> handleRefreshResources()
MethodChannelConstants.METHOD_CHANGE_CHARGING_CASE_RESOURCE -> handleChangeResources(call, result)
MethodChannelConstants.METHOD_DELETE_CHARGING_CASE_RESOURCES -> handleDeleteResources(call, result)
MethodChannelConstants.METHOD_CHARGING_CASE_INFO -> handleConvertUploadFile(call, result)
MethodChannelConstants.METHOD_CANCEL_UPLOAD -> handleCancelUpload(result)
else -> result?.notImplemented()
}
}
分发策略值得注意:凡是无参数或参数可缺省的「触发型」调用(获取状态、刷新资源、取消上传)不返回结果或返回默认值;凡是携带业务参数的操作(亮度、切换、删除、上传)都必须校验参数,失败时通过 result.error(code, message, null) 返回结构化错误。未识别的方法名统一走 result?.notImplemented(),让 Flutter 侧能区分「方法不存在」与「执行失败」。
充电仓状态同步:getChargingState
getChargingState() 是充电仓功能的初始化入口,按固定顺序执行三步:
Source: ChargingCaseManager.kt
fun getChargingState() {
resourceManager.resetResourceState()
setupViewModelObservers()
chargingCaseSettingViewModel.syncDeviceState()
}
resetResourceState():清空上一轮的屏保资源缓存与选中状态,保证每次进入充电仓页面都是干净起点;setupViewModelObservers():注册两个LiveData观察者(见下文「状态观察与回传」);syncDeviceState():通过 ViewModel 向 RCSP SDK 发起设备状态同步,拉取最新ChargingCaseInfo。
先重置、再订阅、后同步的顺序是有意的:先重置避免旧数据残留,先订阅再同步避免错过同步结果事件(LiveData 是粘性的,订阅后立即能收到最后一次的值)。
参数校验与错误码约定
ChargingCaseManager 对每个带参方法都做严格校验,参数解析逻辑集中在 ChargingCaseHandler 的扩展函数中(getIntArgument、getBooleanArgument、getStringArgument、getMapArguments、getBrightnessArgument、getDeleteIndices)。错误码统一约定如下:
| 错误码 | 触发场景 | 示例 |
|---|---|---|
MISSING_PARAMETERS | 必填参数缺失或类型不合法 | 亮度参数缺失;iconState/index 缺失;funcIndex/clickIndex/showLockState/bgImageBase64Str 缺失 |
INVALID_ARGUMENTS | 参数容器本身缺失或无效 | call.arguments 不是 Map;删除索引列表无效 |
NO_VALID_FILES | 删除操作过滤后无有效文件 | 所选索引对应的屏保文件全部不存在或已被移除 |
notImplemented | 方法名未注册 | Flutter 调用未知方法名 |
以删除资源为例,展示了「先解析 → 再过滤 → 后执行」的三段式:
Source: ChargingCaseManager.kt
private fun handleDeleteResources(call: MethodCall, result: MethodChannel.Result?) {
val deleteIndices = ChargingCaseHandler.run { call.getDeleteIndices() } ?: run {
result?.error("INVALID_ARGUMENTS", "Delete indices are missing or invalid", null)
return
}
val (selectedFiles, indicesToRemove) = resourceManager.prepareFilesForDeletion(deleteIndices)
if (selectedFiles.isEmpty()) {
result?.error("NO_VALID_FILES", "No valid files found for deletion", null)
return
}
resourceManager.removeFromResourceList(indicesToRemove)
resourceManager.executeResourceDeletion(selectedFiles, result)
}
设计意图:prepareFilesForDeletion 在删除前把索引转换为真实文件路径并过滤失效项,removeFromResourceList 先更新内存中的资源列表(UI 立即响应),executeResourceDeletion 再异步删除物理文件——先更新内存、后落盘删除的顺序让 UI 无感,同时避免删除失败时列表与文件不一致的脏状态。
状态观察与回传机制
ChargingCaseManager 通过 setupViewModelObservers() 订阅 ViewModel 的两条 LiveData,一条响应设备功能结果、一条响应充电仓信息变更:
Source: ChargingCaseManager.kt
private fun setupViewModelObservers() {
// Observe function result changes
chargingCaseSettingViewModel.functionResultMLD.observeForever { functionResult ->
if (!functionResult.isSuccess()) return@observeForever
val info = chargingCaseSettingViewModel.getChargingCaseInfo()
when (functionResult.data) {
ChargingCaseInfo.FUNC_BRIGHTNESS -> brightnessManager.handleBrightnessUpdate(info)
ChargingCaseInfo.FUNC_CURRENT_SCREEN_SAVER -> resourceManager.loadCurrentResources()
}
}
// Observe charging case information changes
chargingCaseSettingViewModel.chargingCaseInfoMLD.observeForever(mCaseInfoObserver)
}
两条观察路径的分工:
functionResultMLD:承载「操作结果 + 功能码」,只有isSuccess()为真才继续处理,避免把失败结果当状态刷新;成功时按功能码分发——亮度写入成功则handleBrightnessUpdate,屏保切换成功则loadCurrentResources重新加载当前屏保;chargingCaseInfoMLD:承载ChargingCaseInfoChange对象(内含chargingCaseInfo+func),覆盖设备端主动上报的信息变化。
observeForever 意味着观察者生命周期跟随进程而非界面;因此 onCancel() 中必须手动移除观察者并释放资源,防止内存泄漏:
Source: ChargingCaseManager.kt
fun onCancel() {
chargingCaseSettingViewModel.chargingCaseInfoMLD.removeObserver(mCaseInfoObserver)
eventChannelHandler = null
resourceManager.resetResourceState()
uploadManager.release()
uploadManager.cancelCurrentUpload()
}
onCancel() 在插件销毁/断开时被调用:先摘除观察者、清空事件通道引用,再重置资源状态、释放上传管理器并取消进行中的上传——保证断连后重新连接时不会残留上次会话的回调与文件句柄。
核心流程
屏保图片转换上传流程
convertUploadFile 是充电仓最复杂的链路:Flutter 传入背景图(Base64)与锁定图标(Base64),Android 侧负责创建资源消息、调用 RCSP 下发、跟踪进度。整体时序如下:
sequenceDiagram
participant UI as Flutter 设置页
participant Send as BleChargingCaseManager
participant MC as MethodChannel
participant CM as ChargingCaseManager
participant H as ChargingCaseHandler
participant VM as ChargingCaseSettingViewModel
participant UM as UploadManager
participant RCSP as RCSPController/设备
UI->>Send: convertUploadFile(funcIndex, clickIndex, isChecked, bgBase64, lockBase64)
Send->>MC: invokeMethod(methodChargingCaseInfo, arguments)
MC->>CM: handleChargingCase(call, result)
CM->>H: getMapArguments() 解析参数
H-->>CM: Map / null
CM->>H: 校验 funcIndex / clickIndex / showLockState / bgImageBase64Str
alt 任一参数缺失
CM-->>MC: result.error("MISSING_PARAMETERS", ...)
MC-->>Send: 抛 MethodChannel 错误
else 参数合法
CM->>VM: getChargingCaseInfo() 取设备快照
CM->>UM: createResourceMsg(info) 构造资源消息
UM-->>CM: resourceMsg
CM->>UM: handleImageConversion(funcIndex, clickIndex, showLockState, bgBase64, resourceMsg, result)
UM->>RCSP: 转换图片并下发屏保资源
RCSP-->>UM: 上传进度/结果
UM-->>CM: 成功或失败回调
CM-->>MC: result.success(true) / result.error(...)
MC-->>Send: Future<bool> 完成
Send-->>UI: 返回结果
end
关键点:handleConvertUploadFile 在进入 UploadManager 之前完成了全部参数校验与快照获取——resourceMsg 基于设备当前 ChargingCaseInfo 构造,确保屏保数据与设备能力匹配;showLockState 决定屏保是否叠加锁定图标。
设备状态同步流程
sequenceDiagram
participant UI as Flutter 设置页
participant Send as BleChargingCaseManager
participant MC as MethodChannel
participant CM as ChargingCaseManager
participant RM as ResourceManager
participant VM as ChargingCaseSettingViewModel
participant RCSP as RCSP 设备
UI->>Send: getChargingState()
Send->>MC: invokeMethodWithDefault(methodGetChargingCaseState)
MC->>CM: handleChargingCase()
CM->>RM: resetResourceState() 清空旧缓存
CM->>CM: setupViewModelObservers() 注册 LiveData 观察者
CM->>VM: syncDeviceState()
VM->>RCSP: 请求设备充电仓信息
RCSP-->>VM: ChargingCaseInfo 变更
VM-->>CM: functionResultMLD 回调 (isSuccess)
alt 亮度功能变更
CM->>BM: handleBrightnessUpdate(info)
else 当前屏保变更
CM->>RM: loadCurrentResources()
end
CM-->>MC: 默认结果 false
MC-->>Send: 完成
Send-->>UI: 页面加载完成
数据模型
ChargingCaseInfoChange
ChargingCaseInfoChange(Java)是 Android 侧观察者模式的数据载体,把「哪个功能变了」与「最新的充电仓信息」打包成一条变更事件:
classDiagram
class ChargingCaseInfoChange {
+ChargingCaseInfo chargingCaseInfo
+int func
}
class ChargingCaseSettingViewModel {
+LiveData~FunctionResult~ functionResultMLD
+LiveData~ChargingCaseInfoChange~ chargingCaseInfoMLD
+syncDeviceState()
+getChargingCaseInfo() ChargingCaseInfo
}
class ChargingCaseManager {
+handleChargingCase(call, result, handler)
+getChargingState()
+onCancel()
}
ChargingCaseSettingViewModel --> ChargingCaseInfoChange : "发布变更"
ChargingCaseManager --> ChargingCaseSettingViewModel : "observeForever"
| 模型/类 | 位置 | 职责 |
|---|---|---|
ChargingCaseInfoChange | data/model/chargingcase/ChargingCaseInfoChange.java | 携带 func 功能码 + chargingCaseInfo 快照的变更事件 |
ChargingCaseSettingViewModel | data/model/chargingcase/ChargingCaseSettingViewModel.kt | 单例状态中心:设备状态同步、两条 LiveData、信息快照 |
ChargingBinUtil | util/ChargingBinUtil.java | 充电仓通用工具(编解码/数据组装辅助) |
彩屏仓消息推送模型
Flutter 侧接收层在 libs/Receive Interface/ble_event_stream.dart 中暴露彩屏仓消息推送状态流:
Source: ble_event_stream.dart
static Stream<MessagePushStateModel> get messagePushStateStream =>
BleChargingCaseProcessor.messagePushStateStream;
BleChargingCaseProcessor 是彩屏仓事件处理器,messagePushStateStream 为 Stream<MessagePushStateModel>,承载设备端消息推送开关状态的变化(例如用户在其他端改动了推送开关,App 通过此流同步 UI)。消息推送的通道级开关状态模型由 MessagePushStateModel 描述,推送功能码与 ChargingCaseInfo 的功能常量(如 FUNC_BRIGHTNESS、FUNC_CURRENT_SCREEN_SAVER)类似,属于 RCSP SDK 定义的设备功能枚举。
彩屏仓:消息推送与天气
彩屏仓区别于普通充电仓的核心能力是「消息推送 + 天气显示」,对应 BleChargingCaseManager 的一组开关方法:
Source: ble_charging_case_manager.dart
static Future<void> setTotalSwitchState({
required bool totalSwitchState,
}) async {
await BleBaseManager.invokeMethod(
BleMethodConstants.methodSetTotalSwitchState,
arguments: {BleMethodConstants.argTotalSwitchState: totalSwitchState},
);
}
static Future<void> setSmsSwitchState({required bool smsSwitchState}) async {
await BleBaseManager.invokeMethod(
BleMethodConstants.methodSetSmsSwitchState,
arguments: {BleMethodConstants.argSmsSwitchState: smsSwitchState},
);
}
// ... setWechatSwitchState / setQQSwitchState / setDingTalkSwitchState /
// setLarkSwitchState / setWeatherState 结构一致
设计意图:每个开关独立成方法、独立参数名(argTotalSwitchState、argSmsSwitchState、argWechatSwitchState 等),虽然代码重复,但换来的是 MethodChannel 参数名与语义一一对应、可静态检索、可独立演进——插件边界上「显式优于通用」是更安全的取舍。开关语义遵循「总开关 + 分应用开关」层级:setTotalSwitchState 控制消息推送总闸,各应用开关在总开关开启时才生效;setWeatherState 控制天气显示。
彩屏仓的整体能力视图:
flowchart LR
subgraph sg_Case["彩屏仓能力"]
MSG["消息推送"]
WX["天气显示"]
SCR["屏保自定义"]
BRT["亮度调节"]
end
subgraph sg_Switch["推送开关组"]
TOTAL["总开关 totalSwitchState"]
SMS["短信 smsSwitchState"]
WECHAT["微信 wechatSwitchState"]
QQ["QQ qqSwitchState"]
DING["钉钉 dingTalkSwitchState"]
LARK["飞书 larkSwitchState"]
end
MSG --> TOTAL
TOTAL --> SMS
TOTAL --> WECHAT
TOTAL --> QQ
TOTAL --> DING
TOTAL --> LARK
MSG -->|"进入消息页 enterMessagePage"| UI["仓体彩屏消息页"]
WX -->|"weatherState"| UI
SCR -->|"convertUploadFile / changeChargingCaseIndex"| UI
BRT -->|"setBrightness"| UI
使用示例
Flutter 侧:进入充电仓页面并同步状态
// 1. 进入充电仓页面时拉取并同步设备状态
await BleChargingCaseManager.getChargingState();
// 2. 若为彩屏仓,进入消息推送页面
final entered = await BleChargingCaseManager.enterMessagePage();
Source: ble_charging_case_manager.dart
Flutter 侧:切换屏保资源与删除资源
// 切换当前屏保:iconState 表示屏保分组状态,index 为目标资源索引
final changed = await BleChargingCaseManager.changeChargingCaseIndex(
iconState: currentIconState,
index: targetIndex,
);
// 批量删除选中的屏保资源(自动去重)
final deleted = await BleChargingCaseManager.deleteResources(
selectedIndices: [0, 2, 4],
);
Source: ble_charging_case_manager.dart
注意 deleteResources 内部先 selectedIndices.toSet().toList() 去重,避免重复索引导致原生侧重复删除。
Flutter 侧:自定义屏保图片上传
// 将用户选择的背景图与锁定图标上传为屏保
final ok = await BleChargingCaseManager.convertUploadFile(
funcIndex: funcIndex, // 屏保功能索引
clickIndex: clickIndex, // 当前点击的资源位
isChecked: showLockState, // 是否显示锁定图标
backgroundImageBase64: bgBase64, // 背景图 Base64(可为空)
lockIconBase64: lockBase64, // 锁定图标 Base64
);
Source: ble_charging_case_manager.dart
Android 侧:接收并分发 Flutter 调用(框架视角)
fun handleChargingCase(
call: MethodCall,
result: MethodChannel.Result? = null,
eventChannelHandler: EventChannelHandler? = null
) {
this.eventChannelHandler = eventChannelHandler
initManagers(eventChannelHandler)
when (call.method) {
MethodChannelConstants.METHOD_GET_CHARGING_CASE_STATE -> getChargingState()
MethodChannelConstants.METHOD_CHARGING_CASE_BRIGHTNESS -> handleSetBrightness(call, result)
MethodChannelConstants.METHOD_REFRESH_CHARGING_CASE_RESOURCES -> handleRefreshResources()
MethodChannelConstants.METHOD_CHANGE_CHARGING_CASE_RESOURCE -> handleChangeResources(call, result)
MethodChannelConstants.METHOD_DELETE_CHARGING_CASE_RESOURCES -> handleDeleteResources(call, result)
MethodChannelConstants.METHOD_CHARGING_CASE_INFO -> handleConvertUploadFile(call, result)
MethodChannelConstants.METHOD_CANCEL_UPLOAD -> handleCancelUpload(result)
else -> result?.notImplemented()
}
}
Source: ChargingCaseManager.kt
API 参考
Flutter 侧:BleChargingCaseManager(静态方法)
enterMessagePage() → Future<bool>
彩屏仓进入消息推送页面。失败时默认返回 false。
setTotalSwitchState({required bool totalSwitchState}) → Future<void>
设置消息推送总开关。参数:totalSwitchState(bool,必填)。
setSmsSwitchState({required bool smsSwitchState}) → Future<void>
设置短信推送开关。参数:smsSwitchState(bool,必填)。
setWechatSwitchState({required bool wechatSwitchState}) → Future<void>
设置微信推送开关。
setQQSwitchState({required bool qqSwitchState}) → Future<void>
设置 QQ 推送开关。
setDingTalkSwitchState({required bool dingTalkSwitchState}) → Future<void>
设置钉钉推送开关。
setLarkSwitchState({required bool larkSwitchState}) → Future<void>
设置飞书推送开关。
setWeatherState({required bool weatherState}) → Future<void>
设置天气显示开关。
setBrightness({required int brightness}) → Future<void>
设置充电仓屏保亮度。参数:brightness(int,必填)。Android 侧缺失/非法时返回 MISSING_PARAMETERS 错误。
getChargingState() → Future<void>
拉取并同步充电仓设备状态(重置资源状态 → 注册观察者 → 同步设备)。使用 invokeMethodWithDefault,失败时返回 false。
refreshResources() → Future<void>
刷新屏保资源列表。触发型调用,失败默认返回 false。
changeChargingCaseIndex({required int iconState, required int index}) → Future<bool>
切换当前屏保资源。参数:iconState(int,屏保分组状态)、index(int,目标资源索引)。参数缺失时 Android 侧返回 MISSING_PARAMETERS。
deleteResources({required List<int> selectedIndices}) → Future<bool>
批量删除屏保资源。参数:selectedIndices(List<int>,必填,内部自动去重)。无有效文件时返回 NO_VALID_FILES。
convertUploadFile({required int funcIndex, required int clickIndex, required bool isChecked, required String? backgroundImageBase64, required String lockIconBase64}) → Future<bool>
图片转换并上传为屏保。参数:funcIndex(int,屏保功能索引)、clickIndex(int,资源位)、isChecked(bool,是否显示锁定图标)、backgroundImageBase64(String?,背景图 Base64)、lockIconBase64(String,锁定图标 Base64)。任一参数缺失返回 MISSING_PARAMETERS。
cancelUpload() → Future<bool>
取消进行中的屏保上传。失败默认返回 false。
Android 侧:ChargingCaseManager
handleChargingCase(call: MethodCall, result: MethodChannel.Result?, eventChannelHandler: EventChannelHandler?)
统一分发入口。参数:call(MethodCall,来自 Flutter)、result(结果回调,可空)、eventChannelHandler(事件通道,可空)。未识别方法调用 result.notImplemented()。
getChargingState()
初始化充电仓状态:resourceManager.resetResourceState() → setupViewModelObservers() → chargingCaseSettingViewModel.syncDeviceState()。
onCancel()
清理资源:移除 chargingCaseInfoMLD 观察者、清空事件通道引用、重置资源状态、释放并取消上传。
Flutter 侧:事件流
BleEventStream.messagePushStateStream → Stream<MessagePushStateModel>
静态消息推送状态流,代理自 BleChargingCaseProcessor.messagePushStateStream,用于订阅彩屏仓推送开关状态变化。
失败模式、边界情况与并发
失败模式
| 失败场景 | 表现 | 处理 |
|---|---|---|
| Flutter 参数缺失/类型错误 | result.error("MISSING_PARAMETERS", ...) | 调用方捕获 MethodChannel 异常;不进入子模块 |
call.arguments 非 Map | result.error("INVALID_ARGUMENTS", ...) | 参数解析短路返回 |
| 删除时索引对应的文件全部无效 | result.error("NO_VALID_FILES", ...) | 不做任何删除,避免误删 |
| 上传过程中断连 | UploadManager.cancelCurrentUpload() | onCancel() 中取消并释放,防止文件句柄泄漏 |
| 设备功能结果失败 | functionResultMLD 回调 isSuccess() == false | 观察者直接跳过,不刷新 UI 状态 |
| 未知方法名 | result.notImplemented() | Flutter 侧可区分「方法不存在」 |
边界情况
- 重复调用
getChargingState():setupViewModelObservers每次调用都会observeForever;如果多次注册同一观察者对象,LiveData 会去重,但mCaseInfoObserver是单例字段,重复注册安全; deleteResources去重:Flutter 侧toSet().toList()保证同一索引不会重复删除;backgroundImageBase64可空:上传转换时背景图允许为空,仅上传锁定图标场景成立;- 亮度参数边界:
brightness为 int 但代码未在 Manager 层做范围校验(0~100 等),实际范围约束依赖 RCSP SDK 或设备固件,插件层只保证「存在且为整数」。
并发与一致性
- 单例状态中心:
ChargingCaseSettingViewModel单例 + 主线程LiveData保证了ChargingCaseInfo快照的读写一致性,子模块之间的状态竞争被收敛到单一数据源; - 上传与 UI 状态分离:
UploadManager持有自己的上传状态,ResourceManager维护资源列表;onCancel中先release()再cancelCurrentUpload(),避免取消时使用已释放的资源; - 观察者生命周期:
observeForever不随 UI 销毁,必须在onCancel()显式removeObserver,否则会造成 Activity/Plugin 泄漏; - 命令/事件通道分离:MethodChannel 的命令结果与 EventChannel 的设备事件互不阻塞,设备高频事件(如屏保进度)不会挤占命令返回通道。
性能与运维注意事项
- 图片上传为高成本路径:
convertUploadFile传 Base64 字符串过 MethodChannel(Java/Kotlin 侧String参数),大图会显著增加内存与通道拷贝开销,建议在 Flutter 侧先压缩/缩放再上传;原生侧应避免在主线程执行图片转换; - Base64 过通道:背景图 Base64 通过
getStringArgument读取,超大字符串在 JNI/通道边界有拷贝成本,上传期间应避免同时进行其他大对象传递; - 资源列表内存缓存:
ResourceManager缓存屏保资源列表与选中状态,getChargingState()每次都重置,页面退出(onCancel)也重置,保证内存不随多次进入累加; - 消息推送流订阅:
messagePushStateStream是冷流还是广播流取决于BleChargingCaseProcessor内部实现,订阅方应在页面销毁时取消订阅,避免重复消费事件。
扩展点
- 新增充电仓/彩屏仓能力:按「常量 → Flutter 方法 → Manager 分发 → 子模块」四步扩展——在
BleMethodConstants/MethodChannelConstants增加方法常量,BleChargingCaseManager增加静态方法,ChargingCaseManager.handleChargingCase的when增加分支并委托给对应子模块; - 新增设备功能码:在
ChargingCaseInfo.FUNC_*家族增加常量,并在functionResultMLD观察者与mCaseInfoObserver的when中增加分发逻辑; - 子模块复用:
BrightnessManager/ResourceManager/UploadManager通过构造参数注入EventChannelHandler与ChargingCaseSettingViewModel,便于替换为自定义实现或复用给其他设备类型; - 消息推送应用扩展:新增推送应用(如微博)时,在
BleChargingCaseManager增加对应开关方法、在 MethodChannel 常量组增加参数名即可,Android 侧通过 RCSP 协议对应字段下发。
Related Links
- BleChargingCaseManager 发送接口 — Flutter 侧全部充电仓/彩屏仓方法
- BleEventStream 接收接口 — 消息推送状态流等事件入口
- ChargingCaseManager.kt — Android 侧核心管理器
- ChargingCaseHandler.kt — 参数解析扩展
- ChargingCaseInfoChange.java — 信息变更模型
- ChargingCaseSettingViewModel.kt — 状态中心 ViewModel
- ChargingBinUtil.java — 充电仓工具类