1. 概述
Flutter Module(flutter_module)集成到 HarmonyOS 原生应用(flutter_Ohos)的完整过程,采用 单 Ability + FlutterEntry 架构:
- 应用只有一个
EntryAbility(继承UIAbility),首页为原生 ArkUI 页面 - 通过
router.pushUrl跳转到 Flutter 页面,使用FlutterEntry创建引擎并渲染 - 支持传递不同
route参数,直接打开指定的 Flutter 页面
架构示意
EntryAbility (UIAbility)
└── pages/Index(原生首页 · 按钮列表)
│
├── router.pushUrl({ params: { route: '/' } })
├── router.pushUrl({ params: { route: '/counter' } })
├── router.pushUrl({ params: { route: '/profile' } })
└── router.pushUrl({ params: { route: '/settings' } })
│
└── pages/FlutterIndex
└── FlutterEntry(读取 route → 创建 FlutterEngine → 渲染 FlutterPage)
2. 前置条件
| 依赖项 | 说明 |
|---|---|
| DevEco Studio | 支持 HarmonyOS 开发的 IDE |
| Flutter SDK | 需支持 HarmonyOS/OpenHarmony 平台(包含 flutter-hvigor-plugin) |
| HarmonyOS SDK |
targetSdkVersion 需与 Flutter 引擎 HAR 兼容(见问题 3) |
| Flutter Module 工程 | 位于 ../flutter_module(与 flutter_Ohos 同级目录) |
3. 最终项目结构
FlutterDemo/
├── flutter_module/ # Flutter Module 工程
│ ├── lib/main.dart # Flutter Dart 入口(路由定义)
│ └── .ohos/
│ ├── local.properties # [需配置] flutter.sdk 路径
│ └── flutter_module/ # Flutter 构建产物桥接模块
│ ├── index.ets # 导出 GeneratedPluginRegistrant
│ ├── oh-package.json5 # 声明 @ohos/flutter_ohos 依赖
│ └── src/main/resources/rawfile/
│ └── flutter_assets/ # Dart 编译产物(自动生成)
│
└── flutter_Ohos/ # HarmonyOS 原生宿主工程
├── local.properties # [需配置] flutter.sdk 路径
├── include_flutter.ts # [新增] Flutter 构建插件配置
├── hvigorfile.ts # [修改] 加载 Flutter 构建插件
├── build-profile.json5 # [修改] SDK 版本 + 注册 flutter_module
├── node_modules/
│ └── flutter-hvigor-plugin → ... # [新增] 符号链接到 Flutter SDK
│
└── entry/
├── oh-package.json5 # [修改] 添加 @ohos/flutter_module 依赖
└── src/main/
├── module.json5 # 仅注册 EntryAbility(无需额外 Ability)
├── ets/
│ ├── entryability/
│ │ └── EntryAbility.ets # [修改] 注册 FlutterManager
│ └── pages/
│ ├── Index.ets # [修改] 原生首页,按钮跳转 Flutter
│ └── FlutterIndex.ets # [新增] FlutterEntry 容器页
└── resources/base/profile/
└── main_pages.json # [修改] 注册 FlutterIndex 页面
4. 集成步骤
步骤 1:配置 Flutter SDK 路径
需要在两个位置配置 flutter.sdk:
flutter_Ohos/local.properties:
flutter.sdk=/path/to/your/flutter_sdk
flutter_module/.ohos/local.properties(如不存在则创建):
flutter.sdk=/path/to/your/flutter_sdk
替换为实际的 Flutter SDK 路径。两处路径必须一致。
步骤 2:创建 flutter-hvigor-plugin 符号链接
Flutter 的 Hvigor 构建插件位于 Flutter SDK 内部,需通过符号链接让 Node.js 模块解析系统找到它:
cd flutter_Ohos
mkdir -p node_modules
ln -sf /path/to/flutter_sdk/packages/flutter_tools/hvigor node_modules/flutter-hvigor-plugin
如果
flutter命令已在 PATH 中,可用动态路径:ln -sf $(dirname $(dirname $(which flutter)))/packages/flutter_tools/hvigor node_modules/flutter-hvigor-plugin
步骤 3:创建 include_flutter.ts
在 flutter_Ohos/ 根目录创建,用于引入构建插件并指定 Flutter Module 路径:
// include_flutter.ts
import path from 'path'
export { flutterHvigorPlugin, injectNativeModules } from 'flutter-hvigor-plugin'
export function getFlutterProjectPath(): string {
return path.resolve(__dirname, '../flutter_module')
}
步骤 4:修改根 hvigorfile.ts
加载 Flutter 构建插件:
// hvigorfile.ts
import { appTasks } from '@ohos/hvigor-ohos-plugin';
import { flutterHvigorPlugin, getFlutterProjectPath } from './include_flutter';
export default {
system: appTasks,
plugins: [flutterHvigorPlugin(getFlutterProjectPath(), 1)]
}
第二个参数
1表示 Flutter 模块数量。
步骤 5:修改 build-profile.json5
需要做三件事:调整 SDK 版本、关闭 useNormalizedOHMUrl、注册 flutter_module。
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "5.0.5(17)", // ← 与 Flutter 引擎 HAR 兼容
"compatibleSdkVersion": "5.0.5(17)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": false // ← 必须关闭
}
}
}
]
// ... signingConfigs, buildModeSet 保持不变
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [{ "name": "default", "applyToProducts": ["default"] }]
},
{
"name": "flutter_module",
"srcPath": "../flutter_module/.ohos/flutter_module", // ← 指向桥接模块
"targets": [{ "name": "default", "applyToProducts": ["default"] }]
}
]
}
关键点:
srcPath指向../flutter_module/.ohos/flutter_module(Flutter 构建产物的桥接模块),而非./flutter_module。
步骤 6:添加 entry 模块对 flutter_module 的依赖
修改 entry/oh-package.json5:
{
"dependencies": {
"@ohos/flutter_module": "file:../flutter_module"
}
}
步骤 7:确保 Flutter 桥接模块的 oh-package.json5 声明了依赖
检查 flutter_module/.ohos/flutter_module/oh-package.json5,确保包含 @ohos/flutter_ohos 依赖:
{
"name": "@ohos/flutter_module",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "index.ets",
"author": "",
"license": "OpenValley",
"dependencies": {
"@ohos/flutter_ohos": ""
}
}
同时确认 flutter_module/.ohos/flutter_module/index.ets 内联了 GeneratedPluginRegistrant:
import { FlutterEngine } from '@ohos/flutter_ohos';
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine): void {
}
}
为什么内联? 原始
index.ets通过相对路径引用GeneratedPluginRegistrant,但 ArkTS 编译器禁止跨模块的相对路径导入。内联后可避免此问题。
步骤 8:修改 EntryAbility
EntryAbility 保持继承 UIAbility,但需要向 FlutterManager 注册自身,这样 FlutterEntry 才能通过 getContext() 找到对应的 Ability 和 WindowStage:
// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { FlutterManager } from '@ohos/flutter_ohos';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN, 'testTag', 'EntryAbility onCreate');
FlutterManager.getInstance().pushUIAbility(this); // ← 注册
}
onWindowStageCreate(windowStage: window.WindowStage): void {
FlutterManager.getInstance().pushWindowStage(this, windowStage); // ← 注册
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load content: %{public}s', JSON.stringify(err));
return;
}
});
}
onWindowStageDestroy(): void {
FlutterManager.getInstance().popWindowStage(this); // ← 注销
}
onDestroy(): void {
FlutterManager.getInstance().popUIAbility(this); // ← 注销
}
onForeground(): void {}
onBackground(): void {}
}
步骤 9:创建原生首页(Index.ets)
使用 router.pushUrl 跳转到 Flutter 页面,通过 params 传递目标路由:
// entry/src/main/ets/pages/Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
private openFlutterPage(route: string): void {
router.pushUrl({
url: 'pages/FlutterIndex',
params: { 'route': route }
});
}
build() {
Column() {
Text('Flutter Module Demo')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 40 })
Button('Flutter 首页')
.width('70%').height(45).fontSize(16)
.margin({ bottom: 12 })
.onClick(() => this.openFlutterPage('/'))
Button('计数器页面')
.width('70%').height(45).fontSize(16)
.margin({ bottom: 12 })
.onClick(() => this.openFlutterPage('/counter'))
Button('个人资料页面')
.width('70%').height(45).fontSize(16)
.margin({ bottom: 12 })
.onClick(() => this.openFlutterPage('/profile'))
Button('设置页面')
.width('70%').height(45).fontSize(16)
.onClick(() => this.openFlutterPage('/settings'))
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
步骤 10:创建 Flutter 容器页(FlutterIndex.ets)
使用 FlutterEntry(非 FlutterAbility)在同一 Ability 内创建 Flutter 引擎并渲染视图:
// entry/src/main/ets/pages/FlutterIndex.ets
import { FlutterEntry, FlutterPage, FlutterEngine, FlutterEngineConfigurator } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '@ohos/flutter_module';
import { router } from '@kit.ArkUI';
class PluginRegistrant implements FlutterEngineConfigurator {
configureFlutterEngine(flutterEngine: FlutterEngine): void {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
cleanUpFlutterEngine(flutterEngine: FlutterEngine): void {
}
}
@Entry
@Component
struct FlutterIndex {
private flutterEntry: FlutterEntry | null = null;
@State viewId: string = "";
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
let route = '/';
if (params !== null && params !== undefined
&& params['route'] !== null && params['route'] !== undefined) {
route = params['route'] as string;
}
this.flutterEntry = new FlutterEntry(getContext(this), { 'route': route });
this.flutterEntry.setFlutterEngineConfigurator(new PluginRegistrant());
this.flutterEntry.aboutToAppear();
this.viewId = this.flutterEntry.getFlutterView().getId();
}
aboutToDisappear(): void {
this.flutterEntry?.aboutToDisappear();
}
onPageShow(): void {
this.flutterEntry?.onPageShow();
}
onPageHide(): void {
this.flutterEntry?.onPageHide();
}
onBackPress(): boolean {
this.flutterEntry?.onBackPress();
return true;
}
build() {
Column() {
FlutterPage({ viewId: this.viewId })
}
}
}
关键 API 说明:
| API | 作用 |
|---|---|
new FlutterEntry(context, params) |
创建 FlutterEntry 实例,params 中的 route 对应 Flutter 的 initialRoute
|
setFlutterEngineConfigurator() |
设置引擎配置器,用于注册 Flutter 插件(必须在 aboutToAppear 之前调用) |
aboutToAppear() |
创建 FlutterEngine 和 FlutterView,初始化引擎 |
getFlutterView().getId() |
获取 viewId,传给 FlutterPage 组件进行渲染 |
aboutToDisappear() |
销毁引擎和视图,释放资源 |
onPageShow() / onPageHide()
|
生命周期同步,通知 Flutter 前后台切换 |
onBackPress() |
将返回事件转发给 Flutter 的 NavigationChannel |
步骤 11:注册页面
确保 entry/src/main/resources/base/profile/main_pages.json 包含两个页面:
{
"src": [
"pages/Index",
"pages/FlutterIndex"
]
}
步骤 12:Flutter Dart 端路由配置
flutter_module/lib/main.dart 中通过 PlatformDispatcher.instance.defaultRouteName 接收原生传递的路由:
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Module',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
initialRoute: ui.PlatformDispatcher.instance.defaultRouteName,
routes: {
'/': (context) => const MyHomePage(title: 'Flutter Home'),
'/counter': (context) => const CounterPage(),
'/profile': (context) => const ProfilePage(),
'/settings': (context) => const SettingsPage(),
},
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => UnknownRoutePage(routeName: settings.name ?? ''),
);
},
);
}
}
添加新路由步骤:
- 在
main.dart的routes中添加新的路由映射 - 在
Index.ets中添加对应按钮,调用this.openFlutterPage('/your_route') - 无需修改
FlutterIndex.ets或其他配置文件
5. 构建与运行
# 1. 确保 Flutter 依赖已解析
cd ../flutter_module
flutter pub get
# 2. 在 DevEco Studio 中打开 flutter_Ohos 项目
# 执行 File → Sync and Refresh Project
# 3. 点击 Run 编译运行
运行效果
- 应用启动 → 显示原生首页(4 个按钮)
- 点击「Flutter 首页」→ 显示 Flutter 导航首页(内含 Counter / Profile / Settings 卡片)
- 点击「计数器页面」→ 直接打开 Flutter 计数器页面
- 点击「个人资料页面」→ 直接打开 Flutter 个人资料页面
- 点击「设置页面」→ 直接打开 Flutter 设置页面
- 按返回键 → 返回原生首页
6. 常见问题与解决方案
Q1:Cannot find module 'flutter-hvigor-plugin' {#q1}
原因: Flutter 的 Hvigor 构建插件不在 node_modules 中。
解决:
mkdir -p node_modules
ln -sf /path/to/flutter_sdk/packages/flutter_tools/hvigor node_modules/flutter-hvigor-plugin
同时检查 local.properties 中 flutter.sdk 路径是否正确。
Q2:Cannot find module '@ohos/flutter_ohos' 或 Cannot find module './src/main/ets/plugins/GeneratedPluginRegistrant' {#q2}
原因: flutter_module/.ohos/flutter_module/index.ets 中通过相对路径引用 GeneratedPluginRegistrant,但 ArkTS 编译器禁止跨模块相对路径导入。
解决: 将 GeneratedPluginRegistrant 类内联到 index.ets 中(见步骤 7),并在 oh-package.json5 中声明 @ohos/flutter_ohos 依赖。
Q3:ArkTS 编译报错 ESObject type is restricted / 大量 deprecated API 警告 {#q3}
原因: 宿主工程的 targetSdkVersion 高于 Flutter 引擎 HAR 的构建版本。Flutter 引擎 HAR 通常针对 SDK 5.x 构建,如果宿主工程使用 6.x SDK,会触发严格模式不兼容。
解决: 在 build-profile.json5 中将 SDK 版本降到兼容范围:
"targetSdkVersion": "5.0.5(17)",
"compatibleSdkVersion": "5.0.5(17)",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": false
}
}
Q4:Failed to resolve OhmUrl for flutter_module/index.ets {#q4}
原因: ArkTS 编译器无法解析 Flutter 桥接模块的路径。
解决: 在 build-profile.json5 的 modules 数组中静态注册桥接模块,srcPath 必须指向 ../flutter_module/.ohos/flutter_module:
{
"name": "flutter_module",
"srcPath": "../flutter_module/.ohos/flutter_module",
"targets": [{ "name": "default", "applyToProducts": ["default"] }]
}
Q5:Flutter 页面打不开(白屏或显示错误页面)
可能原因及检查项:
| 检查项 | 说明 |
|---|---|
FlutterManager 是否注册 |
EntryAbility.onCreate 必须调用 FlutterManager.getInstance().pushUIAbility(this)
|
| WindowStage 是否注册 |
onWindowStageCreate 必须调用 FlutterManager.getInstance().pushWindowStage(this, windowStage)
|
| FlutterEntry 创建时机 |
setFlutterEngineConfigurator 必须在 aboutToAppear() 之前调用 |
| Flutter 资产是否构建 | 检查 flutter_module/.ohos/flutter_module/src/main/resources/rawfile/flutter_assets/ 下是否有 kernel_blob.bin
|
| 生命周期是否正确桥接 |
FlutterIndex.ets 必须实现 aboutToDisappear、onPageShow、onPageHide、onBackPress
|
Q6:如何在原生和 Flutter 之间传递数据?
通过 Flutter Platform Channel 机制实现:
- MethodChannel — 方法调用(如触发原生功能)
- EventChannel — 事件流(如传感器数据)
- BasicMessageChannel — 通用消息传递
在 FlutterIndex.ets 中可通过 this.flutterEntry.getFlutterEngine() 获取引擎实例,进而创建 Channel。
7. 关键文件变更清单
| 文件 | 操作 | 说明 |
|---|---|---|
local.properties |
修改 | 添加 flutter.sdk 路径 |
node_modules/flutter-hvigor-plugin |
新增 | Flutter SDK hvigor 插件的符号链接 |
include_flutter.ts |
新增 | Flutter 构建插件配置,指定 Flutter Module 路径 |
hvigorfile.ts |
修改 | 加载 flutterHvigorPlugin
|
build-profile.json5 |
修改 | SDK 版本调整 + 注册 flutter_module 桥接模块 |
entry/oh-package.json5 |
修改 | 添加 @ohos/flutter_module 本地依赖 |
entry/.../EntryAbility.ets |
修改 | 注册/注销 FlutterManager
|
entry/.../Index.ets |
修改 | 添加按钮,通过 router.pushUrl 跳转到 Flutter |
entry/.../FlutterIndex.ets |
新增 | FlutterEntry 容器页,创建引擎并渲染 Flutter |
entry/.../main_pages.json |
修改 | 注册 pages/FlutterIndex
|
flutter_module/.ohos/local.properties |
新增 | 添加 flutter.sdk 路径 |
flutter_module/.ohos/flutter_module/index.ets |
修改 | 内联 GeneratedPluginRegistrant
|
flutter_module/.ohos/flutter_module/oh-package.json5 |
修改 | 添加 @ohos/flutter_ohos 依赖声明 |
flutter_module/lib/main.dart |
修改 | 配置 initialRoute 和路由表 |
8. FlutterEntry vs FlutterAbility 对比
| 特性 | FlutterEntry(当前方案) | FlutterAbility |
|---|---|---|
| Ability 数量 | 1 个(单 UIAbility) | 2 个(UIAbility + FlutterAbility) |
| 页面跳转方式 |
router.pushUrl(页面内导航) |
startAbility(跨 Ability 导航) |
| 引擎创建位置 | 在页面 aboutToAppear 中手动创建 |
在 Ability onCreate 中自动创建 |
| 路由传递方式 |
router.getParams() → FlutterEntry 构造参数 |
Want.parameters['route'] |
| 生命周期管理 | 需手动桥接(onPageShow/onPageHide 等) |
自动管理 |
| 适用场景 | 原生为主、Flutter 为辅的混合应用 | Flutter 为主的独立页面 |
| 返回行为 |
router.back() 返回原生页 |
关闭 Ability |