适合谁看
正在设计 Flutter 与鸿蒙原生插件接口的人
不确定某个能力该走结果返回还是事件回推的人
想减少后续返工的人
问题背景
很多平台能力在刚开始接入时,看起来都像:
Flutter 发一个方法
原生回一个结果
这会让人很容易产生一种错觉:
所有插件都应该按同步调用式设计。
但真实项目里,这种判断往往会很快出问题。
因为不同鸿蒙能力的生命周期差异非常大:
有的就是一次调用一次结果
有的会经过完整会话
有的则是系统持续推状态
如果不先分清这件事,通信接口设计就很容易返工。
项目中的真实场景
当前项目几组鸿蒙能力正好覆盖了非常典型的几种形态:
语音识别:最终返回识别文本
TTS:返回完成、停止或错误
Intent:原生主动推导航事件
防窥:原生持续推系统状态变化
也就是说,这个项目本身已经足够说明一个事实:
不是所有 HarmonyOS 原生插件都应该按同一种返回模型设计。
核心实现
先说结论:
原生插件是直接返回结果,还是改成事件回调,关键不在技术习惯,而在这项鸿蒙能力到底是一锤子买卖,还是会持续产生新的状态。
一、什么时候适合直接返回结果
如果一项能力具备下面这些特征,它通常更适合直接返回结果:
调用边界很明确
结束点很明确
Flutter 页面真正需要的是最终结果
语音识别为什么大体上仍然适合结果返回
虽然鸿蒙语音识别内部有一整段识别会话,但当前 Flutter 边界层最终只关心:
这次识别出来的最终文本是什么
对应到原生插件SpeechRecognitionPlugin.ets,它会在:
onResult的isLastonCompleteonError
这些结束点上,把结果通过pendingResult.success(...)或error(...)回给 Flutter。
这说明当前项目的设计选择是:
内部过程可以复杂
但只要 Flutter 页面最终只需要一个结束结果,就仍然可以收成结果型能力
TTS 为什么也仍然适合结果返回
鸿蒙 TTS 的原生层同样并不简单:
创建引擎
监听
onStart监听
onComplete监听
onStop监听
onError
但当前 Flutter 页面层的主要需求仍然比较简单:
播报开始了没有
播报结束了没有
停止成功了没有
所以原生插件当前也是把:
完成
停止
错误
收成结果型返回。
这对当前页面需求来说是合理的。
二、什么时候更适合事件回调
如果一项能力具备下面这些特征,它通常更适合事件回调:
系统会主动在未来某个时刻再次推新状态
调用者并不一定是页面主动触发
页面需要持续感知系统变化
Intent 为什么明显更适合事件回推
在当前项目里,Intent 类能力的关键不在“我主动调了原生”,而在:
鸿蒙系统入口可能先到了
Flutter 需要被动承接导航意图
所以原生侧IntentNavigationPlugin.ets会主动调用:
channel.invokeMethod('onIntentNavigation', args)
Flutter 侧再通过:
setMethodCallHandler
接住这次导航事件。
这说明 Intent 的本质不是“返回一个结果”,而是“向 Flutter 交付一条鸿蒙外部入口事件”。
防窥为什么更应该用事件回调
鸿蒙防窥这类能力更典型。
系统状态可能随时变化,原生插件需要持续订阅:
HIDEPASSDEACTIVATE
这类能力如果强行做成一次调用一次返回,几乎一定会出问题。
因为页面真正需要的是:
持续感知当前可见性变化
而不是一次性的“调用成功”。
三、判断标准其实不是“有没有回调”,而是“生命周期是不是持续的”
这是最值得记住的一点。
很多人会误把判断标准写成:
有回调 → 事件型
没回调 → 结果型
这不够准确。
更准确的判断应该是:
这项鸿蒙能力的状态会不会在调用完成后继续演化
如果会,那就更偏事件型。
如果不会,或者页面层只关心最终收口结果,那就仍然可以收成结果型。
四、为什么有些能力看起来复杂,但仍然不必一开始就做事件流
语音识别和 TTS 就是很好的例子。
它们鸿蒙原生内部都不简单,甚至都带多个回调。
但当前项目并没有一开始就把它们全部暴露成复杂事件流,而是先按页面实际需求收成:
一个最终文本
一个完成或停止的结束结果
这说明判断标准不是“原生内部复杂不复杂”,而是:
Flutter 页面真正是不是要消费整个过程
只要页面暂时不需要完整过程,先收成结果型能力通常更稳。
五、什么时候一个结果型能力未来可能要演进成事件型
这也是很实际的问题。
如果以后这个鸿蒙 Flutter 项目需要:
语音识别返回中间识别片段
TTS 暴露更细的播报状态
页面实时感知更多原生过程变化
那原本的结果型能力就可能逐步演进为:
结果型 + 事件型混合模型
但这里的关键词是“以后”。
在当前需求还没有清楚出现之前,过早把所有能力都做成事件流,通常只会让页面和边界层变重。
六、如果按当前项目来做一眼分流,应该怎么判断
如果你现在正在接一个新的 HarmonyOS 能力,可以先快速问自己四个问题:
Flutter 页面最终要的是一个结果,还是一段持续状态?
鸿蒙系统会不会在未来主动再次推送新信息?
页面是不是必须实时感知过程变化?
这个能力是不是从系统入口先发生,而不是页面先发起?
如果前两个问题大多回答“会”,那通常更偏事件型。
如果页面真正只要一个收口结果,那通常还是结果型更稳。
七、为什么这个判断会直接影响 Flutter 边界层质量
因为结果型和事件型能力,对 Flutter 边界层的要求完全不同:
结果型边界层更像一个调用入口
事件型边界层更像一个状态翻译层
如果你把本该事件型的能力硬做成结果型,页面层迟早会自己长出状态机。
如果你把本该结果型的能力过早做成事件流,页面层和边界层又会平白变重。
所以这个判断做得对不对,最后会直接决定:
页面 API 好不好懂
边界层会不会失控
原生插件和 Flutter 层会不会反复返工
关键代码位置
app/ohos/entry/src/main/ets/plugins/SpeechRecognitionPlugin.etsapp/ohos/entry/src/main/ets/plugins/TextToSpeechPlugin.etsapp/ohos/entry/src/main/ets/plugins/IntentNavigationPlugin.etsapp/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.etsapp/lib/core/platform/intent_navigation_channel.dartapp/lib/core/platform/anti_peep_protection_channel.dart
鸿蒙侧实现
从 ArkTS / HarmonyOS 原生侧看,最重要的不是“都支持返回结果或推事件”,而是:
插件要按能力生命周期去设计通信方式
这样才能避免原生层和 Flutter 边界层同时长出奇怪状态机。
Flutter 侧实现
从 Flutter 侧看,结果型和事件型能力的差别非常大:
结果型边界层更像一个调用入口
事件型边界层更像一个状态翻译层
所以越早判断清楚,后面页面层越轻。
常见坑
所有能力都强行做同步返回
所有能力又都过早设计成复杂事件流
原生内部有多个回调,就误以为 Flutter 一定要消费全部回调
页面明明只要最终结果,却被迫理解一整套过程状态
明明是鸿蒙系统持续状态,却还想收成一次调用一次返回
可复用模板
结果型能力 适合: - 单次调用 - 明确结束点 - 页面只要最终结果 事件型能力 适合: - 状态会持续变化 - 系统会主动推新事件 - 页面需要持续感知变化判断关键 不要问“原生有没有回调” 要问“Flutter 页面到底要不要持续消费这个过程”本篇总结
原生插件什么时候直接返回结果,什么时候改为事件回调,本质上不是代码风格问题,而是鸿蒙能力生命周期问题。
当前这几组能力很清楚地说明了:
页面只要最终结果时,结果型返回更稳
鸿蒙系统会持续推新状态时,事件回调才更合适
先把这个判断做对,后面的 Flutter 边界层和原生插件层都会轻很多。