Flutter/OpenHarmony 三方库适配实战:mobpush_plugin 推送通道与启动
Flutter/OpenHarmony 三方库适配实战:mobpush_plugin 推送通道与启动
推送类插件的鸿蒙适配,难点通常不在“把 MethodChannel 写出来”,而在于如何处理原生 SDK 缺失、事件回调时序和错误能力边界。mobpush_plugin 同时使用 MethodChannel 和 EventChannel,原库在 Android/iOS 上依赖 MobPush SDK 完成注册、别名、标签、测试推送和消息回调。OHOS 当前没有本仓库可接入的 MobPush SDK,因此这次适配不能把所有方法简单改成 result.success(true),否则业务代码会误以为远程推送已经工作。
本次将 mobpush_plugin 1.3.2 适配为 1.3.2-ohos.1,保留 Dart API 和原始通道名称,补齐 OHOS HAR、插件注册、示例工程和测试。适配过程中还定位并修复了一个真实的启动崩溃:EventChannel 原生处理器和 Dart listen 的注册顺序不一致,会在 Flutter 引擎启动阶段触发 SIGILL 或 SIGSEGV。

| 项目 | 内容 |
|---|---|
| 仓库名称 | mobpush_plugin |
| AtomGit 目标地址 | https://atomgit.com/oh-flutter/mobpush_plugin |
| 当前状态 | AtomGit 目标仓库尚未创建,本地代码已适配并验证,尚未推送 |
| 原始包名 | mobpush_plugin |
| 原始版本 | 1.3.2 |
| OHOS 版本 | 1.3.2-ohos.1 |
| 原始项目 | MobClub/MobPush-for-Flutter |
| OHOS 原生入口 | MobpushPlugin |
| MethodChannel | com.mob.mobpush.methodChannel |
| EventChannel | com.mob.mobpush.reciever |
| 示例包名 | com.mob.mobpush.example |
| 构建产物 | entry-default-unsigned.hap |
AtomGit 当前搜索 mobpush_plugin 的项目结果为 0。SSH 推向目标地址时服务端返回“项目不存在”,因此文章和 README 都明确标记“尚未创建”,已提交代码清单也不会提前加入它。仓库真正创建后,再把本地 main 推送过去。
二、先判断能适配什么,不能适配什么
原库的 Android 实现直接依赖:
com.mob.pushsdk.MobPush
com.mob.pushsdk.MobPushReceiver
com.mob.pushsdk.MobPushLocalNotification
MobSDK / MobCommons / MobTools / MobGuard
这些依赖负责远程推送、registration id、厂商通道、别名标签、本地通知和隐私上报。OHOS 侧没有把 Android AAR 直接搬过去的办法,也没有可验证的 MobPush OHOS SDK。因此本次适配目标分为两层:
- 可完成的适配:插件发现、HAR 生命周期、MethodChannel、EventChannel 注册顺序、平台版本查询、事件流错误边界、示例启动。
- 暂不伪造的推送能力:远程注册、消息发送、别名标签、手机号、本地通知、隐私、角标、静音时段和厂商配置。
最终能力边界如下:
| Dart 能力 | OHOS 状态 | 返回结果 |
|---|---|---|
getPlatformVersion | 可用 | OHOS |
getSDKVersion | 可用 | mobpush_plugin OHOS adapter 1.3.2-ohos.1 |
addPushReceiver | 通道可注册,事件源不可用 | Dart onError 收到 mobpush_unsupported |
removePushReceiver | 可用 | 清理本地事件处理器 |
stopPush / restartPush / isPushStopped | 不可用 | mobpush_unsupported |
getRegistrationId | 不可用 | mobpush_unsupported |
setAlias / getAlias / deleteAlias | 不可用 | mobpush_unsupported |
addTags / getTags / deleteTags / cleanTags | 不可用 | mobpush_unsupported |
bindPhoneNum / send | 不可用 | mobpush_unsupported |
| 本地通知、隐私、角标、iOS 配置 | 不可用 | mobpush_unsupported |
这里没有维护一个“OHOS 推送已停止/已启动”的本地布尔值。那样的实现虽然能返回 true,但它描述的不是真实 MobPush 状态,反而会让业务侧误判,所以最终实现统一返回错误码。
三、原库的通道契约
Dart 入口只有两条静态通道:
class MobpushPlugin {
static const MethodChannel _channel =
MethodChannel('com.mob.mobpush.methodChannel');
static EventChannel _channelReciever =
const EventChannel('com.mob.mobpush.reciever');
}
拼写 reciever 是原库已有契约。适配时不能顺手改成 receiver,否则 Android/iOS 已有调用和 OHOS 实现会分裂。
调用关系可以概括为:
Dart MobpushPlugin
|
+-- MethodChannel
| com.mob.mobpush.methodChannel
| |
| +-- 版本查询
| +-- 推送生命周期
| +-- 注册 id
| +-- 别名与标签
| +-- 测试推送与本地通知
| +-- 隐私、角标、静音、iOS/Android 配置
|
+-- EventChannel
com.mob.mobpush.reciever
|
+-- Dart receiveBroadcastStream().listen()
+-- 原生 onListen / onCancel
+-- MobPush 消息事件(需要真实 SDK)
适配的第一条规则是保留通道字符串,第二条规则是没有事件源时给出明确错误,而不是让消息凭空出现。
四、让 Flutter OHOS 识别插件
根 pubspec.yaml 增加 OHOS 平台声明:
version: 1.3.2-ohos.1
flutter:
plugin:
platforms:
android:
package: com.mob.flutter.mobpush
pluginClass: MobpushPlugin
ios:
pluginClass: MobpushPlugin
ohos:
pluginClass: MobpushPlugin
OHOS 模块使用普通 HAR:
{
"name": "mobpush_plugin",
"version": "1.3.2-ohos.1",
"main": "index.ets",
"license": "MIT",
"dependencies": {}
}
ohos/index.ets 只负责导出 ArkTS 插件类:
import MobpushPlugin
from './src/main/ets/components/plugin/MobpushPlugin';
export default MobpushPlugin;
Flutter 工具根据 pubspec.yaml 的 ohos 配置生成 GeneratedPluginRegistrant.ets:
import MobpushPlugin from 'mobpush_plugin';
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine) {
flutterEngine.getPlugins()?.add(new MobpushPlugin());
}
}
示例 EntryAbility 保持标准调用:
export default class EntryAbility extends FlutterAbility {
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine);
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
这条链路缺少任何一层,都可能出现 Dart 通道存在但原生 handler 没有注册,或者 HAP 构建成功但启动时找不到插件类。
五、ArkTS 插件生命周期
插件类实现三个接口:
export default class MobpushPlugin
implements FlutterPlugin, MethodCallHandler, StreamHandler {
private methodChannel: MethodChannel | null = null;
private eventChannel: EventChannel | null = null;
private receiverRegistered = false;
}
引擎附着时创建两条通道,但只立即安装 MethodChannel 处理器:
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.methodChannel = new MethodChannel(
binding.getBinaryMessenger(),
METHOD_CHANNEL,
);
this.eventChannel = new EventChannel(
binding.getBinaryMessenger(),
EVENT_CHANNEL,
);
this.methodChannel.setMethodCallHandler(this);
}
引擎分离时清理 handler 和状态:
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.methodChannel !== null) {
this.methodChannel.setMethodCallHandler(null);
this.methodChannel = null;
}
this.unregisterReceiver();
this.eventChannel = null;
}
receiverRegistered 用来避免重复安装 EventChannel 处理器。重复调用 addPushReceiver 时,第二次调用只返回成功,不会覆盖第一次的 handler。
六、方法分发和 unsupported 结果
原生方法分发保持原 Dart 方法名:
onMethodCall(call: MethodCall, result: MethodResult): void {
switch (call.method) {
case 'getPlatformVersion':
result.success('OHOS');
return;
case 'getSDKVersion':
result.success('mobpush_plugin OHOS adapter 1.3.2-ohos.1');
return;
case 'addPushReceiver':
this.registerReceiver(result);
return;
case 'removePushReceiver':
this.unregisterReceiver();
result.success(true);
return;
case 'getRegistrationId':
case 'stopPush':
case 'restartPush':
case 'isPushStopped':
this.respondUnsupported(result);
return;
default:
this.respondUnsupported(result);
return;
}
}
稳定错误由一个函数统一产生:
private respondUnsupported(result: MethodResult): void {
result.error(UNSUPPORTED, UNSUPPORTED_MESSAGE, null);
}
错误常量:
const UNSUPPORTED = 'mobpush_unsupported';
const UNSUPPORTED_MESSAGE =
'MobPush SDK is not available on OpenHarmony; ' +
'this method requires the Android or iOS MobPush implementation.';
对于返回 Map 的原库方法,OHOS 兼容层返回统一结构:
{
'res': 'failed',
'error': 'MobPush SDK is not available on OpenHarmony...',
'errorCode': 'mobpush_unsupported',
}
这样调用方既能保持原来的 Map 解析逻辑,也能稳定判断失败原因。
七、启动崩溃:故障现象
最初 HAP 构建、安装和 aa start 都返回成功,但应用启动后几十到几百毫秒内退出。设备故障文件位于:
/data/log/faultlog/faultlogger/cppcrash-com.mob.mobpush.example-*.log
早期堆栈:
Reason:Signal:SIGILL(ILL_ILLOPC)
#00 pc ... /data/storage/el1/bundle/libs/arm64/libflutter.so
#01 pc ... /system/lib/ld-musl-aarch64.so.1
#02 pc ... /system/lib/ld-musl-aarch64.so.1(dlopen_impl+1160)
#03 pc ... /system/lib64/platformsdk/libace_napi.z.so
#07 pc ... ArkNativeEngine::RequireNapi
另一次故障进入:
FlutterNapi
DartExecutor.executeDartEntrypoint
FlutterEngineGroup.createAndRunEngine
FlutterAbilityAndEntryDelegate.setupFlutterEngine
FlutterAbility.onCreate
仅看 libflutter.so 崩溃很容易误判为引擎损坏。但同一个引擎、同一个 libflutter.so SHA-256 在可运行的其他示例中正常,说明必须继续做应用层定界。
八、启动崩溃:单变量实验
本次没有同时修改多个文件,而是逐项建立实验矩阵:
| 实验 | 唯一变化 | 结果 | 结论 |
|---|---|---|---|
| A | EntryAbility 暂不调用插件注册 | 进程稳定,出现 onFirstFrame | Flutter/HAP 模板正常 |
| B | 恢复插件注册 | 生成新的 cppcrash | 崩溃进入插件注册链 |
| C | 保留 MethodChannel,移除 EventChannel 注册 | 进程稳定 | MethodChannel 正常 |
| D | 创建 EventChannel 对象,但不调用 setStreamHandler | 进程稳定 | EventChannel 导入和构造正常 |
| E | 启动后延迟 1 秒再调用 setStreamHandler | 进程稳定,但 Dart listen 已经被空回复 | 延迟只是规避崩溃 |
| F | Dart 先调用原生 addPushReceiver,再监听事件流 | 进程稳定,顺序正确 | 最终修复 |
实验 A 的关键日志:
Adding plugin: MobpushPlugin // 未出现
called onFirstFrame isPreLoad:0 // 正常出现
恢复注册后的实验 B 立即生成新故障文件,因此可以排除“模拟器偶发”。
实验 D 和 E 进一步把问题缩小到:
EventChannel 对象创建:正常
EventChannel.setStreamHandler:触发时序问题
实验 E 还给出了不能采用延时方案的直接证据:
Received message from Dart over channel 'com.mob.mobpush.reciever'
No registered handler for message.
Responding to Dart with empty reply message.
如果只把 handler 延后,Dart 的 listen 会先到达,事件通道虽然不崩溃,但实际上没有正确建立。
九、最终修复:由 Dart 显式触发原生注册
Dart 的 addPushReceiver 在 OHOS 上先调用 MethodChannel:
static Future<void> addPushReceiver(
EventHandler onEvent,
EventHandler onError,
) async {
if (defaultTargetPlatform == TargetPlatform.ohos) {
await _channel.invokeMethod('addPushReceiver');
}
_channelReciever
.receiveBroadcastStream()
.listen(onEvent, onError: onError);
}
Android/iOS 不进入 OHOS 分支,继续使用原库事件流行为。使用 defaultTargetPlatform == TargetPlatform.ohos 的原因是它在 Flutter OH SDK 和主机单元测试中都可用,不需要依赖不存在于标准 Dart IO 中的 Platform.isOhos。
原生收到 addPushReceiver 后才安装 EventChannel 处理器:
case 'addPushReceiver':
if (this.eventChannel !== null && !this.receiverRegistered) {
this.eventChannel.setStreamHandler(this);
this.receiverRegistered = true;
}
result.success(true);
return;
随后 Dart 才执行 receiveBroadcastStream().listen(),所以最终时序是:
Dart addPushReceiver
-> MethodChannel addPushReceiver
-> 原生 setStreamHandler
-> MethodChannel 返回
-> Dart EventChannel listen
-> 原生 onListen
-> Dart onError(mobpush_unsupported)
这个设计同时满足两点:EventChannel 真正完成注册;没有 MobPush OHOS 事件源时,调用方马上得到明确错误。
onListen 不保存伪造事件源,而是主动报告边界:
onListen(args: Any, events: EventSink): void {
events.error(UNSUPPORTED, UNSUPPORTED_MESSAGE, null);
}
最终设备日志:
Setting handler for channel 'com.mob.mobpush.reciever'
Received message from Dart over channel 'com.mob.mobpush.reciever'
onError:PlatformException(mobpush_unsupported, MobPush SDK is not available...)
called onFirstFrame
顺序、错误内容和首帧结果都符合预期。
Dart 侧还修复了 stopPush 和 restartPush 不等待通道返回的问题:
static Future<void> stopPush() async {
if (defaultTargetPlatform == TargetPlatform.ohos) {
await _channel.invokeMethod('stopPush');
return;
}
_channel.invokeMethod('stopPush');
}
最终实现删除了这个本地状态,三个方法全部返回:
case 'stopPush':
case 'restartPush':
case 'isPushStopped':
this.respondUnsupported(result);
return;
Dart 侧还修复了 stopPush 和 restartPush 不等待通道返回的问题:
static Future<void> stopPush() async {
if (defaultTargetPlatform == TargetPlatform.ohos) {
await _channel.invokeMethod('stopPush');
return;
}
_channel.invokeMethod('stopPush');
}

