适合谁看
想理解鸿蒙事件型平台能力怎么在 Flutter 侧封装的人
正在做状态回推类原生能力的人
想保持页面层干净的人
问题背景
鸿蒙防窥保护和语音识别、TTS 不一样。
它并不是一个简单的:
调一次
拿结果
结束
而是同时包含了两种东西:
命令:开启或关闭保护
状态:系统可能持续推送当前可见性变化
这类 HarmonyOS 能力如果按普通调用型 channel 去设计,最容易出现两个问题:
页面层直接拿到一堆原生事件字符串
页面层自己去拼状态机
这两件事都会让边界层迅速失守。
项目中的真实场景
当前项目的 Flutter 侧封装在:
app/lib/core/platform/anti_peep_protection_channel.dart
它一边暴露:
activateCollectionProtection()deactivateCollectionProtection()
一边又注册:
setMethodCallHandler
同时还维护了:
ValueNotifier<AntiPeepVisibilityState>
对应的 HarmonyOS 原生插件在:
app/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.ets
这组结构非常适合拿来讲事件型 channel 的封装思路。
核心实现
先说结论:
AntiPeepProtectionChannel的价值,不是把鸿蒙原生事件搬到 Flutter,而是把“命令 + 事件”收成“命令 + 页面可消费状态”。
一、为什么它不能只做普通静态方法调用
如果只看方法名,这个 channel 看上去很像普通调用型能力:
开启保护
关闭保护
但只要回到 HarmonyOS 原生插件AntiPeepProtectionPlugin.ets,你就会发现事情远不止如此。
原生层还会:
订阅
dlpAntiPeep状态变化在状态变化时主动推回
HIDE/PASS/DEACTIVATE在需要时再推
MASK_SHOWN、REQUEST_OPTIONS等事件
也就是说,这不是“调用后等结果”能讲清的能力。
它天然就是:
调用型入口
事件型结果
二、为什么 Flutter 侧要有initialize()
和IntentNavigationChannel类似,这个 channel 也不是靠某次页面调用才完整成立的。
它首先需要在 Flutter 侧完成初始化:
防止重复初始化
注册
setMethodCallHandler
这一步的本质是:
先把事件接收器挂起来
因为原生层后面会主动推事件,如果 Flutter 侧没有先准备好 handler,这些状态就接不住。
这也是鸿蒙事件型能力和普通调用型能力的一个根本差别:
普通调用型能力可以“用到时再调”
事件型能力往往要“先把接收器挂好”
三、为什么不能把事件字符串直接扔给页面
这也是这篇最关键的地方。
当前 HarmonyOS 原生侧会推回来类似这些事件:
HIDEPASSDEACTIVATEMASK_SHOWNREQUEST_OPTIONS
如果 Flutter 页面直接消费这套字符串,马上就会遇到几个问题:
页面要知道鸿蒙原生事件协议
页面要自己判断哪些事件会影响可见性
页面要自己拼出业务状态
这样一来,边界层就等于不存在了。
而当前项目现在的做法更稳:
原生事件先在
AntiPeepProtectionChannel里被解析再被翻译成
AntiPeepVisibilityState.visible / hidden
这一步的意义非常大。
因为它把:
HarmonyOS 原生事件模型 →
页面状态模型
这条转换链稳稳收在了边界层。
四、为什么这里用ValueNotifier很合适
Flutter 侧当前暴露状态的方式是:
ValueNotifier<AntiPeepVisibilityState>
这是一种很克制但很有效的设计。
因为页面层真正需要的,不是完整事件流,而是:
当前内容是否可见
所以用ValueNotifier的好处在于:
足够轻
页面容易监听
不会把边界层过早设计成复杂状态管理系统
它并不是唯一方案,但对当前这个项目来说,是一个非常顺手的中间层收口方式。
五、为什么_invoke()要自己兜底异常
在这层代码里,调用原生方法最终会进入_invoke(String method),其中已经做了:
MissingPluginException兜底普通异常日志记录
这说明AntiPeepProtectionChannel不只是状态翻译器,它还是:
鸿蒙平台可用性边界
异常兜底边界
页面层最终不需要关心:
当前平台有没有这个插件
原生调用底层抛了什么异常
它只需要关心:
当前保护有没有被激活
当前页面内容该不该隐藏
六、为什么说它比普通MethodChannel更像“命令 + 状态总线”
普通调用型 channel 的主链路通常是:
Flutter 发方法
原生回结果
但AntiPeepProtectionChannel更像:
Flutter 发命令
原生持续推状态
Flutter 把状态收成页面可消费值
所以它和普通 channel 最大的差别不是“多了几个事件名”,而是:
它的主目标已经不只是完成调用
而是维持 Flutter 页面对鸿蒙系统状态的正确感知
这也是为什么它特别适合拿来做事件型能力的样板。
七、如果把这条链路从 Flutter 页面走到鸿蒙原生,顺序是怎样的
把当前代码对起来看,完整链路大致是这样:
Flutter 页面 -> AntiPeepProtectionChannel.initialize() -> setMethodCallHandler 挂好鸿蒙事件接收器 -> 页面调用 activateCollectionProtection() -> HarmonyOS 原生插件订阅 dlpAntiPeep 状态 -> 系统变化时原生推回 HIDE / PASS / DEACTIVATE 等事件 -> Flutter 边界层把事件翻译成 visible / hidden -> 页面监听 ValueNotifier 更新 UI只要这条链路先建立清楚,后面你无论是改 Flutter 边界层,还是改 HarmonyOS 原生插件,都会更知道自己在改哪一层。
八、什么时候说明这层 Flutter 封装已经该重构了
如果后面开始出现下面这些信号,就说明这层边界可能需要升级:
页面开始自己理解越来越多 HarmonyOS 原生事件名
visible / hidden已经不够表达真实业务状态不同页面开始各自做一套防窥状态判断
边界层已经承担了越来越多页面特定逻辑
这时候需要重构的不是页面,而是边界层本身。
也就是说,边界层应该继续演化,但依然不该把鸿蒙原生事件协议直接倾倒给页面层。
关键代码位置
app/lib/core/platform/anti_peep_protection_channel.dartapp/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.ets
鸿蒙侧实现
从 HarmonyOS 原生侧看,插件负责的是:
系统开关检测
状态订阅
蒙层控制
事件推回
这说明真正复杂的状态源头始终在原生层。
Flutter 侧实现
从 Flutter 侧看,边界层负责的是:
挂接事件 handler
把原生事件翻译成 Flutter 状态
对页面暴露稳定的可观察值
把平台异常挡在页面层外面
这就是它和普通调用型 channel 最大的分工差异。
常见坑
页面直接消费 HarmonyOS 原生事件字符串
事件型能力仍按同步调用思路设计
页面层一边发命令,一边自己拼原生状态机
只把 channel 当成方法转发器,没有把它当作状态边界层
没先初始化 handler 就开始依赖原生事件
可复用模板
enum FeatureState { active, inactive } static final ValueNotifier<FeatureState> state = ValueNotifier(FeatureState.inactive); _channel.setMethodCallHandler((call) async { if (call.method == 'onFeatureEvent') { // parse native event and update state } });事件型 channel 设计思路 1. Flutter 先挂 handler 2. 鸿蒙原生推事件 3. 边界层翻译状态 4. 页面只消费状态本篇总结
AntiPeepProtectionChannel的 Flutter 侧封装思路,重点不是“多接几个事件”,而是把鸿蒙原生状态流收成页面能稳定消费的状态模型。
当前这层设计之所以有价值,是因为它清楚地区分了:
鸿蒙原生层负责产生系统事件
Flutter 边界层负责翻译事件
页面层负责消费最终状态
这正是事件型平台能力最需要的边界结构。