给鸿蒙 App 增加唤起系统邮件发送能力 —— flutter_email_sender 的鸿蒙使用指南

本文配套仓库:https://atomgit.com/oh-flutter/flutter_email_sender(TAG:10.0.1-ohos-1.0.0-beta.1,分支:feat/ohos_flutter_email_sender_10.0.1)。本文讲怎么用:引入依赖、调接口、跑通发送与附件流程;适配是怎么一步步做出来的。

一、最终运行效果

先看跑起来是什么样子。下面是示例工程的实测截图,主验证环境为 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.1.120 / API 24),Flutter 3.41.10-ohos-1.0.1。

应用启动后是标准的邮件编辑表单:收件人、主题、正文三个输入框,右上角发送按钮,底部附件区,界面上方还会按当前平台能力动态隐藏不支持的功能开关:

在这里插入图片描述

图一:example 启动后的主界面

点击发送按钮,插件经 startAbilityByType('mail') 拉起系统邮件应用选择面板,面板按设备上已安装并声明邮件能力的应用列出可选方式(真机实测列出了「电子邮件」):

在这里插入图片描述

图二:点击发送后系统弹出邮件应用选择面板

选择「电子邮件」,进入写邮件界面,收件人、主题、正文已按 Email 对象自动填充:

在这里插入图片描述

图三:选定邮件应用后,写邮件界面字段自动填充

点击附件按钮添加附件,附件行出现在底部,路径为应用临时目录下的文件:

在这里插入图片描述

图四:添加附件后附件行出现

带附件再次发送并选定邮件应用,写邮件界面顶部显示「1 个附件 (373 B)」——附件已随邮件真实传入邮件应用:

在这里插入图片描述

图五:带附件发送,写邮件界面列出附件

设备上没有已安装并声明邮件能力的应用时(例如未配置邮件应用的模拟器),面板会给出兜底提示「暂无可用邮件发送方式」,send() 同样正常返回——拉起链路本身仍然可用(见 FAQ Q1)。接入成本如何?往下看。

二、flutter_email_sender 是什么

flutter_email_sender 是 pub.dev 上的一个老牌 Flutter 插件(作者 sidlatau,Apache-2.0 协议,月下载量超过十三万),职责单一:把一封待发送的邮件交给系统,拉起平台原生的邮件界面。它不直接联网发信,发送动作始终由用户在系统邮件界面里确认完成,这也是各平台邮件类插件的标准做法。

上游支持 Android、iOS、macOS 与 Web,适配鸿蒙后新增 OpenHarmony / HarmonyOS 支持:插件在鸿蒙上通过 startAbilityByType('mail') 拉起系统级邮件应用选择面板,由用户选择已安装的邮件应用完成发送。

典型的使用场景:意见反馈入口、邀请好友、错误日志上报、把分享内容转成邮件发送——只要业务里出现"把一段内容发给某个邮箱",这个插件就能覆盖。

三、环境准备

本文所有实测均在以下环境完成:

版本说明
Flutter(ohos 版)3.41.10-ohos-1.0.1主验证环境
编译 SDK26.0.0(26)example 宿主工程 compatibleSdkVersion 为 6.1.0(23)(真机安装所需,见适配教程 4.1 Q5)
设备DevEco 模拟器 + HUAWEI 真机模拟器 OpenHarmony 7.0.0.105 / API 26;真机 ADA-AL10U,HarmonyOS 6.1.1.120 / API 24

两点提醒:

  1. 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物,环境搭建见官方指南;
  2. 设备上需要装有邮件应用才能走完"选择应用并进入写邮件界面"的完整链路;没有邮件应用时面板提示"暂无可用邮件发送方式",拉起链路本身仍然可用(见 FAQ Q1)。本文真机装有华为邮件,完整链路已实测走通。

四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  flutter_email_sender:
    git:
      url: https://atomgit.com/oh-flutter/flutter_email_sender.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 10.0.1-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x

Flutter 框架版本TAG 名称分支名
3.4110.0.1-ohos-1.0.0-beta.1feat/ohos_flutter_email_sender_10.0.1

