Flutter 三方库鸿蒙适配实践:以 Firebase Messaging 为例实现跨平台推送集成
引言:Flutter 遇上 HarmonyOS
如今多端融合的趋势下,Flutter 凭借高效的渲染和“一次编写,多端部署”的特性,成为了很多开发团队的首选。不过,当应用需要上架华为鸿蒙(HarmonyOS)这类新兴系统时,一个实际问题就出现了:Flutter 丰富的第三方插件(比如推送、地图、支付)大多依赖 Android/iOS 的原生实现,而鸿蒙的应用框架(ArkUI)、API 设计与系统服务跟 Android 有着本质上的不同,导致很多插件无法直接运行。
推送功能就是个典型例子。firebase_messaging 是 Flutter 生态里最常用的 Firebase 云消息插件,但它的官方实现只支持 Android(FCM)和 iOS(APNs)。如果你希望一个依赖该插件的 Flutter 应用能在鸿蒙设备上正常接收推送,就不得不为它重新实现一套鸿蒙端的原生逻辑。
本文就以 firebase_messaging 插件为例,详细梳理 Flutter 三方库适配鸿蒙的完整过程。我们会从底层通信机制讲起,理清适配思路,并给出从架构设计、代码实现、集成调试到性能优化的一整套可行方案,帮助开发者更顺畅地跨过平台之间的生态差异。
一、技术架构与适配原理
1.1 Flutter 插件与平台通道(Platform Channel)机制
Flutter 和原生平台之间通过 Platform Channel 进行异步消息通信。对插件开发者来说,关键是理解它的三层结构:
- Dart 层:在 Flutter 应用中调用插件提供的 API。
- 平台层:在 Android(Java/Kotlin)和 iOS(Objective-C/Swift)中实现具体的功能。
- 桥接层:通过
MethodChannel(方法调用)、EventChannel(事件流)等进行数据传递。
适配鸿蒙的核心,其实就是在鸿蒙侧(使用 ArkTS/Java)重新实现插件的 “平台层”,并确保它能通过 Channel 与 Flutter 的 Dart 层正确通信。好在 OpenHarmony 社区的 flutter_ohos 项目已经提供了 Flutter 引擎在鸿蒙上的基础支持,实现了 Channel 的底层通信能力,这为我们适配插件打下了基础。
1.2 鸿蒙推送生态与 Firebase 的功能对照
firebase_messaging 在 Android 端主要依赖 Google Play 服务和 FCM SDK。在鸿蒙上,我们需要找到对等的系统服务——也就是 华为 Push Kit。但要注意,这并非简单的 API 替换,两者在设计和接口上都有不少区别:
| 功能模块 | Firebase Cloud Messaging (FCM) | 华为 Push Kit | 适配策略 |
|---|---|---|---|
| 初始化与令牌 | 自动初始化,通过 onTokenRefresh 获取 FCM Token | 需手动调用 getToken 接口申请 HMS Push Token | 在鸿蒙端封装 Token 获取逻辑,通过 MethodChannel 返回给 Dart。 |
| 接收消息 | 通过继承 FirebaseMessagingService 处理后台消息 | 通过 PushReceiver 或 Ability 接收透传消息和通知 | 创建鸿蒙 Ability 作为消息接收器,解析后通过 EventChannel 转发至 Flutter。 |
| 主题订阅 | 提供 subscribeToTopic / unsubscribeFromTopic API | 提供 subscribe / unsubscribe 主题接口 | 在鸿蒙端实现对应的主题管理方法,与 Push Kit SDK 交互。 |
| 前台消息处理 | 可通过 onMessage 监听前台消息流 | Push Kit 通知默认展示,透传消息需在接收器内处理 | 在接收器 Ability 中判断应用状态,区分处理并转发。 |
适配的核心思路:在鸿蒙端,我们构建一个叫 firebase_messaging_ohos 的插件。它作为 firebase_messaging 在鸿蒙上的“替身”,保持 Dart API 完全一致,但底层将所有对 FCM 的调用,转为对华为 Push Kit 的调用。
二、适配方案设计与关键代码实现
2.1 整体架构设计
我们为鸿蒙端设计一个双层的插件结构:
- Flutter Plugin 接口层:实现标准 Flutter 插件接口,负责与 Dart 侧通信。
- 鸿蒙 Push Kit 封装层:封装华为 Push Kit 的初始化、Token 管理、消息接收等原生逻辑。
Flutter Dart App (firebase_messaging API)
↓
MethodChannel / EventChannel
↓
鸿蒙端 `firebase_messaging_ohos` 插件
├── PushService (管理Token、主题)
├── PushAbility (接收并处理推送消息)
└── PushEventDispatcher (向Flutter转发事件)
↓
华为 Push Kit SDK
↓
华为推送服务
2.2 核心代码实现
2.2.1 Dart 层(保持接口一致)
为了不改动业务代码,我们新建一个插件包(例如 firebase_messaging_ohos),其 Dart API 与官方 firebase_messaging 完全一致。
// firebase_messaging_ohos.dart
class FirebaseMessagingOhos {
static const MethodChannel _channel =
MethodChannel('plugins.flutter.io/firebase_messaging_ohos');
static const EventChannel _eventChannel =
EventChannel('plugins.flutter.io/firebase_messaging_ohos/events');
// 获取设备令牌
static Future<String?> getToken() async {
return await _channel.invokeMethod<String?>('getToken');
}
// 订阅主题
static Future<void> subscribeToTopic(String topic) async {
await _channel.invokeMethod('subscribeToTopic', {'topic': topic});
}
// 监听消息流(前后台消息)
static Stream<RemoteMessage> get onMessage {
return _eventChannel.receiveBroadcastStream().map((dynamic event) {
return RemoteMessage.fromMap(Map<String, dynamic>.from(event));
});
}
// 初始化配置,行为与原插件对齐
static Future<void> initialize({
FirebaseOptions? options,
bool? automaticDataCollectionEnabled,
}) async {
// 鸿蒙端初始化主要触发 Push Kit SDK 初始化
await _channel.invokeMethod('initialize');
}
}
2.2.2 鸿蒙端实现 - Push Service(核心服务)
这部分负责与 Push Kit SDK 直接交互。
// PushService.java
package com.example.firebasemessagingohos;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.RemoteException;
import ohos.push.PushAgent;
import ohos.push.PushCallback;
import ohos.push.PushManager;
import ohos.push.model.TokenInfo;
public class PushService {
private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "PushService");
private final Context context;
private PushAgent pushAgent;
public PushService(Context context) {
this.context = context;
initPushKit();
}
private void initPushKit() {
pushAgent = PushManager.getPushAgent(context);
pushAgent.setPushCallback(new PushCallback() {
@Override
public void onTokenReceived(TokenInfo tokenInfo) {
// Token 通常会在此回调,但我们主动获取更稳妥
HiLog.info(LABEL, "Push Token received (callback).");
}
@Override
public void onPushMsgStateChanged(boolean isSuccess, String messageId, int errorCode) {
HiLog.debug(LABEL, "Message state changed: %{public}s, code: %{public}d", messageId, errorCode);
}
});
}
// 获取 Token - 对应 Dart 端的 getToken()
public String getToken() throws PushAdapterException {
try {
// 注意:需确保已在 agconnect-services.json 中配置好应用信息
String token = PushManager.getToken(context);
if (token == null || token.isEmpty()) {
throw new PushAdapterException("Failed to get push token: token is empty.");
}
HiLog.info(LABEL, "Successfully got Push Token: %{public}s", token);
return token;
} catch (RemoteException e) {
HiLog.error(LABEL, "RemoteException when getting token: %{public}s", e.getMessage());
throw new PushAdapterException("RemoteException: " + e.getMessage(), e);
} catch (Exception e) {
HiLog.error(LABEL, "Exception when getting token: %{public}s", e.getMessage());
throw new PushAdapterException("Exception: " + e.getMessage(), e);
}
}
// 订阅主题
public void subscribeToTopic(String topic) throws PushAdapterException {
try {
PushManager.subscribe(context, topic);
HiLog.info(LABEL, "Subscribed to topic: %{public}s", topic);
} catch (RemoteException e) {
HiLog.error(LABEL, "Failed to subscribe to topic: %{public}s", e.getMessage());
throw new PushAdapterException("Subscribe failed: " + e.getMessage(), e);
}
}
// 自定义异常,便于统一处理错误
public static class PushAdapterException extends Exception {
public PushAdapterException(String message) { super(message); }
public PushAdapterException(String message, Throwable cause) { super(message, cause); }
}
}
2.2.3 鸿蒙端实现 - Push Ability(消息接收器)
负责接收系统推送,并转发给 Flutter。
// PushAbility.java (继承自Ability,需在config.json中声明)
package com.example.firebasemessagingohos;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.push.PushManager;
import ohos.push.model.PushMsg;
import java.util.HashMap;
import java.util.Map;
public class PushAbility extends Ability {
private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "PushAbility");
private static EventSink eventSink; // 用于向 Flutter 发送事件流的接口,需从 Plugin 类注入
public static void setEventSink(EventSink sink) {
eventSink = sink;
}
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL, "PushAbility started.");
// 处理冷启动时携带推送数据的 Intent
processIntent(intent);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
HiLog.info(LABEL, "PushAbility onNewIntent.");
// 处理从通知栏点击打开应用时携带的数据
processIntent(intent);
}
private void processIntent(Intent intent) {
if (intent == null) return;
// 解析 Push Kit 传递过来的消息数据
PushMsg pushMsg = intent.getSerializableParam(PushManager.MESSAGE_KEY);
if (pushMsg != null) {
Map<String, Object> messageMap = parsePushMsgToMap(pushMsg);
// 通过 EventChannel 发送给 Flutter 端(模拟 onMessageOpenedApp)
if (eventSink != null) {
eventSink.success(messageMap);
}
HiLog.info(LABEL, "Processed push message from intent: %{public}s", pushMsg.getContent());
}
}
// 静默推送或后台消息会触发此方法(需在 config.json 中配置相应权限)
public void onPushMsg(PushMsg pushMsg) {
HiLog.info(LABEL, "Received push message in background: %{public}s", pushMsg.getContent());
Map<String, Object> messageMap = parsePushMsgToMap(pushMsg);
// 转发给 Flutter 的 onMessage 流
if (eventSink != null) {
eventSink.success(messageMap);
}
// 根据消息内容决定是否创建本地通知
if (pushMsg.getNotification() != null) {
createLocalNotification(pushMsg);
}
}
private Map<String, Object> parsePushMsgToMap(PushMsg pushMsg) {
Map<String, Object> map = new HashMap<>();
map.put("messageId", pushMsg.getMessageId());
map.put("data", pushMsg.getContent()); // 通常是 JSON 字符串
map.put("from", pushMsg.getDeviceToken());
map.put("sentTime", pushMsg.getSendTime());
map.put("ttl", pushMsg.getTtl());
// 可在此解析 content 中的自定义键值对
// ... 解析逻辑
return map;
}
private void createLocalNotification(PushMsg pushMsg) {
// 使用鸿蒙的 NotificationHelper 创建本地通知
// ... 实现创建通知的代码
}
}
2.2.4 鸿蒙端实现 - Flutter 插件主类
整合服务与通信,实现 FlutterPlugin 和 MethodChannel 接口。
// FirebaseMessagingOhosPlugin.java
package com.example.firebasemessagingohos;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class FirebaseMessagingOhosPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "FMPPlugin");
private MethodChannel methodChannel;
private EventChannel eventChannel;
private Context applicationContext;
private PushService pushService;
private EventChannel.EventSink eventSink;
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
applicationContext = binding.getApplicationContext();
methodChannel = new MethodChannel(binding.getBinaryMessenger(), "plugins.flutter.io/firebase_messaging_ohos");
methodChannel.setMethodCallHandler(this);
eventChannel = new EventChannel(binding.getBinaryMessenger(), "plugins.flutter.io/firebase_messaging_ohos/events");
eventChannel.setStreamHandler(this);
pushService = new PushService(applicationContext);
HiLog.info(LABEL, "FirebaseMessagingOhosPlugin attached.");
}
@Override
public void onMethodCall(MethodCall call, Result result) {
try {
switch (call.method) {
case "getToken":
String token = pushService.getToken();
result.success(token);
break;
case "subscribeToTopic":
String topic = call.argument("topic");
pushService.subscribeToTopic(topic);
result.success(null);
break;
case "initialize":
// 初始化逻辑,确保 PushAgent 已就绪
result.success(null);
break;
default:
result.notImplemented();
}
} catch (PushService.PushAdapterException e) {
HiLog.error(LABEL, "Method call %{public}s failed: %{public}s", call.method, e.getMessage());
result.error("PUSH_SERVICE_ERROR", e.getMessage(), e.getCause() != null ? e.getCause().toString() : null);
}
}
// EventChannel.StreamHandler 方法
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
this.eventSink = events;
PushAbility.setEventSink(events); // 将 sink 传递给 Ability,用于发送消息
HiLog.info(LABEL, "Flutter 端开始监听消息事件流");
}
@Override
public void onCancel(Object arguments) {
this.eventSink = null;
PushAbility.setEventSink(null);
HiLog.info(LABEL, "Flutter 端取消监听消息事件流");
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
methodChannel.setMethodCallHandler(null);
eventChannel.setStreamHandler(null);
HiLog.info(LABEL, "FirebaseMessagingOhosPlugin detached.");
}
}
三、集成步骤与调试建议
3.1 环境准备与配置
- 开发环境:安装 DevEco Studio,配置好 HarmonyOS SDK 和
flutter_ohos工具链。 - 项目配置:在鸿蒙应用的
config.json中声明推送权限和 Push Ability。{ "app": { "bundleName": "com.your.app", "vendor": "your.vendor" }, "module": { "abilities": [ { "name": "com.example.firebasemessagingohos.PushAbility", "type": "service", "visible": true, "permissions": ["ohos.permission.PUSH"] } ], "reqPermissions": [ { "name": "ohos.permission.PUSH", "reason": "用于接收华为推送消息" } ] } } - 依赖引入:在鸿蒙模块的
build.gradle中添加 Push Kit SDK 依赖。 - 华为侧配置:在华为开发者联盟创建应用,开通 Push Kit 服务,配置好应用签名证书指纹(
.p7b),并把下载的agconnect-services.json文件放到鸿蒙应用模块的entry/src/main/resources/rawfile/目录下。
3.2 代码集成与测试
- 替换插件依赖:在 Flutter 项目的
pubspec.yaml中,将firebase_messaging换成我们开发的firebase_messaging_ohos(或已发布的适配版本)。 - 初始化:在 Flutter 应用启动时,调用
FirebaseMessagingOhos.initialize()。 - 获取 Token:在适当位置调用
FirebaseMessagingOhos.getToken()并上传到你的应用服务器。 - 监听消息:设置
FirebaseMessagingOhos.onMessage监听器来处理收到的消息。 - 测试推送:
- 使用 华为推送调试工具(HMS Toolkit)发送测试消息。
- 在服务端集成华为推送服务端 API,针对设备 Token 发送推送。
- 分别测试 前台、后台 和 应用关闭 三种状态下的消息接收与展示。
3.3 性能与稳定性注意点
- 冷启动耗时:注意鸿蒙端初始化 Push Kit 和获取 Token 的时间,避免影响应用启动速度。
- 内存占用:常驻的 Push Ability 要留意内存使用,避免不必要的对象引用。
- 网络兼容性:确保弱网环境下 Token 获取和消息接收的稳定性,可以考虑加入重试机制。
- 功耗:监听网络状态变化,避免频繁重连导致耗电过快。
- 兼容性:在不同 HarmonyOS 版本(如 API 8、9)上测试插件行为是否一致。
四、总结
通过以上步骤,我们基本完成了 firebase_messaging 插件在鸿蒙上的功能适配。这个案例也展示了 Flutter 三方库鸿蒙适配的通用方法:
- 接口保持一致:Dart 层 API 不变,业务代码无需修改。
- 利用通道通信:复用 Flutter Platform Channel 进行跨平台通信。
- 功能映射替换:理解鸿蒙对应服务的 API,完成等价功能替换。
- 工程化封装:将原生代码封装清晰,提供良好的错误处理和日志。
这种适配不仅是技术实现,更是一种应对多系统生态的设计思路。对于地图、支付、生物识别等其他核心插件,其实都可以参照类似的路径进行适配。
如果能将这类适配方案沉淀为开源项目或工具,将会大幅降低 Flutter 应用向鸿蒙迁移的成本,真正发挥 Flutter 在多平台统一开发上的优势。


被折叠的 条评论
为什么被折叠?



