Flutter 社区地址: https://atomgit.com/CPF-Flutter/flutter_flutter
适配后仓库地址:https://atomgit.com/oh-flutter/sms_sender_plus

本文记录了将开源 Flutter 三方库 sms_sender_plus 适配到 OpenHarmony / HarmonyOS 平台的完整过程,
包含适配思路、代码改动对照、关键决策和踩坑复盘。


一、背景

1.1 三方库简介

sms_sender_plus 是一个 Flutter 社区广泛使用的短信发送插件,提供以下能力:

  • Android SIM 卡选择发送 — 通过 TelephonyManager / SmsManager 指定订阅(subscription)发送短信,支持双卡设备选卡
  • 发送状态回执 — 通过系统广播 SMS_SENT / SMS_DELIVERED 上报发送与送达状态,Dart 侧以事件流订阅
  • 批量群发sendTextMessages() 一次向多个收件人发送,支持长短信自动分片(divideMessage
  • iOS 系统短信编辑器 — 基于 MFMessageComposeViewController 唤起系统 composer 发送
  • 能力与权限探测isSmsAvailable()checkSmsPermission()checkPhoneStatePermission() 等统一能力/权限查询接口

该三方库最初支持 Android / iOS 两个平台,本次任务将其适配到 OpenHarmony / HarmonyOS 平台。

项目地址https://atomgit.com/oh-flutter/sms_sender_plus

1.2 适配目标

维度要求
功能一致性全部 8 个方法 + 状态事件流的接口签名与 Android/iOS 完全对齐;在鸿蒙系统权限受限的前提下执行"能力受限"语义,不出现崩溃或悬挂调用
Dart 层零改动lib/ 下 Dart 代码零修改,仅通过 pubspec.yaml 新增 ohos 平台注册
性能方法调用与事件流走标准 MethodChannel / EventChannel,不引入额外桥接层
工程规范提供完整 ohos/ 插件实现与 example/ohos 示例工程,真机可签名、可编译、可运行;文档、TAG、元信息齐备

二、适配路线图

整个适配分为 4 个阶段:

第 1 阶段:项目初始化 ── 用 flutter create 生成 ohos 插件模板骨架(index.ets / HAR 配置)
第 2 阶段:原生实现   ── 将 Android Kotlin 实现逐一翻译为 ArkTS(方法通道 + 事件通道)
第 3 阶段:三方库注册 ── pubspec.yaml 增加 ohos 平台配置,GeneratedPluginRegistrant 自动注册
第 4 阶段:示例验证   ── 生成 example/ohos 工程,签名后真机编译运行验证

三、逐步适配过程

第 1 阶段:项目初始化

使用 Flutter 命令行生成 OHOS 插件模板:

flutter create . --template=plugin --platforms=ohos

该命令自动生成 ohos/ 目录的标准模板结构,包含必要的构建配置和入口文件。

目录结构:

ohos/
├── index.ets                              # 模块入口,导出插件类
├── oh-package.json5                       # HAR 包配置
├── build-profile.json5                    # 构建配置
├── hvigorfile.ts                          # hvigor 构建入口(harTasks)
└── src/main/
    ├── module.json5                       # HAR 模块配置
    └── ets/components/plugin/
        └── SmsSenderPlusPlugin.ets        # 原生插件实现(核心)

关键配置文件:

index.ets(入口导出文件)

import SmsSenderPlusPlugin from './src/main/ets/components/plugin/SmsSenderPlusPlugin';
export default SmsSenderPlusPlugin;

oh-package.json5(包配置)

{
  "name": "sms_sender_plus",
  "version": "1.0.0",
  "description": "HarmonyOS (ohos) platform implementation of sms_sender_plus: a Flutter plugin for SMS capability detection, permission queries, SIM card queries and SMS sending, with capability-limited semantics on OpenHarmony.",
  "main": "index.ets",
  "author": "坚果 <jianguo@nutpi.net>",
  "license": "Apache-2.0",
  "dependencies": {}
}

@ohos/flutter_ohos 由 Flutter 引擎在构建时自动链接(oh_modules/@ohos/flutter_ohos 指向 flutter 引擎缓存的 flutter_embedding_debug.har),无需在 dependencies 中显式声明。

module.json5(HAR 模块配置)

{
  "module": {
    "name": "sms_sender_plus",
    "type": "har",
    "deviceTypes": ["default", "tablet"]
  }
}

第 2 阶段:原生实现(核心)

这是适配的核心工作。将 Android 平台的 Kotlin 实现逐一翻译为 ArkTS。

2.1 整体架构对比

插件按通信机制分两类。sms_sender_plus方法调用型 + 事件流型混合

  • MethodChannel('sms_sender_plus') — Dart 主动调用原生(发送 / SIM / 能力 / 权限)
  • EventChannel('sms_sender_plus/status') — 原生主动推送发送/送达状态事件(Android 用系统广播)

方法调用型(MethodChannel + MethodCallHandler):

 Android (Kotlin)                          OHOS (ArkTS)
 ────────────────────                      ────────────────────
 class SmsSenderPlusPlugin                 class SmsSenderPlusPlugin
   implements FlutterPlugin,                 implements FlutterPlugin,
              MethodCallHandler                            MethodCallHandler
   import io.flutter.plugin.common.          import { FlutterPlugin,
              MethodChannel                                FlutterPluginBinding,
   import android.telephony...                              MethodCall,
                                                            MethodCallHandler,
                                                            MethodChannel,
                                                            MethodResult
                                                          } from '@ohos/flutter_ohos'

事件流型(EventChannel + StreamHandler):

 Android (Kotlin)                          OHOS (ArkTS)
 ────────────────────                      ────────────────────
 class SmsSenderPlusPlugin                 class SmsSenderPlusPlugin
   implements FlutterPlugin,                 implements FlutterPlugin,
              EventChannel.StreamHandler                  StreamHandler
   BroadcastReceiver (SMS_SENT /            (鸿蒙无第三方短信 API,注册空 handler,
    SMS_DELIVERED) → eventSink              不产生任何事件,保持流打开)
2.2 通道注册
通道AndroidOHOS
方法通道MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "sms_sender_plus")new MethodChannel(binding.getBinaryMessenger(), "sms_sender_plus")
事件通道EventChannel(binding.binaryMessenger, "sms_sender_plus/status").setStreamHandler(this)new EventChannel(binding.getBinaryMessenger(), "sms_sender_plus/status").setStreamHandler(this)

