如何设计鸿蒙 pageId 映射,让原生入口和 Flutter 路由保持长期可维护

适合谁看
-
正在设计系统入口参数的人
-
想让原生和 Flutter 解耦的人
-
想避免后期路由调整牵动原生配置的人
问题背景
系统入口一旦上线,就很难频繁改。而 Flutter 路由在项目迭代中却很可能变化:
-
路由名会改(
/search→/search-page) -
页面层级会调整(从 push 变成 go)
-
详情页打开方式会变(go+push → 直接 push)
-
新增页面时原生配置要跟着加
所以两边不能直接硬绑在一起。
项目中的真实场景
食界探味当前的 pageId 映射设计:
|
文件 |
角色 |
|---|---|
|
|
定义 pageId 枚举(稳定语义层) |
|
|
校验 pageId 合法性 |
|
|
桥接传递 pageId |
|
|
pageId → Flutter 路由(实现层) |
核心设计:原生侧只认识 pageId,不认识 Flutter 路由;Flutter 侧把 pageId 翻译成路由。
核心实现
一、pageId 代表业务语义,不代表路由实现
当前定义的 5 个 pageId:
|
pageId |
业务语义 |
对应的 Flutter 路由 |
|---|---|---|
|
|
搜索美食 |
|
|
|
AI 助手 |
|
|
|
心愿单 |
|
|
|
食材探索 |
|
|
|
探索美食 |
|
|
|
菜品详情 |
|
注意 dish_detail 的路由最复杂——它需要先 go('/explore') 回到主页,再 push('/dish/$dishId') 打开详情。如果原生侧直接写这个路由逻辑,就会非常脆弱。
二、原生侧只校验 pageId
InsightIntentExecutorImpl.ets 的白名单:
const VALID_PAGE_IDS: string[] = [
'search', 'wish_box', 'ingredients', 'explore', 'dish_detail'
];
if (!VALID_PAGE_IDS.includes(pageId)) {
resolve(makeResult(-1, `unknown pageId: ${pageId}`));
return;
}
它不关心 Flutter 内部到底是 go 还是 push,也不关心壳路由和详情路由层级。原生侧只需要知道:
-
合法的 pageId 有哪些
-
哪些 pageId 需要额外参数(如
dish_detail需要dishId)
三、Flutter 侧再做最后一跳
intent_navigation_channel.dart 里才把业务实现细节补上:
static void _navigate(_NavigationPayload payload) {
// 特殊处理:AI 助手需要检查功能开关
if (payload.pageId == 'ai_assistant' && !AppConfig.enableAi) {
_router?.go('/explore');
return;
}
// 特殊处理:详情页需要 go + push
if (payload.pageId == 'dish_detail') {
final dishId = payload.dishId;
if (dishId == null || dishId.isEmpty) return;
_router?.go('/explore');
scheduleMicrotask(() {
_router?.push('/dish/$dishId');
});
return;
}
// 通用处理:pageId → 路由
final route = _pageIdToRoute[payload.pageId];
if (route == null) return;
// Shell 路由用 go,非 Shell 路由用 go+push
if (_shellRoutes.contains(route)) {
_router?.go(route);
} else {
_router?.go('/explore');
scheduleMicrotask(() {
_router?.push(route);
});
}
}
这段代码处理了 3 种复杂情况:
|
情况 |
处理方式 |
|---|---|
|
AI 功能关闭时的 |
降级到 |
|
|
校验 dishId + go+push |
|
非 Shell 路由需要先回主页 |
go('/explore') + push |
如果这些逻辑写在原生侧,每次 Flutter 路由调整都要改原生配置和代码。
四、pageId 映射表的设计
static const _pageIdToRoute = <String, String>{
'search': '/search',
'ai_assistant': '/ai-assistant',
'wish_box': '/wish-box',
'ingredients': '/ingredients',
'explore': '/explore',
};
映射表的设计原则:
-
pageId 是稳定的业务语义 — 不会因为路由变化而改名
-
映射表只在 Flutter 侧 — 原生侧完全不感知
-
复杂路由不在映射表中处理 —
dish_detail单独写逻辑
五、这样改 Flutter 路由时成本更低
假设以后路由从 /search 改成 /search-page:
有 pageId 层时:
-
改 Flutter 映射表:
'search': '/search-page' -
原生配置不改
-
系统侧不改
-
影响范围:1 个文件 1 行
没有 pageId 层时:
-
改 Flutter 路由名
-
改原生
insight_intent.json(如果直接写了路由) -
改执行器校验逻辑
-
改插件层传递逻辑
-
影响范围:3-4 个文件
pageId 层让路由变化的影响范围从 3-4 个文件缩小到 1 个文件。
六、新增页面时的扩展方式
如果以后要新增一个页面入口:
有 pageId 层时:
-
在
insight_intent.json加一个 enum 值 -
在执行器的
VALID_PAGE_IDS加一个值 -
在 Flutter 映射表加一行
三步完成,每层各改一处,互不影响。
没有 pageId 层时:
-
改
insight_intent.json(加路由路径) -
改执行器(加路由校验)
-
改插件(加路由传递)
-
改 Flutter(加路由定义)
每层都要理解其他层的细节,耦合度高。
关键代码位置
|
文件 |
作用 |
|---|---|
|
|
pageId 枚举定义 |
|
|
pageId 白名单校验 |
|
|
pageId → Flutter 路由映射 |
三层解耦架构图
┌─────────────────────────────────────────────────┐
│ 配置层(insight_intent.json) │
│ │
│ 定义 pageId 枚举 + 搜索关键词 │
│ "search" / "ai_assistant" / "dish_detail" ... │
│ │
│ 语义稳定,不随路由变化 │
└──────────────────────┬──────────────────────────┘
│ pageId
▼
┌─────────────────────────────────────────────────┐
│ 执行器层(InsightIntentExecutorImpl) │
│ │
│ 校验 pageId 合法性 + 额外参数 │
│ "dish_detail 必须有 dishId" │
│ │
│ 只校验,不做路由 │
└──────────────────────┬──────────────────────────┘
│ pageId + dishId
▼
┌─────────────────────────────────────────────────┐
│ 插件层(IntentNavigationPlugin) │
│ │
│ channel 可用 → 推给 Flutter │
│ channel 不可用 → 暂存 pending │
│ │
│ 只桥接,不理解路由 │
└──────────────────────┬──────────────────────────┘
│ pageId + dishId
▼
┌─────────────────────────────────────────────────┐
│ Flutter 层(intent_navigation_channel)│
│ │
│ pageId → Flutter 路由映射 │
│ 'search' → '/search' │
│ 'dish_detail' → go('/explore') + push('/dish/$id')│
│ │
│ 复杂路由逻辑都在这一层 │
└─────────────────────────────────────────────────┘
常见坑
-
在原生配置里直接写 Flutter 路由路径 — 路由变化时要改原生配置
-
页面语义和路由路径混成同一层 — pageId 应该是业务语义,不是路由字符串
-
一个 pageId 对应多个完全不同业务语义 — 每个 pageId 应该只对应一种业务
-
路由变化后忘记同步入口逻辑 — 映射表只在 Flutter 侧,影响范围小
-
dish_detail 没有单独处理 — 详情页需要参数,不能走通用映射
-
AI 功能关闭时没有降级 —
ai_assistant入口需要检查开关
可复用模板
pageId 映射表模板
// 简单映射(页面在 Tab Shell 内)
static const _pageIdToRoute = <String, String>{
'home': '/home',
'profile': '/profile',
'settings': '/settings',
};
// Shell 路由判断
static const _shellRoutes = <String>{
'/home', '/profile', '/settings',
};
// 导航逻辑
static void _navigate(NavigationPayload payload) {
final route = _pageIdToRoute[payload.pageId];
if (route == null) return;
if (_shellRoutes.contains(route)) {
_router?.go(route); // Tab 路由直接 go
} else {
_router?.go('/home');
scheduleMicrotask(() => _router?.push(route)); // 非 Tab 路由 go+push
}
}
扩展新页面的步骤模板
1. insight_intent.json
→ 在 pageId enum 中加一个值
→ 加 displayName / keywords / displayDescription
2. InsightIntentExecutorImpl.ets
→ 在 VALID_PAGE_IDS 数组中加一个值
→ 如果需要额外参数,加参数校验
3. intent_navigation_channel.dart
→ 在 _pageIdToRoute 映射表中加一行
→ 如果是复杂路由,写特殊处理逻辑
4. 测试
→ 鸿蒙系统搜索新页面的 displayName
→ 验证直达入口跳转正确
本篇总结
pageId 映射层的价值,在于让系统入口语义和 Flutter 路由实现解耦。原生侧负责稳定语义,Flutter 侧负责最终实现。
这层设计看起来简单,但对长期维护非常关键:
-
路由变化不影响原生 — 改映射表一行即可
-
新增页面扩展简单 — 每层各改一处
-
复杂路由逻辑内聚 — 都在 Flutter 侧处理
-
原生侧保持轻量 — 只校验 pageId,不理解路由
在鸿蒙项目中,pageId 层是原生能力和 Flutter 业务之间的"翻译带"。没有它,两边会直接耦合;有了它,两边可以各自独立演进。
更多推荐



所有评论(0)