适合谁看

  • 想理解鸿蒙事件型平台能力怎么在 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_SHOWNREQUEST_OPTIONS 等事件

也就是说,这不是“调用后等结果”能讲清的能力。
它天然就是:

  • 调用型入口

  • 事件型结果

二、为什么 Flutter 侧要有 initialize()

IntentNavigationChannel 类似,这个 channel 也不是靠某次页面调用才完整成立的。
它首先需要在 Flutter 侧完成初始化:

  • 防止重复初始化

  • 注册 setMethodCallHandler

这一步的本质是:

  • 先把事件接收器挂起来

因为原生层后面会主动推事件,如果 Flutter 侧没有先准备好 handler,这些状态就接不住。

这也是鸿蒙事件型能力和普通调用型能力的一个根本差别:

  • 普通调用型能力可以“用到时再调”

  • 事件型能力往往要“先把接收器挂好”

三、为什么不能把事件字符串直接扔给页面

这也是这篇最关键的地方。

当前 HarmonyOS 原生侧会推回来类似这些事件:

  • HIDE

  • PASS

  • DEACTIVATE

  • MASK_SHOWN

  • REQUEST_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.dart

  • app/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 边界层负责翻译事件

  • 页面层负责消费最终状态

这正是事件型平台能力最需要的边界结构。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