差异:OHOS 使用 getBinaryMessenger() 替代 Android 的 getFlutterEngine().getDartExecutor(),接口更简洁;EventChannel 构造参数为 (messenger, name, codec?)codec 默认 StandardMethodCodec.INSTANCE,与 Dart 端默认一致,无需显式传入。

2.2.1 事件流型插件的 StreamHandler 生命周期(核心差异)

OHOS 侧实现 StreamHandler 接口的 onListen / onCancel,并持有 EventSink 用于推送事件。

时机AndroidOHOS
插件绑定引擎onAttachedToEngine 创建 EventChannelonAttachedToEngine 创建 EventChannel
Dart 开始订阅onListen → 注册 BroadcastReceiveronListen → 注册空 handler(鸿蒙无短信事件源)
事件到达广播回调中 eventSink?.success(payload)不产生事件(受限语义)
Dart 取消订阅onCancel → 注销广播onCancel → 无可释放资源
插件解绑引擎onDetachedFromEngine → 注销广播 + setStreamHandler(null)onDetachedFromEnginesetMethodCallHandler(null)
2.3 原生方法实现对照
方法Android 实现OHOS 实现返回值
isSmsAvailableTelephonyManager.isSmsCapablefalsebool
checkSmsPermissionContextCompat.checkSelfPermission(SEND_SMS)falsebool
requestSmsPermissionActivityCompat.requestPermissions + 结果监听false(不弹窗)bool
checkPhoneStatePermissionContextCompat.checkSelfPermission(READ_PHONE_STATE)falsebool
requestPhoneStatePermissionActivityCompat.requestPermissions + 结果监听false(不弹窗)bool
getSimCardsSimCardResolver.getActiveSimCards()SubscriptionManager + TelephonyManager恒空列表List<SimCard>
sendTextMessageSmsSender.sendSingle()SmsManager.sendTextMessage / 分片发送 + PendingIntent 回执)恒抛 smsUnavailable 错误SmsSendResult
sendTextMessagesSmsSender.sendBatch()(逐收件人发送)恒抛 smsUnavailable 错误SmsBatchSendResult

