1. OpenHarmony 上的分享功能为什么不能照搬 Android 思路
做 Flutter 跨端开发做了几年,遇到"分享"这个需求我向来是不慌的——不就是调一次系统分享面板吗?文本、图片、链接各来一发,Android 上 Intent 一拉,iOS 上 UIActivityViewController 一弹,完事。直到我真正开始把 Flutter 应用往 OpenHarmony 设备上移植,才发现这套经验完全不够用。
OpenHarmony 的分享机制底层不叫 Intent,叫 Want。它有一套独立的 Ability 拉起规则和参数体系,fltter engine 跑在上面时,Dart 侧调系统分享能力是没有现成路径的。更麻烦的是,Flutter 生态里成熟的分享插件,比如 share、share_plus,默认只实现了 Android 和 iOS 的平台代码,OpenHarmony 设备上是拿不到 SystemShare 的。
所以拿到"系统分享功能"这个需求时,我的第一反应是:不能等插件作者适配,得自己动手。这条路走下来,核心工作其实就两件事,第一是找到 API 设计和扩展性都合适的 Flutter 第三方库,第二是通过 Flutter 的 MethodChannel 机制,把分享请求从 Dart 侧送进 OpenHarmony 的系统 Ability。这一整套做完,share_extend 这个名字才真正从"一个候选插件"变成了"鸿蒙设备上的可用分享能力"。
1.1 先弄清楚 OpenHarmony 的 Want 到底想干什么
想要在 OpenHarmony 上做系统分享,必须先理解它和 Android 的差异。Android 的分享 Intent 核心是ACTION_SEND+type+EXTRA_TEXT/EXTRA_STREAM,系统根据 MIME 类型匹配能处理这条 Intent 的应用。OpenHarmony 把这套东西换了个实现:
Action字段负责描述意图,例如分享文本通常用系统预置的ohos.want.action.sendToData;Uri和type负责描述要分享的数据,type是 MIME 类型;parameters是额外参数,比如ability.params.stream用来传文件流。
这意味着 Flutter 插件层要做的事,其实就是把 Dart 侧给的字符串、文件路径、图片字节流,翻译成 Want 参数,再通过context.abilityContext.startAbility(want)或者startAbilityForResult把系统分享面板拉起来。
对 Flutter 开发者来说,这段代码不会出现在 Dart 侧,而是在插件的 OpenHarmony 平台实现里。所以选哪个插件做基底,决定了这个翻译层要自己写的部分有多少。
1.2 share / share_plus / share_extend 三条路线的取舍
当时我对比了三个插件:
| 插件 | Android 支持 | iOS 支持 | OpenHarmony 支持 | 分享图片方式 | API 灵活度 |
|---|---|---|---|---|---|
| share | 完整 | 完整 | 无 | 只支持 XFile 路径 | 中 |
| share_plus | 完整 | 完整 | 社区有 PR 但未合入 | XFile 路径 + base64 | 中高 |
| share_extend | 完整 | 完整 | 无 | 路径 + base64 + 多类型混合 | 高 |
share_extend 的 API 设计是我见过最贴近"跨端系统分享"场景的。它不要求你必须把图片放在某个临时文件里,而是可以直接传 base64 字符串,这对 OpenHarmony 适配是一个极大的便利——鸿蒙的图片分享能力本身就对内存字节流接受度很高,少一层文件落地就少很多权限和路径问题。
而且 share_extend 允许一次调用同时分享 "text" + "filePaths" 或 "text" + "imageBase64",这个"混合分享"能力在处理"一段文字配一张截图"这种高频场景时非常有用。share_plus 想做到这个就得自己拼多个 channel 调用,逻辑就散了。
1.3 share_extend 源码里的插件扩展点在哪里
选定 share_extend 之后,我第一步是翻它的源码结构。Flutter 插件的标准结构是lib/放 Dart API,android/和ios/放平台实现,share_extend 也不例外。
关键文件是share_extend.dart里暴露的三个方法:ShareExtend.share()(文本)、shareExtend.shareImage()(图片)、shareExtend.shareFile()(文件)。这三个方法最终都会汇聚到一个私有方法里,通往下层MethodChannel.invokeMethod。
让我觉得这个插件适配 OpenHarmony 成本可控的核心原因就在这里:它所有平台相关逻辑都收口在ShareExtendPlugin的onMethodCall分发里,方法名就三个,参数结构也清晰。适配时只要在工程里新增一个 OpenHarmony 的 har 包,在这个插件类里处理shareText、shareImage、shareFile三种 case,剩下的 Dart 侧 API 完全不用动。
2. 环境准备:Flutter SDK 与 OpenHarmony 工具链的前置组合
正式开始之前,环境是最容易卡人的环节。OpenHarmony 上的 Flutter 开发不是装个 Flutter SDK 就能跑的,它需要一整套版本咬合正确的工具链。我的建议是:先花半天把所有版本确定下来,再动手写代码,不然中途升版本会搞得你怀疑人生。
2.1 版本组合怎么选
OpenHarmony 的 Flutter 适配走的是 OpenHarmony SIG 维护的分支,发布节奏和 Google 主仓有偏移。我实测下来比较稳的一套组合是:
- OpenHarmony SDK:API 10(4.0 Release 及以上)
- Flutter SDK:OpenHarmony 4.0 适配分支(基于 Flutter 3.7 至 3.10 之间)
- DevEco Studio:4.0 Release,配套的 hvigor 版本
- Flutter SDK 的 path 里要能看到
flutter_ohos相关目录
这套组合的验证标准很简单:用 DevEco Studio 新建一个 OpenHarmony 空工程,然后在同一 IDE 里打开 Flutter 插件的 ohos 示例工程,如果能跑起来,说明工具链基本咬合了。
注意:不要手痒去把 Flutter SDK 升到 3.16+ 再用在 OpenHarmony 4.0 上。OpenHarmony 的 Flutter engine 还在持续适配,版本跳太大容易出现 Dart AOT 产物格式不兼容,运行时报错查起来非常费劲。
2.2 配置文件里藏着的是权限,不是代码
OpenHarmony 工程里有一个很容易忽略的文件:ohos/module.json5,它管理着模块的权限声明。分享功能至少要声明ohos.permission.READ_IMAGEVIDEO(读图库图片)和ohos.permission.READ_MEDIA(读媒体文件),如果还要分享下载目录里的文件,ohos.permission.READ_DOCUMENT也得加上。
这段配置往往被放在最后才处理,但缺了它你会发现一个诡异现象:分享面板能弹出来,但图片输出到微信或其他应用后是空的,或者在相册选图阶段直接崩溃。因为权限缺失导致文件读取失败,系统分享面板拿不到数据,不会报错,只会"安静地失败"。
2.3 环境变量和 IDE 识别的经典问题
我踩过的第一个坑是:DevEco Studio 识别不到 OpenHarmony 签名设备,但 adb 能看到。原因通常是 IDE 用的hdc(HarmonyOS Device Connector)路径没有配置到系统环境变量里。解决方式是在.bashrc或 Windows 环境变量里加入 DevEco SDK 自带的toolchains目录,例如:
export OHOS_SDK_HOME=/path/to/ohos-sdk export PATH=$PATH:$OHOS_SDK_HOME/9/toolchains配好之后,在终端跑hdc list targets,能列出设备就说明 IDE 也能识别了。
3. share_extend 接入实战:从依赖声明到分享面板首次调起
环境就绪之后,进入正题。这里我会把从零接入的完整路径走一遍,包括依赖声明、权限、Dart 侧调用,以及 OpenHarmony 平台侧的关键实现。
3.1 依赖声明和版本锁定
share_extend 在 pub.dev 上的最新版本是 2.0.3(我用的版本,再往上的版本我没有实测过),但直接把它加进 OpenHarmony 工程的pubspec.yaml还不行,因为 pub 默认拉到的插件包不含 ohos 实现。
实操做法是:先把 share_extend 源码 fork 或直接下载到本地third_party目录,在pubspec.yaml里用 path 引用:
dependencies: flutter: sdk: flutter share_extend: path: ./third_party/share_extend然后在 share_extend 源码工程里新增ohos目录,把插件的 OpenHarmony 实现放进去。这一步做完,flutter pub get 才会把 ohos 的 har 一起打进去。
为什么我不直接用 pub 的线上版本而是用本地路径?因为插件在 pub 上架后,OpenHarmony 分支的代码不会自动同步,只有源码里真正含ohos目录的版本才能被 Flutter 的 ohos 构建链识别。用 path 依赖可以确保你改的是那份带鸿蒙实现的代码,排查问题的时候路径也是可控的。
3.2 Dart 侧最小可行代码
依赖加好之后,Dart 侧其实不需要做任何针对 OpenHarmony 的特殊处理,这个体验对 Flutter 开发者来说很舒服:
import 'package:share_extend/share_extend.dart'; // 分享纯文本 ShareExtend.share('来自 OpenHarmony 的分享测试', 'text'); // 分享单张图片 ShareExtend.shareImage('/data/storage/el2/base/files/share_test.png', 'image'); // 分享 base64 图片 String base64Str = 'iVBORw0KGgo...'; // 自己生成或从网络获取 ShareExtend.shareImageBase64(base64Str, 'image', 'share_test.png');这三行代码覆盖了系统分享最常见的三个入口。实战中,分享文本和分享图片是最高频的,base64 那条路径在"分享网络图片"这个场景特别好用,因为不用先把字节流写成临时文件。
首次跑到ShareExtend.share()的时候,我的预期是弹一个系统分享面板。但真相是,第一次我拿到了一个空的分享面板,里面列了很多应用,但点开目标应用后没有任何内容。这个坑留在第五节细说,先继续讲平台侧的实现。
3.3 OpenHarmony 平台实现:把 Dart 参数翻译成 Want
这是本篇文章最核心的代码。share_extend 插件在 OpenHarmony 侧的实现,本质是一个ShareExtendPlugin,它继承 FlutterPlugin 并注册 MethodChannel:
import { FlutterPlugin } from '@ohos/flutter_ohos'; import { MethodCall, MethodChannel } from '@ohos/flutter_ohos'; export class ShareExtendPlugin implements FlutterPlugin { private methodChannel: MethodChannel; onAttachedToEngine(binding: PluginBinding): void { this.methodChannel = new MethodChannel(binding.getBinaryMessenger(), 'share_extend'); this.methodChannel.setMethodCallHandler((call: MethodCall) => { return this.handleMethodCall(call); }); } private async handleMethodCall(call: MethodCall): Promise<any> { switch (call.method) { case 'shareText': return this.shareText(call.arguments as Map<string, string>); case 'shareImage': return this.shareImage(call.arguments as Map<string, string>); case 'shareFile': return this.shareFile(call.arguments as Map<string, string>); default: throw new Error(`Unknown method: ${call.method}`); } } }shareText的完整实现要点是构造 Want 并指定 action:
async shareText(args: Map<string, string>): Promise<number> { let text = args['text'] || args['texts'] || ''; let want: Want = { action: 'ohos.want.action.sendToData', type: 'text/plain', parameters: { 'ability.params.stream': text, 'ability.params.title': '分享文本' } }; // context 是通过 UIAbilityContext 获取的 await this.context.startAbility(want); return 0; }这里有个细节要提醒大家:ability.params.stream在 OpenHarmony 里可以被塞字符串,也可以被塞文件流。分享纯文本时直接塞字符串是最省事的;分享图片时优先塞fileUri经fs.open拿到的流,再把流塞进ability.params.stream。
3.4 图片分享的两种实现路径
图片分享是 share_extend 相对其他插件最有优势的场景。在 OpenHarmony 上,它有两条实现路线:
- 路径分享:如果图片已经落在本地(例如下载到了
files目录),直接取文件路径,通过fileUri.getUri拿到的串传给 Want 的uri字段; - base64 分享:网络图片或者内存缓存图片,不落盘直接用 base64 字符串。此时 Want 的 type 是
image/*,同时需要把 base64 解码成ArrayBuffer,放进parameters['ability.params.stream']。
async shareImage(args: Map<string, string>): Promise<number> { let path = args['path'] || args['paths'] || ''; let type = args['type']; let name = args['name'] || 'share_image.png'; let fileUriObj = fileUri.getUriFromPath(path); let stream = await fs.open(fileUriObj.path, fs.OpenMode.READ_ONLY); let want: Want = { action: 'ohos.want.action.sendToData', type: type || 'image/*', uri: fileUriObj.toString(), parameters: { 'ability.params.stream': stream, 'ability.params.title': name } }; await this.context.startAbility(want); return 0; }base64 路径几乎一模一样,只是把uri换成解码后的字节流:
async shareImageBase64(args: Map<string, string>): Promise<number> { let base64Str = args['base64']; let type = args['type']; let name = args['name'] || 'share_image.png'; // base64 转 ArrayBuffer let buffer = base64ToArrayBuffer(base64Str); let want: Want = { action: 'ohos.want.action.sendToData', type: type || 'image/*', parameters: { 'ability.params.stream': buffer, 'ability.params.title': name } }; await this.context.startAbility(want); return 0; }两条路径实测都能在微信、邮箱、信息应用里正常唤起分享草稿。会写到这里,基本说明 share_extend 在 OpenHarmony 上的核心能力已经打通了。
4. 拆穿这层壳:MethodChannel 如何把 Flutter 分享请求送进系统 Want
分享功能跑通之后,我反而更想把中间那层桥接彻底讲清楚。因为 OpenHarmony 的 Flutter 插件机制和 Android 有差异,很多问题如果不理解这层壳,排查起来就是黑盒乱猜。
4.1 一次分享请求的完整调用链
从 Dart 侧的ShareExtend.share()到 OpenHarmony 系统分享面板,全程经过五个环节:
- Dart 侧调用
ShareExtend.share(),它内部走platformChannel.invokeMethod('shareText', args); - Flutter engine 把 MethodCall 编码成二进制消息,经由
BinaryMessenger发送到 OpenHarmony native 侧; ShareExtendPlugin的MethodChannel.setMethodCallHandler收到消息,解析 method 名和参数;- 插件代码构造 Want 对象,调用
context.startAbility(want); - OpenHarmony 系统根据 Want 匹配可接收的应用,弹出分享面板。
在整个链路里,Dart 和 TS(ArkTS)两侧通过字符串方法名做契约。任何一侧方法名对不上,结果是 Dart 侧收到MissingPluginException,这也是 OpenHarmony 插件适配最常见的启动期问题。
// Dart 侧调用后,可以捕获异常判断插件是否注册成功 try { await ShareExtend.share('测试内容', 'text'); } on MissingPluginException catch (e) { print('插件未注册: ${e.message}'); }如果你的应用在 OpenHarmony 真机上出现这个异常,九成是插件类没有在EntryAbility的onCreate里调用ShareExtendPlugin.register()。Flutter 的 ohos 适配要求所有原生插件在 Ability 启动阶段手动注册,这个动作和 Android 自动注册不太一样。
4.2 Want 构造的几个核心参数是怎么确定的
很多人遇到的问题是:照着官方样例写了 Want,但分享面板不弹,或者弹出来是空的。我帮同事排查时发现,大多数时候是 Want 参数填错了。
OpenHarmony 的 Want 结构有几个关键字段,分享场景下尤其注意:
| 字段 | 分享文本 | 分享图片 | 分享文件 |
|---|---|---|---|
| action | ohos.want.action.sendToData | 同左 | ohos.want.action.sendToData |
| type | text/plain | image/png或image/* | 与文件后缀匹配的 MIME |
| uri | 可不填 | 文件 uri 或流 | 文件 uri |
| parameters.stream | 文本内容 | 图片字节流 | 文件流 |
| parameters.title | 分享标题 | 分享标题 | 分享标题 |
action 不必多说,sendToData对应的是"发送数据"这个系统能力。type 很关键,它是系统做应用匹配的过滤条件,填image/*能匹配所有支持图片分享的应用,填image/png则只匹配对 png 有明确声明的应用,实测微信在image/png下也还能匹配到,但部分邮件应用可能匹配不到。
parameters.title比较容易忽略,它会在分享面板顶部标题栏显示。不传也不会崩,但很多系统应用会把 title 作为分享内容的一部分带入草稿,例如邮件会把 title 当作邮件标题,所以建议都带上。
4.3 为什么说 MethodChannel 参数结构要简单
share_extend 的 Dart 侧代码在传参时做了一件很聪明的事情:所有分享类型最终都用Map<String, String>传递。
static Future<void> share(String text, String type) async { var args = <String, String>{ 'text': text, }; await _channel.invokeMethod('shareText', args); }参数结构保持扁平,对 OpenHarmony 侧的类型转换压力最小。ArkTS 侧的call.arguments拿到的就是Map<string, string>,不需要再嵌套解包。别小看这个设计,有些插件在传文件列表时用List<Map>,OpenHarmony 侧解析 json 数组再转 ArrayBuffer,API 10 上容易踩序列化转换的坑。
如果你要自己适配插件,建议严格遵守"Dart 侧只传扁平 Map,值为 String 或 int"这个原则。复杂结构走 JSON 序列化反而更容易出问题。
5. 踩坑实录:分享面板不弹出、图片不展示、结果回调失效
任何第三方库适配到新平台,不可能一遍跑通。这里我记录三个最典型的坑,以及完整排查链路,希望帮你省下两三个通宵。
5.1 错误一:分享面板弹出但内容是空的
这个现象最迷惑人,因为你看到的是"系统分享面板正常工作",但目标应用收到的内容为空。
我第一次遇到时排查链路是这样的:
- 先看 Dart 侧有没有异常——没有,
shareText返回正常; - 再看 OpenHarmony 侧日志——用
hdc shell hilog | grep ShareExtend,没发现报错; - 单独测试 shareText 的文本内容——发现问题出在
parameters的 key 写错了。
OpenHarmony 的系统应用约定使用ability.params.stream取值,但部分系统应用版本还认ability.params.text。我一开始只写了ability.params.stream,在部分版本上内容丢失。修复方式是同时塞两份:
parameters: { 'ability.params.stream': text, 'ability.params.text': text, 'ability.params.title': '分享文本' }这个兼容性写法不仅适用于文本,图片分享里的 base64 数据同时塞一份到ability.params.stream和ability.params.fileUriList也能提升兼容面。代价仅是内存多一点,可接受。
5.2 错误二:图片分享到微信后只有文件名没有图
这个问题定位起来比第一个快,因为它和权限强相关。现象是:分享面板正常,微信也弹出了,但聊天框里只有一个share_image.png的文件名,点击没有预览图,发送后对方看到的是空文件。
定位过程:
- 先确认图片源文件是否存在——用
hdc file read /data/storage/el2/base/files/share_test.png,文件存在且大小正常; - 再查应用是否申请了媒体读取权限——
module.json5里漏了ohos.permission.READ_MEDIA; - 补上权限后重新安装——问题消失。
这里有个细节:OpenHarmony 的权限分为system_grant(安装时授权)和user_grant(运行时动态弹窗确认)两类。READ_MEDIA属于 user_grant,所以不只要在module.json5声明,还要在代码里动态申请:
import { abilityAccessCtrl, Permissions } from '@ohos.abilityAccessCtrl'; async function requestPermission(): Promise<void> { let atManager = abilityAccessCtrl.createAtManager(); let permissions: Permissions = [ 'ohos.permission.READ_MEDIA', 'ohos.permission.READ_IMAGEVIDEO' ]; let result = await atManager.requestPermissionsFromUser(context, permissions); if (result.authResults.some(r => r !== 0)) { // 有权限被拒绝,需引导用户手动开启 } }分享图片前先跑这段权限请求,比直接读取文件再失败重试要合理得多。
5.3 错误三:分享完成后 Dart 侧拿不到结果
share_extend 的 API 设计里,share()返回值是 Future,但没有设计"分享完成/取消"的回调。这在 Android 上可以接受,因为 Activity 的onActivityResult需要插件层单独实现。但到了 OpenHarmony,如果希望感知分享结果,比如用户取消分享,用来做埋点,就需要额外处理。
问题是:startAbility()是拉起即返回的,它不代表分享完成。真正能感知结果的是startAbilityForResult(),它会在目标应用处理完成后回调。
适配思路是:在 ArkTS 插件里实现abilityContext.startAbilityForResult(want, callback),把结果码回传到 Dart 侧:
async shareTextWithResult(args: Map<string, string>): Promise<number> { let want: Want = { /* 构造同上 */ }; let result = await this.context.startAbilityForResult(want); // result.resultCode == 0 表示成功,-1 表示用户取消 return result.resultCode; }Dart 侧相应增加一个方法:
static Future<int> shareWithResult(String text, String type) async { return await _channel.invokeMethod('shareTextWithResult', { 'text': text, }); }数据回传链路变成:分享面板关闭 →startAbilityForResult回调 → 插件层返回 resultCode → MethodChannel 回传 Dart 侧。这比单纯startAbility的体验完整很多,建议在正式产品里采用。
但这里也顺便提醒:startAbilityForResult在部分 OpenHarmony 系统应用(如某些第三方输入法)上会有超时,需要设定ONLY_IF_NEEDED之类的超时参数,或者容忍它 3 到 5 秒不回调。不要因为一次回调失败就认为用户取消了分享,否则埋点数据会很脏。
5.4 排查日志的实用命令
OpenHarmony 真机调试时,最有效的日志查看命令是:
hdc shell hilog | grep -E "ShareExtend|FlutterJNI|MethodChannel"hilog默认日志量很大,务必 grep 过滤。定位 plugin 注册问题看FlutterJNI,定位分享调用链看ShareExtend。另外注意,hdc shell在 API 10 上的部分版本需要加-t指定目标设备,多设备连接时尤其容易踩。
6. 覆盖更全的使用场景与进阶扩展思路
share_extend 在 OpenHarmony 上跑通基础分享后,你会发现思路一下就打开了。很多原生能力都能照这个路径接入,不必等官方适配。
6.1 文件分享与多文件混合分享
share_extend 原生 API 里没有多文件分享,但它允许传paths数组。在 OpenHarmony 侧,多文件的处理方式是把每个文件流放进ability.params.streams:
async shareFiles(args: Map<string, string>): Promise<number> { let paths: Array<string> = JSON.parse(args['paths'] || '[]'); let streams = []; for (let path of paths) { let fileUriObj = fileUri.getUriFromPath(path); let file = await fs.open(fileUriObj.path, fs.OpenMode.READ_ONLY); streams.push(file); } let want: Want = { action: 'ohos.want.action.sendToData', type: '*/*', parameters: { 'ability.params.streams': streams, 'ability.params.title': '多文件分享' } }; await this.context.startAbility(want); return 0; }实测在文件管理器里选中多个文件再点分享,能正常唤起微信的多文件发送。这个能力在业务场景里做"导出多个报表附件"特别实用。
6.2 EventChannel 扩展:分享状态的主动通知
如果你需要更精细的分享状态管理,比如分享成功、取消、失败,而不是在startAbilityForResult里被动等待,可以引入 EventChannel。思路是:插件在启动时注册一个 EventChannel,分享事件发生后主动向 Dart 侧推消息。
// 插件内注册 EventChannel private eventChannel: EventChannel; private eventSink: EventSink; onAttachedToEngine(binding: PluginBinding): void { this.eventChannel = new EventChannel( binding.getBinaryMessenger(), 'share_extend_events' ); this.eventChannel.setStreamHandler({ onListen: (args, sink) => { this.eventSink = sink; }, onCancel: () => { this.eventSink = null; } }); } // 分享完成后主动推状态 private notifyShareResult(code: number): void { if (this.eventSink) { this.eventSink.success({ 'resultCode': code }); } }Dart 侧监听:
final _eventChannel = EventChannel('share_extend_events'); _eventChannel.receiveBroadcastStream().listen((event) { int resultCode = (event as Map)['resultCode'] as int; if (resultCode == 0) { // 分享成功 } else { // 分享取消 } });这个方案的优点是不需要每个分享方法都等回调,分享面板一关结果立刻推送,适合做"分享成功打点"和"分享失败后重试提示"的交互逻辑。
6.3 其他插件适配的通用套路
把 share_extend 适配到 OpenHarmony 的经验,完全可以复制到其他 Flutter 插件上:
- 先看插件的 MethodChannel 方法名和参数结构,绘制出"方法名 → 参数格式 → 预期返回"清单;
- 对应到 OpenHarmony 的能力映射,查 API 文档确认底层用哪个 Ability 或 API 能实现;
- 在插件工程里新增 ohos 目录,新建同名 plugin 类,注册到 EntryAbility;
- 先用最简单的方法调用跑通链路,再逐步添加复杂参数;
- 多设备真机测试,特别关注不同系统版本在
parameters字段兼容性上的差异。
这套流程走完,你会发现 OpenHarmony 上的 Flutter 插件开发并没有想象中吓人,核心就是 Want + MethodChannel + 权限管理三板斧。
7. 验证清单:发布前你至少要确认这几件事
功能写完只是开始。我在交付前会跑一个固定的验证清单,每一项都直接关系最终体验:
| 检查项 | 验证方法 | 通过标准 |
|---|---|---|
| 文本分享 | 分享到备忘录、邮件、微信 | 纯文本无乱码,无内容缺失 |
| 单图分享 | 分享到微信、相册 | 图片预览正常,发送后对方可见 |
| base64 分享 | 分享网路图片 | 不发原图,字节流能正常展示 |
| 多文件分享 | 分享 2 个以上 PDF | 目标应用能收到多个附件 |
| 取消行为 | 打开分享面板后返回应用 | 无白屏,Dart 侧无异常 |
| 权限拒绝 | 拒绝媒体权限后分享 | 有引导弹窗,不崩溃 |
| 重复分享 | 连续分享 5 次 | 无内存泄漏,无崩溃 |
前四项偏功能正确性,后三项偏健壮性。第六项特别重要,因为用户很容易误点"不允许",一个权限被拒后就静默失败的分享功能,在用户那里就是"这个 App 分享坏了"。
另外一个体验细节是分享面板的 title。不要用 "分享" 这种通用文案,最好能带上业务上下文,比如"分享订单 #1024 详情"。在 OpenHarmony 上,这个 title 会直接显示在分享面板顶部,也会被部分应用作为默认文件名或邮件标题,带业务信息能让接收方省很多判断成本。
我实际使用下来的体会是:OpenHarmony 的 Flutter 生态正在快速补课,插件适配的思路其实比想象中统一。share_extend 这套"扁平参数 + Want 构造 + 权限管理"的三板斧,不仅解决了系统分享的问题,更是一个完整的插件迁移范本。后续遇到其他 Flutter 插件在 OpenHarmony 上不支持,翻一翻插件的 MethodChannel 定义,照这个路子写一版 openharmony 实现,大多数常见能力都能自己搞定。