该 TAG 已在 3.41.10-ohos-1.0.1 搭配 DevEco 模拟器(OpenHarmony 7.0.0.105 / API 26)与 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.1.120 / API 24)上实测通过,真机上带附件发送已验证附件进入邮件编辑界面。不同 TAG 之间的变更详见仓库中的 CHANGELOG.OpenHarmony.md。

五、代码接入

5.1 导入库

import 'package:flutter_email_sender/flutter_email_sender.dart';

5.2 发送一封最简单的邮件

final Email email = Email(
  body: '这是邮件正文',
  subject: '这是邮件主题',
  recipients: ['to@example.com'],
);

await FlutterEmailSender.send(email);

在鸿蒙设备上运行,系统弹出邮件应用选择面板,收件人、主题与正文自动填充,用户选择邮件应用后进入写邮件界面确认发送。整个过程一行平台判断都不需要写。

5.3 补全抄送、密送与附件

Email 是一个不可变的数据类,构造时按需填字段:

final Email email = Email(
  subject: '项目周报',
  body: '各位好,本周进展见附件。',
  recipients: ['boss@example.com'],
  cc: ['pm@example.com'],
  bcc: ['archive@example.com'],
  attachmentPaths: ['/data/storage/el2/base/haps/entry/cache/report.pdf'],
  isHTML: false,
);

各字段说明与鸿蒙支持情况:

字段类型说明鸿蒙支持
subjectString主题支持
recipientsList<String>收件人列表支持
ccList<String>抄送列表支持
bccList<String>密送列表支持
bodyString正文支持(纯文本)
attachmentPathsList<String>?附件本地绝对路径支持
isHTMLbool正文是否按 HTML 渲染不支持,传 true 会前置抛异常(见 FAQ Q3)

附件路径要求是应用可读的本地绝对路径,比如应用沙箱内的缓存目录、临时目录下的文件。插件内部会把路径转换成系统 fileUri 并授予目标邮件应用读权限,不需要声明任何权限。沙箱外或应用无权读取的路径会导致目标应用无法打开附件。

5.4 发送前查询平台能力

getCapabilities() 返回当前平台的邮件能力,返回值 EmailCapabilities 包含 canSendsupportsCcsupportsBccsupportsSubjectsupportsPlainTextBodysupportsHtmlBodysupportsAttachments 七个布尔字段。鸿蒙平台的返回值:支持发送、收件人、抄送、密送、主题、纯文本正文与附件,不支持 HTML 正文。

推荐的姿势是让界面跟着能力走,而不是写死平台判断:

final capabilities = await FlutterEmailSender.getCapabilities();

// canSend 为 false 时禁用发送入口
if (capabilities.canSend) {
  await FlutterEmailSender.send(email);
}

// 没有该能力时自动隐藏对应的功能开关
if (capabilities.supportsHtmlBody) { /* 显示 HTML 正文选项 */ }
if (capabilities.supportsAttachments) { /* 显示附件添加入口 */ }

这样同一份代码在 iOS(支持 HTML)与鸿蒙(不支持 HTML)上各自呈现合理的能力开关,上游 example 就是这个写法:鸿蒙上 HTML 开关会自动隐藏,发送按钮只在 canSend 为 true 时可用。

5.5 跨平台一套代码

sendgetCapabilities 是仅有的两个接口,各平台行为如下:

平台send 行为
Android拉起系统邮件 Intent,无邮件应用时抛 FlutterEmailSenderNotAvailableException
iOS弹出系统邮件编辑界面
macOS弹出邮件编辑窗口
Web打开 mailto: 链接
OpenHarmony / HarmonyOS拉起系统邮件应用选择面板,用户选择邮件应用后进入写邮件界面

调用侧零分支:能力差异通过 getCapabilities() 呈现,失败语义各端对齐(无可用邮件应用一律抛 FlutterEmailSenderNotAvailableException),业务代码不需要为鸿蒙写 if-else。

5.6 实战:给应用加一个"意见反馈"入口

把前面的内容拼起来,做一个真实的业务场景:设置页里的"意见反馈"按钮,点击后预填反馈邮箱与版本信息正文,设备无邮件应用时给出兜底提示:

import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';