OHOS 插件核心实现(ArkTS):

export default class SmsSenderPlusPlugin implements FlutterPlugin, MethodCallHandler, StreamHandler {
  private channel: MethodChannel | null = null;
  private eventChannel: EventChannel | null = null;

  getUniqueClassName(): string {
    return 'SmsSenderPlusPlugin'
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), METHOD_CHANNEL_NAME);
    this.channel.setMethodCallHandler(this);
    this.eventChannel = new EventChannel(binding.getBinaryMessenger(), EVENT_CHANNEL_NAME);
    this.eventChannel.setStreamHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
    this.eventChannel = null;
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    try {
      if (call.method == 'sendTextMessage' || call.method == 'sendTextMessages') {
        result.error('smsUnavailable', SMS_UNAVAILABLE_MESSAGE, null);
      } else if (call.method == 'getSimCards') {
        result.success([]);
      } else if (call.method == 'isSmsAvailable') {
        result.success(false);
      } else if (call.method == 'checkSmsPermission' || call.method == 'requestSmsPermission' ||
        call.method == 'checkPhoneStatePermission' || call.method == 'requestPhoneStatePermission') {
        result.success(false);
      } else if (call.method == 'getPlatformVersion') {
        result.success('OpenHarmony ^ ^');
      } else {
        result.notImplemented();
      }
    } catch (err) {
      result.error('sendFailed', `SMS operation failed: ${(err as Error).message}`, null);
    }
  }
}
2.4 实现差异详解

适配中最关键的问题是:HarmonyOS 未向第三方(normal APL)应用开放短信能力ohos.permission.SEND_MESSAGES 是 system_basic 级别权限,仅系统应用可申请;订阅(SIM 卡)信息读取也没有公开 API。因此在"照抄 Android 实现"与"受限语义"之间需要决策:

方案优点缺点
受限语义:发送返回 smsUnavailable、权限恒 false、SIM 恒空列表接口签名与 Dart 契约完全对齐;不崩溃、不悬挂;与 iOS 的"能力受限"分支行为一致;example/文档可给出确定性行为鸿蒙端无法真实发短信
尝试声明并申请 system_basic 权限理论上可发短信第三方应用安装时即被拒绝授予,requestPermissionsFromUser 不弹窗,纯属无效工作;还会误导用户"授权后即可用"

关键差异点:鸿蒙端所有"能力型"接口返回确定性结果(isSmsAvailable=false、权限 false、SIM 空列表、发送抛 smsUnavailable),与 README 中声明的受限语义自洽;example 层必须先用 isSmsAvailable() 检测能力再决定是否展示发送入口,不能依赖权限接口控制业务逻辑。


第 3 阶段:三方库注册

pubspec.yaml 中添加 OHOS 平台注册:

flutter:
  plugin:
    platforms:
      android:
        package: dev.parham.sms_sender_plus
        pluginClass: SmsSenderPlusPlugin
      ios:
        pluginClass: SmsSenderPlusPlugin
      ohos:                              # ← 新增
        pluginClass: SmsSenderPlusPlugin # ← 对应 index.ets 默认导出

Flutter 的 OHOS 引擎在构建时会读取 pubspec.yaml 中的 ohos 配置,自动加载 ohos/index.ets 中导出的插件类。注意 pluginClass 必须与实现类的 getUniqueClassName() 返回值大小写完全一致,否则真机启动报 Plugin not found


第 4 阶段:示例应用创建

example/ 目录下生成 OHOS 宿主工程(注意:在 example 目录执行,而非插件根目录):

cd example
flutter create . --platforms=ohos

生成的 example/ohos/ 包含签名配置、SDK 版本、测试模块与 Flutter 运行时资源:

