鸿蒙防窥保护 的 Flutter 侧封装思路
适合谁看
-
想理解鸿蒙事件型平台能力怎么在 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 原生侧会推回来类似这些事件:
-
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 边界层负责翻译事件
-
页面层负责消费最终状态
这正是事件型平台能力最需要的边界结构。
更多推荐




所有评论(0)