适合谁看

  • 正在做鸿蒙系统直达入口的人

  • 想把原生参数转成 Flutter 路由的人

  • 不想让原生层直接耦合 Flutter path 的开发者

问题背景

鸿蒙系统入口最容易写坏的地方之一,就是直接把原生参数和 Flutter 路由绑死。例如:

  • 原生层直接知道 /dish/:id

  • Flutter 路由一改,原生配置也跟着改

这会让入口链越来越难维护。

错误做法 vs 正确做法:

做法

问题

结果

原生层直接写 /search

和 Flutter 路由耦合

改路由要改原生配置

原生层用 pageId: "search"

语义和实现解耦

改路由只改 Flutter 映射表

项目中的真实场景

食界探味当前的参数链涉及 4 层:

文件

职责

配置层

insight_intent.json

声明允许的 pageId 枚举

执行层

InsightIntentExecutorImpl.ets

校验 pageId 和 dishId

插件层

IntentNavigationPlugin.ets

转发或暂存 pending

Flutter 层

intent_navigation_channel.dart

pageId → Flutter 路由映射

核心实现

一、配置层——在 insight_intent.json 把入口语义收窄

系统入口不是从代码开始的,而是从入口配置开始的。当前项目在 insight_intent.json 里先定义了允许进入应用的核心参数:

{
  "insightIntents": [{
    "intentName": "JumpFunctionPage",
    "inputParams": [{
      "properties": {
        "pageId": {
          "type": "string",
          "enum": [
            { "value": "search", "displayName": "搜索美食" },
            { "value": "wish_box", "displayName": "心愿单" },
            { "value": "ingredients", "displayName": "食材探索" },
            { "value": "explore", "displayName": "探索美食" },
            { "value": "dish_detail", "displayName": "查看菜品详情" }
          ]
        }
      }
    }]
  }]
}

这一步保证了:

  • 原生层和 Flutter 层讨论的是同一组稳定入口语义

  • 不会变成"系统传什么,应用就随便接什么"

  • pageId 是业务语义,不是 Flutter 路由路径

二、执行层——在 InsightIntentExecutorImpl 做第一轮参数校验

到了 InsightIntentExecutorImpl.ets,当前项目做了更明确的代码检查:

const VALID_PAGE_IDS: string[] = [
  'search', 'wish_box', 'ingredients', 'explore', 'dish_detail'
];

private jumpFunctionPage(param: Record<string, Object>): Promise<insightIntent.ExecuteResult> {
  return new Promise((resolve) => {
    // 1. 类型校验
    if (typeof param?.pageId !== 'string') {
      resolve(makeResult(-1, 'pageId type error'));
      return;
    }

    const pageId = param.pageId as string;
    const dishId = typeof param?.dishId === 'string' ? param.dishId : undefined;

    // 2. 白名单校验
    if (!VALID_PAGE_IDS.includes(pageId)) {
      resolve(makeResult(-1, `unknown pageId: ${pageId}`));
      return;
    }

    // 3. 详情页参数校验
    if (pageId === 'dish_detail' && (!dishId || dishId.length === 0)) {
      resolve(makeResult(-1, 'dishId type error'));
      return;
    }

    // 4. 转发到插件层
    const plugin = IntentNavigationPlugin.getInstance();
    if (plugin !== null) {
      plugin.navigateToPage(pageId, dishId);
    } else {
      IntentNavigationPlugin.setPendingNavigation(pageId, dishId);
    }

    resolve(makeResult(0, 'success'));
  });
}

执行层的 4 道校验:

校验

检查内容

失败处理

类型校验

pageId 必须是 string

返回 -1

白名单校验

pageId 必须在 VALID_PAGE_IDS

返回 -1

详情页校验

dish_detail 必须有 dishId

返回 -1

参数完整性

dishId 如果存在必须非空

返回 -1

这体现了"系统入口治理"的第一道门:

  • 配置层定义允许的语义

  • 执行器层做运行时合法性检查

  • 这样进入插件层的,就不再是完全不受控的外部输入

三、插件层——只负责转发和暂存,不负责解释页面结构

到了 IntentNavigationPlugin.ets,当前项目没有让插件层去理解:

  • /search

  • /dish/:id

  • 是否走 go

  • 是否走 push

插件层只做两件事:

