鸿蒙导航意图 的 Flutter 侧封装思路
适合谁看
想理解鸿蒙 Intent 导航 Flutter 侧特殊性的开发者
正在做系统入口到页面路由衔接的人
想把外部入口和应用路由分开的开发者
问题背景
很多人第一次做原生通信时,默认模型都是:
页面点一个按钮
Flutter 调原生
原生回一个结果
这个模型对语音识别、TTS 这类“页面主动发起”的能力很适用。
但鸿蒙 Intent 导航不是这样。
它最大的特殊性在于:
导航意图可能先从系统外部到达
Flutter 页面甚至还没 ready
页面层不是发起者,而更像承接者
这也是为什么IntentNavigationChannel的 Flutter 侧封装思路,和语音识别、TTS 这类 channel 完全不一样。
项目中的真实场景
当前这条 HarmonyOS 系统入口链路很典型:
app/ohos/entry/src/main/ets/entryability/InsightIntentExecutorImpl.etsapp/ohos/entry/src/main/ets/plugins/IntentNavigationPlugin.etsapp/lib/core/platform/intent_navigation_channel.dart
其中 Flutter 侧的intent_navigation_channel.dart做的不是“普通平台调用边界”,而是:
接住系统整理好的导航 payload
解析
pageId映射到 GoRouter 路由
在 Flutter 侧继续完成页面跳转
所以这篇的核心不是“channel 怎么发请求”,而是“Flutter 怎么把鸿蒙系统入口翻译成应用内导航”。
核心实现
先说结论:
IntentNavigationChannel最重要的职责不是向原生发命令,而是把鸿蒙系统入口结果收成 Flutter 应用能稳定消费的导航语义。
一、它和语音、TTS 的根本区别在哪里
看speech_recognition_channel.dart和text_to_speech_channel.dart,很容易发现它们更像:
我主动发起一件事
原生帮我完成
但IntentNavigationChannel这边更像:
鸿蒙系统先把一条意图送进来
原生先把它整理好
Flutter 再接住并转成本应用路由
也就是说,它的起点不是页面按钮,而是系统入口。
这一点决定了它的 Flutter 封装思路必须不同。
二、为什么这层必须负责pageId到路由的映射
在intent_navigation_channel.dart里,最关键的一段结构是:
_pageIdToRoute
这里把鸿蒙系统侧的意图标识,例如:
searchai_assistantwish_boxingredientsexplore
映射成应用内部真正的 Flutter 路由。
这一步为什么一定要留在 Flutter 侧做,而不是原生侧直接决定最终页面?
因为从职责上看:
原生侧更接近“系统怎么把用户送进来”
Flutter 侧更接近“应用内部到底怎么走路由”
如果把这层路由决定也塞进原生侧,后面一旦 Flutter 路由结构变化,ArkTS 插件也要跟着一起变。
这会让鸿蒙入口层和页面层耦合得很紧。
三、为什么init(router)是这个 channel 的核心入口
这类 channel 和普通调用型 channel 最大的不同之一,是它不是靠页面某一次主动调用才开始工作。
在当前项目里,IntentNavigationChannel的主入口是:
init(GoRouter router)
这说明它真正依赖的是:
Flutter 路由系统已经可用
之后才能把鸿蒙系统入口翻译成页面导航
这和语音识别、TTS 那种“静态方法直接调一次”差别非常大。
它更像是在 Flutter 应用里挂一层“鸿蒙外部入口适配器”。
四、为什么要消费pending navigation
这是这条链路最关键、也最容易被忽略的设计点。
在 Flutter 侧初始化时,IntentNavigationChannel会主动调用:
_consumePending()
它背后的原因非常现实:
鸿蒙系统入口可能先到了
但 Flutter 路由和页面还没 ready
如果没有这一步,最容易发生的情况就是:
系统把意图送进来了
结果 Flutter 这边还没准备好承接
这次导航就直接丢了
所以pending navigation不是“多此一举的缓存”,而是:
鸿蒙系统入口时序和 Flutter 应用初始化时序之间的桥
五、为什么它必须注册setMethodCallHandler
这也是它和普通调用型 channel 的又一个关键区别。
在init(router)里,Flutter 侧注册了:
_channel.setMethodCallHandler(...)
它要接的不是“某次调用的返回值”,而是:
onIntentNavigation
也就是说,HarmonyOS 原生层会主动把导航事件推给 Flutter。
所以这类 channel 的运行方式更接近:
Flutter 先把接收器挂好
原生一旦有入口事件,就把它推回来
这也是为什么IntentNavigationChannel比普通调用型 channel 更像“鸿蒙入口事件适配层”。
六、为什么解析 payload 也必须留在边界层
在当前实现里,_parseArguments(Object? arguments)负责:
校验参数是不是
Map拿出
pageId拿出
dishId收成
_NavigationPayload
这看起来像简单的数据处理,但位置其实非常关键。
因为页面层不该直接去理解:
鸿蒙原生传来的参数结构
dishId有没有带pageId空不空
这些都更应该由边界层收口。
只有这样,页面层才只需要面对“我要跳到哪一个 Flutter 路由”这件事。
七、为什么这里还要负责一些应用内导航策略
看_navigate(_NavigationPayload payload)会发现,这里除了路由映射,还做了几件很应用内的事情:
ai_assistant在AppConfig.enableAi关闭时要兜底dish_detail需要先回到/explore再push('/dish/$dishId')shell route 和普通 route 的跳法不一样
这说明 Flutter 边界层在这里不只是“收消息”,而是在做一层很重要的转换:
鸿蒙系统入口语义 →
应用内导航策略
而这一层正是原生侧不适合决定、页面层又不应该散着处理的地方。
八、如果把这条链路从鸿蒙系统入口走到 Flutter 页面,顺序是怎样的
把当前代码对起来看,完整链路大致是这样:
HarmonyOS 系统入口 -> InsightIntentExecutorImpl.ets 校验 pageId / dishId -> IntentNavigationPlugin.ets 存 pending 或主动推送事件 -> IntentNavigationChannel.init(router) -> Flutter setMethodCallHandler 接住 onIntentNavigation -> _parseArguments 收成 _NavigationPayload -> _navigate 映射成 GoRouter 路由 -> Flutter 页面完成跳转只要这条链路先建立清楚,后面你再去改pageId映射、路由策略或系统入口逻辑,都会更知道自己在改哪一层。
九、这种 Flutter 封装最适合什么样的鸿蒙能力
当前这种封装特别适合下面这类 HarmonyOS 系统入口能力:
小艺搜索直达
系统推荐入口
桌面卡片触达后的页面跳转
外部意图把用户送进应用某个业务页
它们的共同点是:
入口先从系统来
Flutter 页面不是第一触点
页面层只需要承接整理后的导航语义
所以这类能力如果没有专门的 Flutter 边界层,页面层很容易被入口协议污染。
十、什么时候说明这层边界已经该重构了
如果后面开始出现下面这些信号,就说明这层 Flutter 边界可能需要升级:
_pageIdToRoute越来越大,已经像隐藏路由表页面层开始自己理解
pageId和dishId原生层直接写死越来越多 Flutter 业务页
不同入口类型共用一套 payload,但语义已经明显分叉
这时候需要重构的不是页面,而是入口边界层本身。
也就是说,Flutter 边界层应该继续演化,但依然要守住“系统入口协议别直接漏进页面层”这条线。
关键代码位置
app/lib/core/platform/intent_navigation_channel.dartapp/ohos/entry/src/main/ets/plugins/IntentNavigationPlugin.etsapp/ohos/entry/src/main/ets/entryability/InsightIntentExecutorImpl.ets
鸿蒙侧实现
从 HarmonyOS 原生侧看,Intent 相关代码负责的是:
接住系统入口
校验入口参数
在 Flutter 没 ready 时先缓存待处理导航
在 Flutter 可用时主动把入口事件推回去
也就是说,原生层解决的是“入口从鸿蒙系统到应用边界”的问题。
Flutter 侧实现
从 Flutter 侧看,IntentNavigationChannel解决的是:
入口结果怎么接住
payload 怎么收口
pageId怎么映射成 GoRouter鸿蒙系统入口和应用路由怎么衔接
这也是为什么它的封装重点不是“调用原生”,而是“承接系统入口”。
常见坑
把 Intent 当成普通页面按钮跳转
没处理 Flutter 还没 ready 的时机问题
让原生层直接决定最终 Flutter 业务页
页面层直接解析原生 payload,导致入口协议细节泄漏
把鸿蒙系统入口策略散落在多个页面里处理
可复用模板
static void init(GoRouter router) { _router = router; _channel.setMethodCallHandler((call) async { if (call.method == 'onIntentNavigation') { final payload = _parseArguments(call.arguments); if (payload != null) { _navigate(payload); } } }); _consumePending(); }Intent 类 channel 的边界职责 1. 接事件 2. 解析 payload 3. 映射应用路由 4. 处理初始化时序本篇总结
IntentNavigationChannel的 Flutter 侧封装思路,核心不在“多写几个方法”,而在“把鸿蒙系统入口结果稳稳接住,再翻译成应用内路由”。
当前这层设计之所以稳,是因为它没有把入口协议散到页面层,也没有把最终路由决策推给原生层,而是把这条转换链准确地收在了 Flutter 边界层。