这样 OHOS 调用方可以正常 catch (PlatformException),错误不会变成无人监听的异步异常。Android/iOS 原行为保持不变。
十一、示例工程兼容处理
示例 OHOS 工程从可运行模板复制后做了以下处理:
- BundleName 改为
com.mob.mobpush.example。 - App 名称改为
MobPush 示例。 GeneratedPluginRegistrant导入mobpush_plugin并注册MobpushPlugin。- 清理模板中残留的其他插件导入和包名。
- Dart SDK 上限放宽到
<4.0.0,适配当前 Dart 3.12 工具链。 cupertino_icons升级为^2.0.0。- 删除示例中没有使用的
dart:ffi。 - 将旧
AppBar.textTheme/headline6写法替换为titleTextStyle。 - 修复未使用变量和旧 Flutter API 带来的分析错误。
示例启动页最终显示:
MobPushPlugin Demo
App内推送
通知
定时推送
本地通知
其他API接口
SDK Version: mobpush_plugin OHOS adapter 1.3.2-ohos.1
RegistrationId: Unknown
RegistrationId: Unknown 不是遗漏,而是没有真实 MobPush OHOS SDK 时的诚实结果。
十二、回归测试
测试文件不再是模板计数器,而是围绕真实通道契约:
test('getPlatformVersion reports the OHOS adapter channel result', () async {
expect(await MobpushPlugin.getPlatformVersion, 'OHOS');
});
test('addPushReceiver registers the OHOS native handler', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.ohos;
await MobpushPlugin.addPushReceiver(
(dynamic event) {},
(dynamic error) {},
);
expect(calls, contains('addPushReceiver'));
});
test('OHOS push lifecycle methods preserve unsupported errors', () async {
expect(MobpushPlugin.stopPush(), throwsA(_isMobPushUnsupported));
expect(MobpushPlugin.restartPush(), throwsA(_isMobPushUnsupported));
expect(MobpushPlugin.isPushStopped(), throwsA(_isMobPushUnsupported));
});
addPushReceiver 测试在修复前按预期失败:
Expected: contains 'addPushReceiver'
Actual: []
实现后测试输出:
00:00 +0: getPlatformVersion reports the OHOS adapter channel result
00:00 +1: getRegistrationId preserves the unsupported capability error
00:00 +2: addPushReceiver registers the OHOS native handler before listening
00:00 +3: OHOS push lifecycle methods preserve the unsupported error
00:00 +4: All tests passed!
真实截图:
图 1:OpenHarmony 模拟器上的 mobpush_plugin 示例首屏,插件版本明确显示为 1.3.2-ohos.1。
本次执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter analyze
flutter build hap --debug --no-codesign
结果:
| 检查项 | 结果 |
|---|---|
根包 flutter analyze | No issues found! |
flutter test | 4 个测试全部通过 |
示例 flutter analyze | No issues found! |
| HAP 构建 | 通过 |
| HAP 产物 | entry-default-unsigned.hap |
| 模拟器安装 | 通过 |
| 模拟器启动 | 通过,进程稳定存活 |
| 最新崩溃文件 | 时间早于最终启动,没有新增 |
设备信息:
| 项目 | 实测值 |
|---|---|
| hdc 设备 | 127.0.0.1:5555 |
| 系统 | OHOS OpenHarmony-7.0.0.105 |
| API | 26 |
| 模拟器 | emulator 7.0.0.107 |
| 包名 | com.mob.mobpush.example |
| Ability | EntryAbility |
安装命令:
hdc -t 127.0.0.1:5555 install \
example/build/ohos/hap/entry-default-unsigned.hap
hdc -t 127.0.0.1:5555 shell aa start \
-a EntryAbility \
-b com.mob.mobpush.example
真实截图:

图 1:OpenHarmony 模拟器上的 mobpush_plugin 示例首屏,插件版本明确显示为 1.3.2-ohos.1。
关键日志已经保存到:
mobpush_plugin_ohos/photos/logs/mobpush_receiver_sequence.log
最终启动顺序:
00:29:35.991 Adding plugin: MobpushPlugin
00:29:35.991 Setting handler for channel 'com.mob.mobpush.methodChannel'
00:29:36.330 Dart 调用 MethodChannel
00:29:36.330 Setting handler for channel 'com.mob.mobpush.reciever'
00:29:36.364 Dart 调用 EventChannel
00:29:36.365 onError: PlatformException(mobpush_unsupported, ...)
00:29:36.368 onFirstFrame
最终结论:
- Flutter 插件注册正常。
- MethodChannel 和 EventChannel 时序正确。
- Dart
onError能看到明确的 unsupported 业务错误。 - 示例首帧和页面正常。
- 没有新增 cppcrash。
十四、目录和关键文件
mobpush_plugin_ohos/
├── lib/
│ ├── mobpush_plugin.dart
│ ├── mobpush_local_notification.dart
│ └── mobpush_notify_message.dart
├── ohos/
│ ├── index.ets
│ ├── oh-package.json5
│ ├── build-profile.json5
│ └── src/main/ets/components/plugin/MobpushPlugin.ets
├── example/
│ ├── lib/main.dart
│ └── ohos/
├── test/
│ └── mobpush_plugin_test.dart
├── photos/
│ ├── mobpush_plugin_final.jpeg
│ └── logs/mobpush_receiver_sequence.log
├── README.md
├── verification.md
└── CHANGELOG.md
其中最值得关注的三个文件:
lib/mobpush_plugin.dart:增加 OHOS 平台分支和显式 EventChannel 注册时序。ohos/src/main/ets/components/plugin/MobpushPlugin.ets:通道实现和 unsupported 边界。test/mobpush_plugin_test.dart:防止注册时序和错误码回归。
十五、后续接入真实 MobPush OHOS SDK
当 MobPush 提供 OHOS SDK 后,可以在不改变 Dart API 的前提下替换能力实现:
MobpushPlugin
|
+-- registerReceiver
| -> MobPush OHOS SDK 注册回调
| -> EventSink.success(json)
|
+-- getRegistrationId
| -> MobPush OHOS SDK registration id
|
+-- setAlias / getAlias / tags
| -> MobPush OHOS SDK 异步回调
|
+-- send
-> MobPush OHOS SDK 测试消息
接入时需要同时补:
- AppKey、AppSecret 或 AGC Push Kit 配置。
- 通知权限、通知渠道和后台运行限制。
- 厂商推送通道与系统服务声明。
- 服务端发送、回执和去重。
- 真机前台、后台、锁屏和通知点击测试。
- 从
mobpush_unsupported到真实结果的回归测试。
欢迎加入 CPF-Flutter 鸿蒙社区,一起交流 Flutter 三方库适配、问题复现和真机验证经验:
https://atomgit.com/CPF-Flutter
更多推荐



所有评论(0)