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
MethodChannelcom.mob.mobpush.methodChannel
EventChannelcom.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。因此本次适配目标分为两层:

  1. 可完成的适配:插件发现、HAR 生命周期、MethodChannel、EventChannel 注册顺序、平台版本查询、事件流错误边界、示例启动。
  2. 暂不伪造的推送能力:远程注册、消息发送、别名标签、手机号、本地通知、隐私、角标、静音时段和厂商配置。

最终能力边界如下:

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 在可运行的其他示例中正常,说明必须继续做应用层定界。

八、启动崩溃:单变量实验

本次没有同时修改多个文件,而是逐项建立实验矩阵:

实验唯一变化结果结论
AEntryAbility 暂不调用插件注册进程稳定,出现 onFirstFrameFlutter/HAP 模板正常
B恢复插件注册生成新的 cppcrash崩溃进入插件注册链
C保留 MethodChannel,移除 EventChannel 注册进程稳定MethodChannel 正常
D创建 EventChannel 对象,但不调用 setStreamHandler进程稳定EventChannel 导入和构造正常
E启动后延迟 1 秒再调用 setStreamHandler进程稳定,但 Dart listen 已经被空回复延迟只是规避崩溃
FDart 先调用原生 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');
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a1455506ee8a405fb76cef9d825e2b3d.png)
最初 OHOS 实现曾用 `pushStopped` 保存一个本地布尔值,使 `stopPush` 返回 `true`、`restartPush` 返回 `true`、`isPushStopped` 返回这个布尔值。虽然代码能运行,但它描述的不是 MobPush SDK 的真实状态。

最终实现删除了这个本地状态,三个方法全部返回:

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 工程从可运行模板复制后做了以下处理:

  1. BundleName 改为 com.mob.mobpush.example。
  2. App 名称改为 MobPush 示例。
  3. GeneratedPluginRegistrant 导入 mobpush_plugin 并注册 MobpushPlugin。
  4. 清理模板中残留的其他插件导入和包名。
  5. Dart SDK 上限放宽到 <4.0.0,适配当前 Dart 3.12 工具链。
  6. cupertino_icons 升级为 ^2.0.0。
  7. 删除示例中没有使用的 dart:ffi。
  8. 将旧 AppBar.textTheme/headline6 写法替换为 titleTextStyle。
  9. 修复未使用变量和旧 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!

真实截图:

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5a3eb86e03fa47fcba26195ed8673d6e.png)

图 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 analyzeNo issues found!
flutter test4 个测试全部通过
示例 flutter analyzeNo issues found!
HAP 构建通过
HAP 产物entry-default-unsigned.hap
模拟器安装通过
模拟器启动通过,进程稳定存活
最新崩溃文件时间早于最终启动,没有新增

设备信息:

项目实测值
hdc 设备127.0.0.1:5555
系统OHOS OpenHarmony-7.0.0.105
API26
模拟器emulator 7.0.0.107
包名com.mob.mobpush.example
AbilityEntryAbility

安装命令:

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

其中最值得关注的三个文件:

  1. lib/mobpush_plugin.dart:增加 OHOS 平台分支和显式 EventChannel 注册时序。
  2. ohos/src/main/ets/components/plugin/MobpushPlugin.ets:通道实现和 unsupported 边界。
  3. 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 测试消息

接入时需要同时补:

  1. AppKey、AppSecret 或 AGC Push Kit 配置。
  2. 通知权限、通知渠道和后台运行限制。
  3. 厂商推送通道与系统服务声明。
  4. 服务端发送、回执和去重。
  5. 真机前台、后台、锁屏和通知点击测试。
  6. 从 mobpush_unsupported 到真实结果的回归测试。

欢迎加入 CPF-Flutter 鸿蒙社区,一起交流 Flutter 三方库适配、问题复现和真机验证经验:

https://atomgit.com/CPF-Flutter

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