Future<void> openFeedback(BuildContext context) async {
  // 先查能力,无邮件环境时直接降级,不发起无效拉起
  final capabilities = await FlutterEmailSender.getCapabilities();
  if (capabilities.canSend != true) {
    if (!context.mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('当前设备没有可用的邮件应用')),
    );
    return;
  }

  final email = Email(
    subject: '【应用反馈】问题反馈',
    body: '请描述您遇到的问题:\n\n版本:1.0.0\n设备:',
    recipients: ['support@example.com'],
  );

  try {
    await FlutterEmailSender.send(email);
  } on FlutterEmailSenderNotAvailableException {
    if (!context.mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('未找到可用的邮件应用,请先安装')),
    );
  } on FlutterEmailSenderUnsupportedFeatureException {
    // 邮件包含当前平台不支持的能力(如鸿蒙上的 HTML 正文)
    if (!context.mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('当前平台不支持该邮件内容格式,请使用纯文本')),
    );
  }
}

在按钮的 onPressed 里调用 openFeedback(context) 即可。能力预查加异常兜底双保险后,这个入口在 Android、iOS、鸿蒙上行为一致:有邮件环境就拉起编辑界面,没有就提示降级。

六、运行与验证

以下为 example 工程的实测记录。仓库中 example 的 Bundle Name 为 com.example.demo,自建工程验证时替换为你自己的包名即可。

设备项
设备DevEco 模拟器;HUAWEI ADA-AL10U 真机
系统版本模拟器 OpenHarmony 7.0.0.105(SP6DEVC00E999R4P11);真机 HarmonyOS 6.1.1.120
API 版本模拟器 26;真机 24

6.1 验证一:发送无附件邮件

在 example 目录构建、安装并启动:

cd packages/flutter_email_sender/example
flutter pub get
flutter build hap --debug

# 安装(产物位于 ohos/entry/build/default/outputs/default/)
hdc install ohos/entry/build/default/outputs/default/entry-default-signed.hap

# 启动
aa start -a EntryAbility -b com.example.demo

保持输入框默认内容(收件人 example@example.com、主题与正文已预填),点击右上角发送按钮:

# 观察日志中的插件与系统行为
hdc hilog | grep -iE "flutter|appselector"

预期现象:系统弹出邮件应用选择面板(有邮件应用时列出可选方式,无邮件应用时为兜底提示),SnackBar 显示 success,日志中可见系统应用选择器进程被拉起,无插件异常。对应效果见第一章图二。

6.2 验证二:带附件发送

点击右下角附件按钮。鸿蒙上 example 从打包进应用的内置资源复制一份样例附件到应用临时目录(上游的选附件库暂无鸿蒙实现,example 按 TargetPlatform.ohos 分支做了这个处理,业务工程里替换为你自己的文件路径即可),附件行出现在底部(见第一章图四)。

带附件点击发送,预期现象与验证一一致,附件经 fileUri 机制传入系统层,面板弹出、SnackBar 显示 success。真机实测:面板中选择「电子邮件」进入写邮件界面,顶部显示「1 个附件 (373 B)」,附件已随邮件带入(见第一章图五)。

七、工作原理

整个调用链路如下:

Dart: FlutterEmailSender.send(Email)
  → MethodChannelFlutterEmailSender(能力校验 validateEmail)
    → MethodChannel('flutter_email_sender') / 'send'
      → ArkTS: FlutterEmailSenderPlugin.handleSend
        → 组装 wantParam 并 context.startAbilityByType('mail', ...)
          → 系统弹出邮件应用选择面板

Dart 侧按 defaultTargetPlatform 分发:鸿蒙上命中 TargetPlatform.ohos,使用鸿蒙能力集(supportsHtmlBody: false)并在通道调用前完成能力校验,不支持的请求(如 isHTML: true)直接抛异常,不会到达原生层。

ArkTS 侧把 Email 各字段映射进 wantParam:

wantParam 键含义来源
sceneType1,标准邮件发送场景固定值
email收件人列表recipients
cc / bcc抄送 / 密送列表cc / bcc
subject / body主题 / 正文encodeURI 编码后传入
ability.params.stream附件 fileUri 列表fileUri.getUriFromPath(attachmentPaths)
ability.want.params.uriPermissionFlag授予目标应用读权限FLAG_AUTH_READ_URI_PERMISSION

发起拉起并回传结果的核心代码:

// MethodResult 只允许回复一次,replyOnce 保证成功/失败路径双向互斥
let replied: boolean = false;
const replySuccess = (): void => {
  if (!replied) { replied = true; result.success(null); }
};
const replyNotAvailable = (message: string): void => {
  if (!replied) { replied = true; result.error('not_available', message, null); }
};
const abilityStartCallback: common.AbilityStartCallback = {
  onError: (code, name, message) => { replyNotAvailable(...); },
  onResult: (abilityResult) => { replySuccess(); }
};
context.startAbilityByType('mail', wantParam, abilityStartCallback, (err: BusinessError): void => {
  if (err && err.code != 0) { replyNotAvailable(...); } else { replySuccess(); }
});

成功路径回 success(null)(Dart 侧 send() 正常返回),失败路径回错误码 not_available(映射为 FlutterEmailSenderNotAvailableException),错误码与上游 Android / iOS 语义对齐。

一个值得了解的机制差异:Android 的 resolveActivity 与 iOS 的 canSendMail 都能在发送前探测设备上有没有邮件应用,鸿蒙目前没有等价预查询 API,邮件面板由系统按已安装应用自动兜底。因此鸿蒙上 getCapabilities() 固定返回 canSend: true,"设备上没有邮件应用"这一情况会在拉起阶段经 onError 回 not_available 暴露。业务上对可用性敏感的,按 5.6 的双保险写法处理即可。

八、常见问题

Q1:点击发送提示「暂无可用邮件发送方式」是怎么回事?

设备上没有已安装并声明邮件能力的应用。这不是故障:拉起请求已到达系统层并正确回调(SnackBar success)。安装任意一个邮件应用并登录后再次调用,面板会列出该应用并进入写邮件界面。模拟器环境可在应用市场安装邮件类应用后复测。

在这里插入图片描述

Q2:send() 正常返回就代表邮件发出去了吗?

正常返回代表"系统面板拉起成功、用户已进入选择与发送流程"。最终发送由用户在选定的邮件应用里确认,插件与系统都不代替用户做这个决定。需要感知最终发送结果的场景,邮件类系统面板没有提供回执,建议在业务侧用其他方式确认。

Q3:isHTML: true 为什么直接抛 FlutterEmailSenderUnsupportedFeatureException

鸿蒙邮件扩展面板是纯文本语义,插件在鸿蒙能力集里声明 supportsHtmlBody: false,能力校验在拉起之前拦截不支持的请求。鸿蒙上使用纯文本正文;界面上的 HTML 开关可按 supportsHtmlBody 自动隐藏(见 5.4)。

Q4:附件路径有什么要求?

应用可读的本地绝对路径,例如应用沙箱缓存目录、临时目录下的文件。插件内部经 fileUri 转换并授予目标应用读权限,无需声明权限;但应用无权读取的路径(如其他应用的沙箱文件)会导致目标邮件应用拿不到附件内容。另外路径中不能出现连续分隔符(如 cache//a.txt):系统面板的沙箱策略校验会拒绝这类路径,邮件应用会静默丢弃附件——鸿蒙上 Directory.systemTemp.path/ 结尾,手动拼接路径时注意先去掉尾部分隔符,根因分析见姊妹篇《flutter_email_sender 的鸿蒙适配教程》4.2 Q3。

Q5:构建或安装报错怎么排查?

编译需使用 ohos 版 Flutter SDK;安装报"此应用暂不支持在当前设备安装"通常是宿主工程 compatibleSdkVersion 高于设备 API;hap 装不上先检查签名配置与 bundleName 是否匹配。这些环境类问题的定位过程见姊妹篇《flutter_email_sender 的鸿蒙适配教程》第四章。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 flutter_email_sender,构造 Email 对象调用 FlutterEmailSender.send(),即可在鸿蒙 App 内唤起系统邮件应用选择面板,收件人、抄送、密送、主题、正文与附件自动带入;发送前可用 getCapabilities() 让界面跟着平台能力走。同一段代码在 Android、iOS、macOS 与 Web 上也各自生效,异常语义各端对齐。插件零权限、不绑定单一邮件应用,已在 DevEco 模拟器(OpenHarmony 7.0.0.105 / API 26)与 HUAWEI 真机(HarmonyOS 6.1.1.120 / API 24)完整实测,真机上带附件发送已验证附件进入邮件编辑界面。

使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