navigateToPage(pageId: string, dishId?: string): void {
  if (this.channel) {
    // 1. Flutter 已 ready,直接推送
    const args = new Map<string, Object>();
    args.set('pageId', pageId);
    if (dishId) args.set('dishId', dishId);
    this.channel.invokeMethod('onIntentNavigation', args);
  } else {
    // 2. Flutter 未 ready,先存到 pendingNavigation
    IntentNavigationPlugin.pendingNavigation = { pageId, dishId };
  }
}

插件层不做的事情:

不做

为什么

不解释路由路径

路由是 Flutter 的事

不判断 go/push

跳转策略是 Flutter 的事

不处理页面层级

页面结构是 Flutter 的事

这个设计避免了最常见的耦合:原生层直接知道 Flutter 路由结构。

四、Flutter 边界层——把稳定语义翻译成真正路由

真正的路由翻译发生在 intent_navigation_channel.dart

static const _pageIdToRoute = <String, String>{
  'search': '/search',
  'ai_assistant': '/ai-assistant',
  'wish_box': '/wish-box',
  'ingredients': '/ingredients',
  'explore': '/explore',
};

static const _shellRoutes = <String>{
  '/explore', '/inspiration', '/collection', '/profile',
};

static void _navigate(_NavigationPayload payload) {
  // 特殊处理:详情页
  if (payload.pageId == 'dish_detail') {
    final dishId = payload.dishId;
    if (dishId == null || dishId.isEmpty) return;
    _router?.go('/explore');
    scheduleMicrotask(() {
      _router?.push('/dish/$dishId');
    });
    return;
  }

  // 通用处理:普通页面
  final route = _pageIdToRoute[payload.pageId];
  if (route == null) return;

  if (_shellRoutes.contains(route)) {
    _router?.go(route);
  } else {
    _router?.go('/explore');
    scheduleMicrotask(() {
      _router?.push(route);
    });
  }
}

Flutter 层的 3 个设计要点:

要点

说明

映射表 _pageIdToRoute

pageId → Flutter 路由的唯一真相来源

dish_detail 单独处理

详情页需要 go+push,不能简单 go

Shell 路由判断

Tab 页面用 go,非 Tab 页面用 go+push

Flutter 层不仅负责"把 pageId 变成 route",还负责:

  • 区分壳路由页和独立详情页

  • 区分 gopush

  • 区分冷启动和热跳转时更稳的跳法

五、GoRouter 最终承接页面

到了 app.dart,真正定义页面结构的是 GoRouter:

GoRoute(
  path: '/search',
  builder: (context, state) => const SearchScreen(),
),
GoRoute(
  path: '/dish/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    return DishDetailScreen(dishId: id);
  },
),

完整链路:

insight_intent.json(配置层)
  → 声明允许的 pageId 枚举
  │
  ▼
InsightIntentExecutorImpl(执行层)
  → 校验 pageId 类型、白名单、dishId
  │
  ▼
IntentNavigationPlugin(插件层)
  → 转发或暂存 pending
  │
  ▼
intent_navigation_channel.dart(Flutter 边界层)
  → pageId → Flutter 路由映射
  │
  ▼
GoRouter(路由层)
  → 最终承接页面

六、参数安全传递的完整流程

用户在鸿蒙搜索点击"查看牛肉咖喱详情"
  │
  ▼
鸿蒙系统:pageId="dish_detail", dishId="beef-curry-001"
  │
  ▼
InsightIntentExecutorImpl:
  ├─ 类型校验 → pageId 是 string ✓
  ├─ 白名单校验 → "dish_detail" 在 VALID_PAGE_IDS 中 ✓
  ├─ 详情页校验 → dishId="beef-curry-001" 非空 ✓
  ├─ 转发到 IntentNavigationPlugin
  │
  ▼
IntentNavigationPlugin:
  ├─ channel 可用 → invokeMethod('onIntentNavigation', {pageId: "dish_detail", dishId: "beef-curry-001"})
  │
  ▼
intent_navigation_channel.dart:
  ├─ _parseArguments() → pageId="dish_detail", dishId="beef-curry-001"
  ├─ _navigate() → pageId == "dish_detail"
  ├─ _router?.go('/explore')
  ├─ scheduleMicrotask(() { _router?.push('/dish/beef-curry-001'); })
  │
  ▼
