【AtomGit 携手开源鸿蒙】Flutter-OH三方库鸿蒙化 - 1
简单来说:MethodChannel 是 Flutter 和原生代码(Android/iOS)之间的“电话线”。Flutter 端:你是一个只会说 Dart 语言的人原生端:对方是一个只会说 Java/Kotlin(Android)、 Objective-C/Swift(iOS)或ArkTs(ohos)的人MethodChannel:就是一部翻译电话,让你们能互相通话启动应用:Flutter 页面
新年新气象,小鱼打算跟着【AtomGit 携手开源鸿蒙】开启一段新的旅程:Flutter-OH三方库鸿蒙化。又是一个新的挑战,一个新的从0到1的过程,因为小鱼连flutter插件是什么,怎么工作的都不知道。已经两天的心理建设,不断的告诉自己“慢就是快”,所以小鱼看了一些视频,学了一些基础知识,打算从0出发 🛫,跟着小鱼一起进步吧。
本文两个目标:1、使用MethodChannel调用平台能力:在Flutter页面输入消息内容,点击按钮后调用Notification Kit(用户通知服务)给用户发送一个通知。
2、混合开发场景下,实现ArkUI页面向Flutter页面的跳转与返回,并监听页面的生命周期,适配系统的侧滑手势,实现页面逐级返回
内容实现来源于:开发者学堂 ,文章是我根据自己的理解写的,大家可以边看视频边看文章。
一、基础知识
1、什么是 MethodChannel
简单来说:MethodChannel 是 Flutter 和原生代码(Android/iOS)之间的“电话线”。
-
Flutter 端:你是一个只会说 Dart 语言的人
-
原生端:对方是一个只会说 Java/Kotlin(Android)、 Objective-C/Swift(iOS)或ArkTs(ohos)的人
-
MethodChannel:就是一部翻译电话,让你们能互相通话
2、为什么需要MethodChannel
Flutter 自己不能做这些事情:
-
📱 读取电池电量
-
📍 获取手机位置
-
📷 调用摄像头
-
💳 使用指纹/面容识别
-
🔔 发送本地通知
因为这些功能都是手机系统(原生)才能做的,所以需要通过 MethodChannel 去“打电话”问原生代码要这些信息。
二、实战1:使用MethodChannel调用平台能力
1、Flutter 打电话的人
// 1. 创建一部电话(指定频道名称)
// 这个名字要记住,原生端也要用一样的
static const platform = MethodChannel('com.zmf.demo');
// 2. 打电话发命令
Future<void> _sendNotification() async {
String message = _controller.text;
try {
// 打电话给原生:我要发送通知
await platform.invokeMethod('sendANotification', message);
} on PlatformException catch (e) {
debugPrint("Failed to send notification: '${e.message}'.");
}
}
2、ohos 原生端(接电话的人)
接收 Flutter 的调用请求,并利用鸿蒙系统的 NotificationKit 发送通知。
具体实现涉及以下几个步骤:
编写 WorkPlugin 类,实现 FlutterPlugin 和 MethodCallHandler 接口,是处理通信的核心类。
- 插件初始化:
- 在
onAttachedToEngine生命周期中,初始化MethodChannel并设置当前类为MethodCallHandler。
- 在
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), 'com.zmf.demo');
this.channel.setMethodCallHandler(this);
}
- 消息处理 (
onMethodCall):- 根据方法名
sendANotification分发逻辑。 - 解析 Flutter 传递的参数
message。
- 根据方法名
onMethodCall(call: MethodCall, result: MethodResult): void {
switch (call.method) {
case 'sendANotification':
const message = call.args as string;
this.sendNotification(message);
result.success(0);
break;
default:
result.notImplemented();
}
}
- 发送通知 (
sendNotification):- 权限检查:首先检查是否已授予通知权限 (
notificationManager.isNotificationEnabled)。 - 权限申请:若未授权,调用
notificationManager.requestEnableNotification申请权限。 - 发布通知:构建
NotificationRequest对象,设置通知标题和内容,调用notificationManager.publish发送通知。
- 权限检查:首先检查是否已授予通知权限 (
sendNotification(message: string) {
notificationManager.isNotificationEnabled().then((data: boolean) => {
if (!data) {
notificationManager.requestEnableNotification(this.context).then(() => {
console.log(TAG, `requestEnableNotification success`);
this.send((message));
}).catch((error: BusinessError) => {
console.error(TAG, `requestEnableNotification failed, code: ${error.code}, message: ${error.message}`);
})
} else {
this.send(message);
}
});
}
send(message: string) {
let notificationRequest: notificationManager.NotificationRequest = {
id: 1,
notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: '夏小鱼发送的消息通知',
text: message
}
}
};
notificationManager.publish(notificationRequest, (error: BusinessError) => {
if (error) {
console.error(TAG, `publish notification failed, code: ${error.code}, message: ${error.message}`);
} else {
console.log(TAG, `publish notification success`);
}
})
}
3、插件注册
核心功能已经写完了,但是需要在应用的入口 Ability 中注册这个插件,才生效哦。
export default class EntryAbility extends FlutterAbility {
//重写 `configureFlutterEngine` 方法。
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
// 调用 `flutterEngine.getPlugins().add` 将 `WorkPlugin` 实例添加到引擎中。
flutterEngine.getPlugins().add(new WorkPlugin(this.context));
}
}
4、写页面
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('com.zmf.demo');
final TextEditingController _controller = TextEditingController();
Future<void> _sendNotification() async {
String message = _controller.text;
try {
await platform.invokeMethod('sendANotification', message);
} on PlatformException catch (e) {
debugPrint("Failed to send notification: '${e.message}'.");
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: '通知内容',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(12),
),
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _sendNotification,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 48),
),
child: const Text('发送通知'),
),
],
),
),
),
);
}
}
5、流程总结:
- 启动应用:Flutter 页面加载,显示输入框和按钮。
- 用户操作:用户输入文本 “Hello HarmonyOS”,点击 “发送通知”。
- Flutter -> Native:Flutter 通过
com.zmf.demo通道发送sendANotification消息,携带参数 “Hello, HarmonyOS”。 - Native 处理:
WorkPlugin接收消息。- 检查系统通知权限(必要时弹窗申请)。
- 调用鸿蒙
notificationManager接口。
- 系统响应:系统通知栏显示标题为 “夏小鱼发送的消息通知”,内容为 “Hello, HarmonyOS” 的通知。
上图:

