鸿蒙原生与 Flutter 混合开发:三种通讯方式快速上手
鸿蒙原生与 Flutter 混合开发:三种通讯方式快速上手
这篇文档对应华为开发者官网的混合开发说明,重点讲鸿蒙原生(ArkTS)和 Flutter 之间怎么互相传数据、互相调用。我们把它拆成最常用的三种通道,每种都配一段能直接照着写的代码。
文中的代码不是演示用的假代码,而是已经在下面的项目里编译通过的写法:
一、先搞清楚一件事:谁找谁
混合开发里,Flutter 页面和鸿蒙原生页面放在同一个 App 里。它们互相不认识,所以需要一条“通道”来传话。
华为文档里说的三种通道,本质区别就一句话:数据是谁发起、流向哪边、能不能收到回复。
| 通道 | 谁发起 | 数据方向 | 有没有回复 | 典型场景 |
|---|---|---|---|---|
| MethodChannel | Flutter 主动调用原生 | Flutter → 原生 → Flutter | 有,一次一答 | 获取设备号、调相机、算一个结果 |
| BasicMessageChannel | 双方都可以主动发 | 双向 | 可以回复,也可以不回复 | 传简单字符串、JSON、自定义协议 |
| EventChannel | 原生主动推给 Flutter | 原生 → Flutter | 没有,持续发事件流 | 传感器数据、电量变化、进度更新 |
一句话记忆:
- 想“问一句、答一句”,用 MethodChannel。
- 想“来回聊天”,用 BasicMessageChannel。
- 想“原生不停往 Flutter 推送”,用 EventChannel。
注意:三种通道的名字就是它们的“身份证”,Flutter 侧和原生侧必须写成一模一样,否则对不上。
二、MethodChannel:一问一答
2.1 什么时候用
Flutter 想调用原生能力,并且要拿到一个返回值。比如:
- 获取设备的某个信息;
- 调用原生已经写好的功能;
- 让原生帮忙算个数再还回来。
华为官网的用例是“获取设备号”,我们项目里对应的是“减法由原生计算后返回”。
2.2 数据怎么走
Flutter 调 invokeMethod('subtract')
↓
鸿蒙原生收到方法名和参数
↓
原生计算(执行的函数),result.success(结果)
↓
Flutter 拿到结果并显示
2.3 Flutter 侧代码
import 'package:flutter/services.dart';
// 通道名,两边必须一致
const MethodChannel _nativeChannel =
MethodChannel('com.example.hybrid/native_calculator');
Future<void> subtract() async {
// 把两个数传给原生,原生算完把差返回来
final result = await _nativeChannel.invokeMethod<dynamic>(
'subtract',
{'first': 12, 'second': 5},
);
print(result); // 7
}
2.4 鸿蒙原生侧代码
import { MethodChannel } from '@ohos/flutter_ohos';
const channel = new MethodChannel(
binaryMessenger,
'com.example.hybrid/native_calculator'
);
channel.setMethodCallHandler({
onMethodCall: (call, result) => {
if (call.method === 'subtract') {
const first = call.argument('first') as number;
const second = call.argument('second') as number;
// 计算在原生侧完成
result.success(first - second);
return;
}
result.notImplemented();
}
});
三、MethodChannel 反着用:原生调 Flutter
MethodChannel 不是只能 Flutter 先开口。原生也可以反过来调用 Flutter 里已经写好的方法。
3.1 Flutter 侧:先注册好一个方法等着被调
const MethodChannel _flutterChannel =
MethodChannel('com.example.hybrid/flutter_calculator');
// 在 initState 里注册
_flutterChannel.setMethodCallHandler((call) async {
if (call.method == 'divide') {
return first / second;
}
throw PlatformException(code: 'NOT_IMPLEMENTED');
});
3.2 原生侧:主动调一下
const flutterChannel = new MethodChannel(
binaryMessenger,
'com.example.hybrid/flutter_calculator'
);
// 原生主动让 Flutter 执行除法
flutterChannel.invokeMethod('divide', null);
对应项目里的按钮:除法:原生调 Flutter。
四、BasicMessageChannel:双向聊天
4.1 什么时候用
两边都要能主动发消息,数据是字符串、简单对象或 JSON。它比 MethodChannel 更“轻”,没有方法名,直接发一条消息,对方可以回复。
我们项目里对应“乘法”:Flutter 发 3,4 给原生,原生算完把 3 × 4 = 12 回给 Flutter。
4.2 数据怎么走
Flutter 发消息 "3,4"
↓
鸿蒙原生收到后处理
↓
原生 reply.reply("3 × 4 = 12")
↓
Flutter 收到回复
4.3 Flutter 侧代码
import 'package:flutter/services.dart';
// StringCodec 表示这条通道传的是字符串
const BasicMessageChannel<String> _msgChannel =
BasicMessageChannel<String>(
'com.example.hybrid/basic_message',
StringCodec(),
);
final reply = await _msgChannel.send('3,4');
print(reply); // 3 × 4 = 12
4.4 鸿蒙原生侧代码
import {
BasicMessageChannel,
StringCodec,
} from '@ohos/flutter_ohos';
const msgChannel = new BasicMessageChannel<string>(
binaryMessenger,
'com.example.hybrid/basic_message',
StringCodec.INSTANCE
);
msgChannel.setMessageHandler({
onMessage: (message, reply) => {
const parts = message.split(',');
const first = Number(parts[0]);
const second = Number(parts[1]);
// 原生把结果回给 Flutter
reply.reply(`${first} × ${second} = ${first * second}`);
}
});
4.5 原生也可以先开口
反过来,原生想主动发消息给 Flutter 时,Flutter 也要提前注册一个接收器:
const BasicMessageChannel<String> _nativeMsgChannel =
BasicMessageChannel<String>(
'com.example.hybrid/native_message',
StringCodec(),
);
_nativeMsgChannel.setMessageHandler((message) async {
print('收到原生消息:$message');
return '已收到';
});
原生侧直接发:
const nativeMsgChannel = new BasicMessageChannel<string>(
binaryMessenger,
'com.example.hybrid/native_message',
StringCodec.INSTANCE
);
nativeMsgChannel.send('这是鸿蒙原生发来的消息');
五、EventChannel:原生持续推事件
5.1 什么时候用
数据主要从原生往 Flutter 流,而且可能不止一次。比如传感器读数、网络状态变化、进度更新。
我们项目里对应“原生推事件”:鸿蒙原生通过 EventChannel 推一条设备信息给 Flutter。
5.2 数据怎么走
Flutter 先订阅事件流
↓
鸿蒙原生保存 EventSink(一个“发送口”)
↓
原生随时 sink.success(事件)
↓
Flutter 的监听回调收到事件
5.3 Flutter 侧代码
import 'dart:async';
import 'package:flutter/services.dart';
const EventChannel _eventChannel =
EventChannel('com.example.hybrid/native_event');
StreamSubscription? _subscription;
_subscription = _eventChannel
.receiveBroadcastStream()
.listen((event) {
print('收到原生事件:$event');
});
// 页面销毁时记得取消订阅
_subscription?.cancel();
5.4 鸿蒙原生侧代码
import { EventChannel, EventSink } from '@ohos/flutter_ohos';
private nativeEventSink: EventSink | null = null;
const eventChannel = new EventChannel(
binaryMessenger,
'com.example.hybrid/native_event'
);
eventChannel.setStreamHandler({
onListen: (args, events) => {
// Flutter 开始监听时,把发送口保存下来
this.nativeEventSink = events;
},
onCancel: () => {
// Flutter 取消监听时清掉
this.nativeEventSink = null;
}
});
// 以后任何地方、任何时候都能推一条事件
this.nativeEventSink?.success('设备号 3AQ0224B14026838');
六、华为官网用例和我们项目的对照
华为文档用“获取设备号”贯穿三种通道,我们的项目用“计算器”贯穿,逻辑是对应的:
| 华为文档 | 我们项目 | 通道 |
|---|---|---|
| Flutter 调原生拿设备号 | 减法:Flutter 调原生算差 | MethodChannel |
| 原生方法由 Flutter 实现 | 除法:原生反过来调 Flutter | MethodChannel 反向 |
| 双向发消息 | 乘法:Flutter 发两个数、原生回乘积 | BasicMessageChannel |
| 设备号变化持续推送 | 原生主动推事件给 Flutter | EventChannel |
七、最容易踩的几个坑
-
通道名对不上:Flutter 和原生必须用完全相同的字符串,少一个字符都会报
MissingPluginException。 -
编解码器不匹配:
BasicMessageChannel传字符串就用StringCodec,两边要一致。 -
忘了取消订阅:
EventChannel的StreamSubscription在页面销毁时要cancel(),否则会泄漏。 -
结果回调要各走各的路:原生处理完必须调用
result.success、result.error或result.notImplemented其中一个,不能什么都不做。 -
先注册再调用:如果原生要调 Flutter 的方法,Flutter 侧必须先把
setMethodCallHandler注册好,否则原生调不到。
项目地址:https://gitee.com/wking123321/arkts-flutter
官网介绍:https://developer.huawei.com/consumer/cn/blog/topic/03223830707488374
浙公网安备 33010602011771号