example/ohos/
├── AppScope/app.json5                     # 应用配置(bundleName: dev.parham.sms_sender_plus_example)
├── build-profile.json5                    # 项目构建配置(signingConfigs、SDK 26)
├── entry/
│   ├── build-profile.json5                # entry 模块(default + ohosTest 双 target)
│   └── src/main/
│       ├── module.json5                   # entry 模块配置(EntryAbility + INTERNET 权限)
│       ├── ets/
│       │   ├── entryability/
│       │   │   └── EntryAbility.ets       # FlutterAbility + GeneratedPluginRegistrant
│       │   ├── pages/Index.ets            # FlutterPage 容器
│       │   └── plugins/GeneratedPluginRegistrant.ets  # 自动注册插件(无需手写)
│       └── resources/
│           └── rawfile/buildinfo.json5
│   └── src/ohosTest/                      # 测试目录(hypium)

说明GeneratedPluginRegistrant.ets 由 Flutter 工具根据 pubspec.yamlohos 配置自动生成,会 import SmsSenderPlusPlugin from 'sms_sender_plus' 并注册到 engine,无需手写。


第 5 阶段:构建验证(HAP)

用 Flutter 工具链构建 HAP,验证原生代码与配置无编译错误:

cd example
flutter build hap --debug

成功产出:build/ohos/hap/entry-default-signed.hap(签名配置使用 DevEco 自动签名 debugKey,SDK 26)。

常见构建错误(踩坑复盘见第八章):

  • example/ohos 未配置签名 → flutter run 真机安装失败,需先在 DevEco Studio 中为 example/ohos 配置自动签名;
  • GeneratedPluginRegistrant.ets 缺失 → 先 flutter pub get 再构建,Flutter 工具会自动生成。

四、完整代码对照

4.1 Android vs OHOS 完整实现对照
维度Android (Kotlin)OHOS (ArkTS)
语言KotlinArkTS (TypeScript 语法)
插件接口FlutterPlugin + MethodCallHandler + EventChannel.StreamHandler + ActivityAwareFlutterPlugin + MethodCallHandler + StreamHandler
通道创建MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), name)new MethodChannel(binding.getBinaryMessenger(), name)
短信发送SmsManager.sendTextMessage / sendMultipartTextMessage + PendingIntent 回执无第三方 API,返回 smsUnavailable 错误
SIM 读取SubscriptionManager.getActiveSubscriptionInfoList() + TelephonyManager.getSimOperator无第三方 API,返回空列表
权限检测ContextCompat.checkSelfPermissionfalse(system_basic 权限不可申请)
权限申请ActivityCompat.requestPermissions + onRequestPermissionsResult 回调false,不弹窗
能力检测TelephonyManager.isSmsCapablefalse
状态事件流BroadcastReceiverSMS_SENT / SMS_DELIVERED)→ eventSink.success注册空 handler,保持流打开但不产生事件
4.2 关键 ArkTS 语法差异
Android 语法ArkTS 语法备注
import io.flutter.plugin.common.MethodChannelimport { MethodChannel } from '@ohos/flutter_ohos'OHOS 使用模块化导入
override fun onMethodCall(call, result)onMethodCall(call: MethodCall, result: MethodResult): void方法签名一致,ArkTS 显式标注返回类型
result.success(mapOf(...)) / result.error(code, msg, null)result.success(...) / result.error('smsUnavailable', msg, null)语义一致,返回结构对齐
null 可空性channel: MethodChannel | null = nullArkTS 用联合类型显式表达可空
getFlutterEngine().getDartExecutor()binding.getBinaryMessenger()OHOS 通道构造更简洁
ActivityAware(activity 绑定)无对应场景鸿蒙端无需窗口/上下文,未实现 AbilityAware
getUniqueClassName()必须实现并返回与 pluginClass 一致的类名OHOS 插件注册的契约方法

五、关键决策说明

决策 1:保持通道名不变

Dart 层 MethodChannel('sms_sender_plus')EventChannel('sms_sender_plus/status') 已固定,OHOS 原生侧必须使用完全相同的通道名。通道名是 Dart 与原生之间的通信契约,改名会导致调用静默失败。