GoRouter:
  ├─ 匹配 '/dish/:id' → id="beef-curry-001"
  ├─ DishDetailScreen(dishId: "beef-curry-001")
  │
  ▼
用户看到牛肉咖喱详情页

关键代码位置

文件

作用

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

配置层

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

执行层

app/ohos/entry/src/main/ets/plugins/IntentNavigationPlugin.ets

插件层

app/lib/core/platform/intent_navigation_channel.dart

Flutter 边界层

app/lib/app.dart

GoRouter 路由层

常见坑

  • 原生层直接写 Flutter route path — 改路由要改原生配置

  • 参数既不校验,也没有白名单 — 非法参数直接到 Flutter

  • dishId 这类附加参数没有单独检查 — dish_detail 没有 dishId 会崩溃

  • Flutter 层收到参数后又重新写一套不同语义 — pageId 应该是稳定的业务语义

  • 插件层顺手做了过多路由判断 — 插件只转发,不做路由决策

  • 壳路由页和详情页没有分开处理 — 返回栈会很怪

  • VALID_PAGE_IDS 和 insight_intent.json 的 enum 不一致 — 两边必须同步

  • Flutter 路由映射表没有覆盖所有 pageId — 未知 pageId 会被忽略

可复用模板

参数安全传递模板

insight_intent.json 定义稳定 pageId(枚举)
  → InsightIntentExecutor 校验 pageId / dishId(白名单 + 类型)
  → IntentNavigationPlugin 转发或暂存(不理解路由)
  → intent_navigation_channel.dart 做路由翻译(映射表)
  → GoRouter 最终承接页面(页面结构唯一来源)

鸿蒙侧参数校验模板

const VALID_PAGE_IDS: string[] = ['home', 'profile', 'settings'];

private jumpToPage(param: Record<string, Object>): Promise<insightIntent.ExecuteResult> {
  return new Promise((resolve) => {
    if (typeof param?.pageId !== 'string') {
      resolve(makeResult(-1, 'pageId type error'));
      return;
    }

    const pageId = param.pageId as string;
    if (!VALID_PAGE_IDS.includes(pageId)) {
      resolve(makeResult(-1, `unknown pageId: ${pageId}`));
      return;
    }

    // 转发到插件层
    const plugin = IntentPlugin.getInstance();
    if (plugin) plugin.navigateToPage(pageId);
    else IntentPlugin.setPendingNavigation(pageId);

    resolve(makeResult(0, 'success'));
  });
}

Flutter 侧路由映射模板

static const _pageIdToRoute = <String, String>{
  'home': '/home',
  'profile': '/profile',
  'settings': '/settings',
};

static void _navigate(NavigationPayload payload) {
  final route = _pageIdToRoute[payload.pageId];
  if (route == null) return;

  if (_shellRoutes.contains(route)) {
    _router?.go(route);
  } else {
    _router?.go('/home');
    scheduleMicrotask(() => _router?.push(route));
  }
}

参数安全检查清单

配置层:
  □ pageId 是否有 enum 限制?
  □ displayName 和 keywords 是否填写?

执行层:
  □ pageId 类型是否校验?
  □ pageId 白名单是否校验?
  □ dishId 等附加参数是否校验?
  □ VALID_PAGE_IDS 和 enum 是否一致?

插件层:
  □ 是否只转发,不做路由决策?
  □ pending 机制是否正确?

Flutter 层:
  □ _pageIdToRoute 是否覆盖所有 pageId?
  □ dish_detail 等特殊页面是否单独处理?
  □ go 和 push 是否正确区分?

本篇总结

鸿蒙系统入口参数应该先在原生层被收成稳定语义,再进入 Flutter 路由层。pageIddishId 这种设计的价值,就在于把入口语义和页面实现解耦:

  1. 配置层insight_intent.json 定义允许的 pageId 枚举

  2. 执行层InsightIntentExecutorImpl 做白名单和参数校验

  3. 插件层IntentNavigationPlugin 只转发,不做路由决策

  4. Flutter 边界层intent_navigation_channel.dart 做 pageId → 路由映射

  5. GoRouter — 最终承接页面

只要这条链分层清楚,后面扩展鸿蒙系统直达入口会轻松很多。

Logo

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

更多推荐