鸿蒙当 Flutter 引擎还没 ready 时,如何缓存待处理导航
适合谁看
-
遇到过"鸿蒙原生收到入口但 Flutter 没反应"的开发者
-
正在做多入口系统接入的人
-
想处理冷启动导航时机问题的人
问题背景
普通页面按钮跳转不会遇到这个问题,因为 Flutter 页面已经在运行。但鸿蒙系统入口、搜索直达、冷启动参数不一样。这时顺序可能变成:
鸿蒙系统入口触发
│
├─ 场景 1:冷启动
│ 鸿蒙先收到入口 → Flutter 引擎还在启动 → 路由系统未初始化
│
└─ 场景 2:热启动(应用已在后台)
鸿蒙先收到入口 → Flutter 可能还没恢复 → channel 可能未就绪
如果原生侧没有缓存机制,这些入口请求就会丢失。
项目中的真实场景
食界探味当前的缓存机制涉及 3 个文件:
|
文件 |
角色 |
|---|---|
|
|
维护 pendingNavigation 缓存 |
|
|
冷启动和热启动时写入缓存 |
|
|
Flutter 初始化后消费缓存 |
核心实现
一、IntentNavigationPlugin——缓存的核心
interface PendingNavigation {
pageId: string;
dishId?: string;
}
export default class IntentNavigationPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
private static instance: IntentNavigationPlugin | null = null;
private static pendingNavigation: PendingNavigation | null = null;
navigateToPage(pageId: string, dishId?: string): void {
if (this.channel) {
// Flutter 已 ready,直接推送
console.info(TAG, `pushing pageId "${pageId}" to Flutter`);
const args = new Map<string, Object>();
args.set('pageId', pageId);
if (dishId) {
args.set('dishId', dishId);
}
this.channel.invokeMethod('onIntentNavigation', args);
} else {
// Flutter 未 ready,暂存为 pending
console.warn(TAG, `channel not ready, storing pageId "${pageId}" as pending`);
IntentNavigationPlugin.pendingNavigation = { pageId, dishId };
}
}
private handleConsumePendingNavigation(result: MethodResult): void {
const navigation = IntentNavigationPlugin.pendingNavigation;
IntentNavigationPlugin.pendingNavigation = null; // 消费后清空
if (!navigation) {
result.success(null);
return;
}
const args = new Map<string, Object>();
args.set('pageId', navigation.pageId);
if (navigation.dishId) {
args.set('dishId', navigation.dishId);
}
result.success(args);
}
}
关键设计:
-
pendingNavigation是static的,确保所有实例共享同一个缓存 -
navigateToPage()先判断channel是否可用,再决定是推送还是缓存 -
handleConsumePendingNavigation()消费后立即清空,防止重复处理
二、EntryAbility——冷启动和热启动的处理
export default class EntryAbility extends FlutterAbility {
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
flutterEngine.getPlugins()?.add(new IntentNavigationPlugin())
// ... 其他插件
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
super.onCreate(want, launchParam)
// 冷启动:把启动参数写成 pending navigation
const pageId = want.parameters?.['pageId'] as string;
const dishId = want.parameters?.['dishId'] as string;
if (pageId) {
IntentNavigationPlugin.setPendingNavigation(pageId, dishId);
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
super.onNewWant(want, launchParam)
// 热启动:尝试直接推送,失败则缓存
const pageId = want.parameters?.['pageId'] as string;
const dishId = want.parameters?.['dishId'] as string;
if (pageId) {
const plugin = IntentNavigationPlugin.getInstance();
if (plugin) {
plugin.navigateToPage(pageId, dishId);
} else {
IntentNavigationPlugin.setPendingNavigation(pageId, dishId);
}
}
}
}
两种场景的处理差异:
|
场景 |
方法 |
处理方式 |
|---|---|---|
|
冷启动 |
|
直接写入 |
|
热启动 |
|
先尝试 |
为什么冷启动不能直接调 navigateToPage:
冷启动时:
EntryAbility.onCreate() 被调用
→ IntentNavigationPlugin 还没被 configureFlutterEngine() 注册
→ getInstance() 返回 null
→ 只能写入 pendingNavigation
热启动时:
EntryAbility.onNewWant() 被调用
→ IntentNavigationPlugin 已经被注册
→ getInstance() 可能返回实例
→ 可以尝试 navigateToPage()
三、Flutter 侧——初始化后主动消费
// intent_navigation_channel.dart
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);
}
}
});
// 主动消费 pending navigation
_consumePending();
}
static Future<void> _consumePending() async {
try {
final payload = await _channel.invokeMethod<Object?>(
'consumePendingNavigation',
);
final navigation = _parseArguments(payload);
if (navigation != null) {
_navigate(navigation);
}
} on MissingPluginException {
// 非鸿蒙平台,忽略
} catch (e) {
AppLogger.warning('consumePendingNavigation failed: $e');
}
}
关键设计:
-
init()在监听实时推送的同时,主动调用_consumePending() -
consumePendingNavigation会返回缓存的导航数据(如果有的话) -
消费后缓存立即清空,不会重复处理
四、完整的时序图
场景 1:冷启动(应用未运行)
用户在鸿蒙搜索点击"搜索美食"
│
▼
鸿蒙系统启动应用 → EntryAbility.onCreate(want)
│
├─ 提取 pageId='search'
├─ IntentNavigationPlugin.setPendingNavigation('search')
│ → pendingNavigation = { pageId: 'search' }
│
▼
configureFlutterEngine()
├─ 注册 IntentNavigationPlugin
│
▼
Flutter 引擎启动
│
▼
intent_navigation_channel.dart.init()
├─ 监听 onIntentNavigation(实时推送)
├─ _consumePending()
│ → invokeMethod('consumePendingNavigation')
│ → 拿到 { pageId: 'search' }
│ → _navigate() → _router.go('/search')
│
▼
用户进入搜索页面
场景 2:热启动(应用在后台)
应用在后台运行
│
▼
用户在鸿蒙搜索点击"探索美食"
│
▼
EntryAbility.onNewWant(want)
├─ 提取 pageId='explore'
├─ IntentNavigationPlugin.getInstance()
│ │
│ ├─ plugin 不为 null → navigateToPage('explore')
│ │ → channel.invokeMethod('onIntentNavigation', {pageId: 'explore'})
│ │ → Flutter 收到实时推送 → _navigate()
│ │
│ └─ plugin 为 null → setPendingNavigation('explore')
│ → Flutter 初始化后消费
│
▼
用户进入探索页面
场景 3:鸿蒙搜索直达(非启动入口)
应用已在前台运行
│
▼
用户在鸿蒙搜索点击"搜索美食"
│
▼
InsightIntentExecutorImpl.jumpFunctionPage()
├─ 校验 pageId='search'
├─ IntentNavigationPlugin.getInstance()
│ → plugin 不为 null
│ → navigateToPage('search')
│ → channel.invokeMethod('onIntentNavigation', {pageId: 'search'})
│
▼
Flutter 收到实时推送 → _navigate()
│
▼
用户进入搜索页面
五、为什么 pendingNavigation 必须是 static
private static pendingNavigation: PendingNavigation | null = null;
因为 EntryAbility 和 IntentNavigationPlugin 是两个不同的类。EntryAbility.onCreate() 需要写入缓存,IntentNavigationPlugin.handleConsumePendingNavigation() 需要读取缓存。只有 static 才能让两个类共享同一个缓存。
六、为什么消费后要立即清空
private handleConsumePendingNavigation(result: MethodResult): void {
const navigation = IntentNavigationPlugin.pendingNavigation;
IntentNavigationPlugin.pendingNavigation = null; // 立即清空
// ...
}
如果不清空,同一个导航请求会被多次处理——用户会看到页面跳转了两次。
关键代码位置
|
文件 |
作用 |
|---|---|
|
|
缓存核心 |
|
|
冷启动/热启动处理 |
|
|
Flutter 消费缓存 |
三种场景对比
|
场景 |
入口来源 |
Flutter 状态 |
处理方式 |
|---|---|---|---|
|
冷启动 |
|
未初始化 |
写入 pending |
|
热启动 |
|
可能未恢复 |
尝试推送,失败则 pending |
|
搜索直达 |
|
已运行 |
直接推送 |
常见坑
-
原生入口到了,但 Flutter handler 还没注册 — 必须有 pending 机制
-
没有 pending 机制,冷启动请求直接丢失 — 用户点击搜索直达但页面没跳转
-
只处理
onCreate,不处理onNewWant— 热启动时入口丢失 -
Flutter 初始化后忘了主动补消费逻辑 —
_consumePending()必须在init()中调用 -
pendingNavigation 没有清空 — 同一导航被多次处理
-
pendingNavigation 不是 static — 两个类无法共享缓存
-
没有处理 MissingPluginException — 非鸿蒙平台会抛异常
可复用模板
鸿蒙插件缓存模板
interface PendingNavigation {
pageId: string;
extra?: string;
}
export default class NavPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
private static instance: NavPlugin | null = null;
private static pendingNavigation: PendingNavigation | null = null;
navigateToPage(pageId: string, extra?: string): void {
if (this.channel) {
const args = new Map<string, Object>();
args.set('pageId', pageId);
if (extra) args.set('extra', extra);
this.channel.invokeMethod('onNavigation', args);
} else {
NavPlugin.pendingNavigation = { pageId, extra };
}
}
private handleConsume(result: MethodResult): void {
const nav = NavPlugin.pendingNavigation;
NavPlugin.pendingNavigation = null;
if (!nav) { result.success(null); return; }
const args = new Map<string, Object>();
args.set('pageId', nav.pageId);
if (nav.extra) args.set('extra', nav.extra);
result.success(args);
}
}
EntryAbility 处理模板
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
super.onCreate(want, launchParam)
const pageId = want.parameters?.['pageId'] as string;
if (pageId) {
NavPlugin.setPendingNavigation(pageId);
}
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
super.onNewWant(want, launchParam)
const pageId = want.parameters?.['pageId'] as string;
if (pageId) {
const plugin = NavPlugin.getInstance();
if (plugin) {
plugin.navigateToPage(pageId);
} else {
NavPlugin.setPendingNavigation(pageId);
}
}
}
Flutter 消费模板
static void init(GoRouter router) {
_router = router;
_channel.setMethodCallHandler((call) async {
if (call.method == 'onNavigation') {
final payload = _parseArguments(call.arguments);
if (payload != null) _navigate(payload);
}
});
_consumePending(); // 主动消费
}
static Future<void> _consumePending() async {
try {
final payload = await _channel.invokeMethod<Object?>('consumePending');
final nav = _parseArguments(payload);
if (nav != null) _navigate(nav);
} on MissingPluginException {
// 非鸿蒙平台,忽略
}
}
本篇总结
鸿蒙冷启动入口和普通页面跳转最大的不同,就是时机不确定。食界探味的处理方案:
-
IntentNavigationPlugin 维护 static 缓存 —
pendingNavigation让两个类共享同一个缓存 -
EntryAbility 区分冷启动/热启动 —
onCreate写入 pending,onNewWant尝试推送 -
Flutter 初始化后主动消费 —
_consumePending()在init()中调用 -
消费后立即清空 — 防止同一导航被多次处理
pendingNavigation 这种设计是鸿蒙系统入口项目里非常实用的一层兜底。入口不丢失,比入口"马上跳"更重要。
更多推荐




所有评论(0)