维护策略:通道名作为常量定义在插件文件顶部,与 Dart 端文档同步维护;任何一端改动必须同步另一端并跑通 example 回归。

决策 2:Dart 层零改动

Dart API 通过 MethodChannel.invokeMethod(...) 调用,方法名、参数结构、返回值模型(SmsSendResult / SimCard / SmsStatusEvent)全部保持不变。OHOS 适配零修改 lib/ 代码,仅新增 ohos/ 实现和 pubspec.yaml 平台注册。

维护策略:上游(GitHub 原库)Dart API 变更时,只需同步核对 OHOS 原生侧方法名与返回结构即可。

决策 3:受限语义(鸿蒙 system_basic 权限限制)

HarmonyOS 未向第三方(normal APL)应用开放短信发送、订阅信息读取与短信回执 API(ohos.permission.SEND_MESSAGES 为 system_basic 级别)。因此 OHOS 端执行受限语义:

  • sendTextMessage / sendTextMessages → 抛 smsUnavailable 错误
  • getSimCards → 空列表;isSmsAvailablefalse
  • 4 个权限接口 → 恒 false,且不发起任何授权请求(发起也无效)
  • statusEvents → 保持流打开但不产生事件

维护策略:若未来 HarmonyOS 向第三方开放短信 API(或应用以 system_basic 签名),只需替换受限分支为真实实现,Dart 契约不变。

决策 4:能力检测优先(example 层适配)

鸿蒙上权限接口恒 false 且不弹窗,example 若按 Android 的"查权限 → 申请权限"流程走,会反复出现误导性的 “SEND_SMS permission is required”。因此 example 启动时先调用 isSmsAvailable()

  • false(鸿蒙)→ 显示 “This device cannot send SMS” 提示横幅、禁用发送按钮、跳过 SIM 加载与权限流程;
  • true(Android/iOS)→ 行为与原 example 完全一致。

维护策略:example 的能力检测逻辑保持平台无关(只依赖插件公开 API),三端共用同一份 main.dart


六、测试与验证

测试环境
项目版本
Flutter3.41.10-ohos-1.0.0(channel [user-branch],git@gitcode.com:CPF-Flutter/flutter_flutter.git)
Dart随 Flutter 3.41.10-ohos(SDK 约束 >=3.0.0 <4.0.0)
HarmonyOS SDK26.0.0(API 26),compatibleSdkVersion: 5.1.0(18) / targetSdkVersion: 26.0.0
IDEDevEco Studio 26.0.0(DS-261.23567.138.36.2600821)
设备 ROMALN-AL00 7.0.0.105(SP6C00E105R4P3),OpenHarmony 7.0.0.105(API 26)

版本获取方式:

版本项获取方式
Flutter / Dartflutter --version
HarmonyOS SDK读取 example/ohos/build-profile.json5compatibleSdkVersion / targetSdkVersion
IDE/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" /Applications/DevEco-Studio.app/Contents/Info.plist
设备 ROMhdc shell param get const.product.software.version
验证要点
  1. 静态检查flutter analyze:No issues found(0 error / 0 warning)
  2. HAP 构建cd example && flutter build hap --debug:成功产出 build/ohos/hap/entry-default-signed.hap
  3. 插件注册 — 真机启动无 Plugin not found: SmsSenderPlusPlugin 错误;GeneratedPluginRegistrant.ets 正确导入并注册插件类
  4. 能力检测isSmsAvailable() 返回 false,example 显示 “This device cannot send SMS” 横幅且发送按钮禁用(截图见第七章)
  5. 发送接口sendTextMessage / sendTextMessages 返回 smsUnavailable 错误,Dart 层正确捕获为 SmsSenderPlusException
  6. SIM / 权限接口getSimCards() 返回空列表;4 个权限接口恒 false 且不弹窗
  7. 事件流statusEvents 订阅无异常,保持打开不产生事件

七、运行效果

适配完成后,真机运行 example 获取运行截图:

cd example
flutter run -d <device_id> --no-resident   # 真机部署(也可用 flutter screenshot)

sms_sender_plus 鸿蒙真机运行截图

