适合谁看

  • 正在设计系统入口参数的人

  • 想让原生和 Flutter 解耦的人

  • 想避免后期路由调整牵动原生配置的人

问题背景

系统入口一旦上线,就很难频繁改。而 Flutter 路由在项目迭代中却很可能变化:

  • 路由名会改(/search/search-page

  • 页面层级会调整(从 push 变成 go)

  • 详情页打开方式会变(go+push → 直接 push)

  • 新增页面时原生配置要跟着加

所以两边不能直接硬绑在一起。

项目中的真实场景

食界探味当前的 pageId 映射设计:

文件

角色

insight_intent.json

定义 pageId 枚举(稳定语义层)

InsightIntentExecutorImpl.ets

校验 pageId 合法性

IntentNavigationPlugin.ets

桥接传递 pageId

intent_navigation_channel.dart

pageId → Flutter 路由(实现层)

核心设计:原生侧只认识 pageId,不认识 Flutter 路由;Flutter 侧把 pageId 翻译成路由。

核心实现

一、pageId 代表业务语义,不代表路由实现

当前定义的 5 个 pageId:

pageId

业务语义

对应的 Flutter 路由

search

搜索美食

/search

ai_assistant

AI 助手

/ai-assistant

wish_box

心愿单

/wish-box

ingredients

食材探索

/ingredients

explore

探索美食

/explore

dish_detail

菜品详情

/explore → push /dish/$dishId

注意 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 功能关闭时的 ai_assistant

降级到 /explore

dish_detail 需要参数

校验 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',
};

映射表的设计原则:

  1. pageId 是稳定的业务语义 — 不会因为路由变化而改名

  2. 映射表只在 Flutter 侧 — 原生侧完全不感知

  3. 复杂路由不在映射表中处理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 层时:

  1. insight_intent.json 加一个 enum 值

  2. 在执行器的 VALID_PAGE_IDS 加一个值

  3. 在 Flutter 映射表加一行

三步完成,每层各改一处,互不影响。

没有 pageId 层时:

  1. insight_intent.json(加路由路径)

  2. 改执行器(加路由校验)

  3. 改插件(加路由传递)

  4. 改 Flutter(加路由定义)

每层都要理解其他层的细节,耦合度高。

关键代码位置

文件

作用

app/ohos/entry/src/main/resources/base/profile/insight_intent.json

pageId 枚举定义

app/ohos/entry/src/main/ets/entryability/InsightIntentExecutorImpl.ets

pageId 白名单校验

app/lib/core/platform/intent_navigation_channel.dart

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 侧负责最终实现。

这层设计看起来简单,但对长期维护非常关键:

  1. 路由变化不影响原生 — 改映射表一行即可

  2. 新增页面扩展简单 — 每层各改一处

  3. 复杂路由逻辑内聚 — 都在 Flutter 侧处理

  4. 原生侧保持轻量 — 只校验 pageId,不理解路由

在鸿蒙项目中,pageId 层是原生能力和 Flutter 业务之间的"翻译带"。没有它,两边会直接耦合;有了它,两边可以各自独立演进。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