三、实战2:详细介绍如何在 HarmonyOS 应用中集成 Flutter 页面,并实现从 ArkUI 跳转到 Flutter 以及 Flutter 页面间的导航逻辑
1、Flutter 侧页面实现
首先,我们在 Flutter 工程中创建两个页面:Page1 和 Page2。Page1 是入口页,包含一个跳转按钮;Page2 是二级页面。
// Page1 (MyHomePage)
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
children: <Widget>[
const Text('This is 夏小鱼的 Flutter Page1'),
ElevatedButton(
onPressed: () {
// 使用 Navigator 跳转到 Page2
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const Page2()),
);
},
child: const Text('push to page2'),
),
],
),
),
);
}
}
// Page2
class Page2 extends StatelessWidget {
const Page2({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('This is 夏小鱼的 Flutter Page2')),
);
}
}
2、鸿蒙侧路由跳转 (ArkUI)
在 HarmonyOS 侧,使用 ArkUI 的 Navigation 组件来管理路由。首页是一个 ArkUI 页面,点击按钮跳转到名为 MyFlutterPage 的路由。
@Entry
@Component
struct Index {
// 创建路由栈
pathStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pathStack) {
Column() {
Button('Push to Flutter Page')
.onClick(() => {
// 传递参数,指定 Flutter 初始路由
let param: Record<string, Object> = { 'route': '/' };
this.pathStack.pushPathByName('MyFlutterPage', param)
})
}
}
.navDestination(this.routeMap) // 配置路由映射
}
}
3、新建 NavFlutterEntry (Flutter 入口封装)
为了让 Flutter 页面能被 ArkUI 的 Navigation 管理,我们需要创建一个继承自 FlutterEntry 的类 NavFlutterEntry。它充当了 Flutter 引擎与当前页面上下文的桥梁。
export class NavFlutterEntry extends FlutterEntry {
private pathStack: NavPathStack;
constructor(context: Context, params: Record<string, Object> = {}, pathStack: NavPathStack) {
super(context, params)
this.pathStack = pathStack;
}
}
4、 使用 NavDestination 包裹 FlutterPage
在 ArkUI 的路由目标页面中,我们使用 NavDestination 包裹 FlutterPage 组件。FlutterPage 需要绑定一个 viewId,这个 ID 是由 FlutterEntry 创建的。
@Component
export struct MyFlutterPage {
// ...
build() {
NavDestination() {
// 绑定 viewId 显示 Flutter 内容
FlutterPage({ viewId: this.flutterView?.getId() })
}
.onReady((context) => {
this.init(context) // 初始化引擎
})
// ... 生命周期绑定
}
}
5、 初始化 Flutter 引擎
在 MyFlutterPage 的 init 方法中,我们初始化 NavFlutterEntry。这里可以通过 route 参数告诉 Flutter 引擎启动时加载哪个页面。
init(context: NavDestinationContext) {
this.pathStack = context.pathStack;
// 获取传递过来的参数 (例如 { 'route': '/' })
let params: Record<string, Object> = this.pathStack
.getParamByName('MyFlutterPage')[0] as Record<string, object>
// 初始化 NavFlutterEntry
this.flutterEntry = new NavFlutterEntry(this.getUIContext().getHostContext()!,
params, this.pathStack);
this.flutterEntry.aboutToAppear()
this.flutterView = this.flutterEntry.getFlutterView()
}
6、 绑定 UIAbility 和 WindowStage
由于 Flutter 是在运行态动态加载的,我们需要在应用启动时(EntryAbility 中)将 UIAbility 和 WindowStage 绑定到 FlutterManager,确保 Flutter 引擎能正确获取上下文。
export default class EntryAbility extends FlutterAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 绑定 UIAbility
FlutterManager.getInstance().pushUIAbility(this);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 绑定 WindowStage
FlutterManager.getInstance().pushWindowStage(this, windowStage);
windowStage.loadContent('pages/Index');
}
onDestroy(): void {
FlutterManager.getInstance().popUIAbility(this);
}
}
7、 实现逐级返回 (Back Navigation)
为了实现从 Flutter 页面 2 返回 页面 1,再返回 ArkUI 页面,我们需要处理两个关键点:
- NavDestination.onBackPressed: 拦截系统返回键(包括侧滑手势)。
- 通过
eventHub发送事件 - 调用
flutterEntry.onBackPress()通知 Flutter 引擎处理内部路由回退。
- 通过
.onBackPressed(() => {
console.log('[MyFlutterPage] onBackPressed()')
this.context.eventHub.emit('EVENT_BACK_PRESS')
// 关键:通知 Flutter 处理返回
this.flutterEntry?.onBackPress()
return true // 返回 true 表示我们消费了该事件,不再由系统默认处理
})
- NavFlutterEntry.popSystemNavigator: 当 Flutter 路由栈为空时(即在 Flutter 首页按返回),Flutter 会调用此方法请求退出。此时我们调用 ArkUI 的
pathStack.pop()返回上一级 ArkUI 页面。
popSystemNavigator(): boolean {
if (this.pathStack) {
// Flutter 页面已退完,现在退出 ArkUI 的 NavDestination
this.pathStack.pop();
return true;
}
return false;
}
8、上图



今天结束啦,又是上班摸鱼,收获满满的一天。如果需要代码给我留言~
更多推荐


所有评论(0)