上图左侧为 example 表单(收件人 / 批量 / 消息 / SIM 下拉 / 回执开关),底部 Events 区域展示能力检测后的行为:发送按钮被禁用、显示 “This device cannot send SMS or read SIM cards. HarmonyOS only exposes SMS capabilities to system apps.” 提示横幅,不再出现误导性的权限请求报错。


八、遗留问题与改进方向

踩坑复盘

适配中遇到的实际问题最有价值,复盘如下:

踩坑点现象 / 报错根因与解法
权限请求流程误导鸿蒙真机 example 反复显示 “SEND_SMS permission is required” / “READ_PHONE_STATE permission is required”ohos.permission.SEND_MESSAGES 与电话状态类权限为 system_basic 级别,第三方应用永远申请不到,权限接口恒 false 且不弹窗。解法:example 先调 isSmsAvailable() 做能力检测,false 时显示明确提示、禁用发送按钮、跳过权限流程(commit 20833a5
插件类名不一致风险真机启动 Plugin not found: XxxPluginpubspec.yamlpluginClass 与实现类 getUniqueClassName() 返回值必须大小写完全一致。解法:两者统一为 SmsSenderPlusPlugin
构建时 GeneratedPluginRegistrant.ets 缺失ohos/example/ohos 编译报找不到插件注册文件该文件由 Flutter 工具根据 pubspec.yamlohos 配置自动生成。解法:先 flutter pub get 再构建
oh-package.json5 模板占位符元信息为 “Please describe the basic information.”模板默认占位符。解法:替换为真实描述与作者信息(commit 884f0a8
孤儿 MainActivity.ktexample/android 下出现包名与 gradle namespace 不匹配、未被清单引用的 MainActivity.kt误生成文件。解法:核对 build.gradle.kts namespace 后删除孤儿文件(commit 18f1706
已知问题
  1. 鸿蒙端无法真实发送短信ohos.permission.SEND_MESSAGES 为 system_basic 级别权限,第三方(normal APL)应用无法申请,系统也未向第三方开放短信发送 API。插件按受限语义返回 smsUnavailable 错误。
  2. SIM 卡信息不可读 — HarmonyOS 未向第三方开放订阅(SIM 卡)信息读取 API,getSimCards() 恒返回空列表。
  3. 状态回执事件不可用 — 无第三方短信回执机制,statusEvents 恒为空流(保持打开但不产生事件)。
未来优化
  • 拉起系统短信编辑器 — 在鸿蒙端扩展一个新方法,通过 Want / Intent 拉起系统短信编辑页(预填收件人与内容),由用户在系统短信应用内完成发送,作为受限语义的可用替代方案;
  • 系统应用签名支持 — 若目标设备允许应用以 system_basic 及以上 APL 签名(如行业定制终端),可将受限分支替换为基于 @ohos.telephony.sms 的真实实现,Dart 契约不变;
  • example 增强 — 在能力不可用时展示"使用系统短信"引导按钮,提升示例完整度。

九、总结

将一个 Flutter 三方库适配到 OHOS 平台,核心路径可以概括为 三步走

1. 找对应 ── 找到 OHOS 对每个 Android 原生 API 的等价实现(或确认无等价物)
2. 保契约 ── 确保方法通道名、方法名、返回值结构完全一致(channel / method / map)
3. 补缺口 ── 对于 OHOS 不提供的 API,用合理方案弥补(受限语义、能力检测、UI 引导)

对于 sms_sender_plus 三方库,适配涉及 ohos/ 插件实现(index.ets、SmsSenderPlusPlugin.ets、HAR 配置)、example/ohos 示例工程、两份 README.OpenHarmony 文档与 pubspec.yamlohos 平台注册。Dart 层和其他平台的代码完全不受影响——这正是 Flutter 跨平台三方库生态的魅力所在。

本次适配的核心经验:当目标平台对某类能力有硬性系统限制时(如鸿蒙短信权限为 system_basic),不要硬造无效调用,而是给出确定性的"受限语义",并用能力检测接口(isSmsAvailable())让上层 UI 与业务逻辑做出正确决策。接口签名对齐 + 语义自洽 + 文档透明,就是一次合格的平台适配。


参考文档

Logo

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

更多推荐