笔记 | Flutter 三方库 share_handler 鸿蒙使用指南(整理分享)

刷到一个挺有意思的话题,结合自己之前的经验,整理了一下核心要点。

Flutter 三方库 share_handler 鸿蒙使用指南

本文是 share_handler 鸿蒙适配的使用侧文档,面向想把"接收系统分享"能力接入自己鸿蒙应用的 Flutter 开发者。适配过程、踩坑记录与源码剖析见姊妹篇《share_handler 鸿蒙适配教程》。

在鸿蒙上做社交、笔记、网盘、下载器类应用,几乎都绕不开一个需求:让用户从图库、浏览器、文件管理器里"分享"东西到你的应用。Android/iOS 生态里 share_handler 是这块的成熟方案,现在它也支持 OpenHarmony 了。本文以验证过的 example 为例,讲清楚怎么接、怎么跑、怎么避坑。

一、最终运行效果

先看目标效果。下面的截图均来自 API 26 模拟器实测(example 从图库分享一张图片):

应用冷启动,getInitialSharedMedia 返回最近一次分享东西并展示附件路径。

分享的图片被识别为 SharedAttachmentType.image,识别成功后冒出来 Record message 按钮。

![在这里插入图片描述](https://i-blog. x=700)

点击 Record message,recordSentMessage 持久化会话标识成功。

应用驻留后台时再次分享,sharedMediaStream 推送事件,UI 自动刷新。

二、share_handler 是什么

share_handler 是 AboutShout(MIT 协议)出品的 Flutter 插件,用于接收来自系统分享面板的东西(文本、链接、图片、视频、音频、任意文件),当前上游版本 0.0.25。核心四个接口:

接口用途getInitialSharedMedia()冷启动时取回触发本次启动的那次分享内容sharedMediaStream应用存活期间监听后续的分享事件recordSentMessage(...)记录一条"会话",让系统分享面板能推荐你的会话resetInitialSharedMedia()清空 initial media,防止重复消费

上游是联邦插件结构:share_handler(主包)+ share_handler_platform_interface(平台接口)+ share_handler_android/share_handler_ios 等端实现。鸿蒙适配新增了 share_handler_ohos 端实现包,基于 @kit.ShareKit(systemShare.getSharedData)接收分享数据,并通过 Flutter 的 AbilityAware 机制挂接 want 回调——宿主应用的 EntryAbility 无需任何改造

三、环境准备

以下为本文验证通过的环境,建议对齐或更新:

项版本/说明Flutter(ohos 版)3.41.10-ohos-1.0.1(Dart 3.11.5)DevEco Studio26.0.0.821(内置 API 26 SDK)模拟器/真机OpenHarmony 7.0.0.105(API 26)应用签名DevEco 自动签名即可(调试阶段)分享来源图库、浏览器、备忘录等系统应用

四、引入依赖

pubspec.yaml 中以 git 依赖引入。要注意:与单包插件不同,share_handler 仓库内主包位于 share_handler/ 子目录,path 务必指向它:

dependencies:
  share_handler:
    git:
      url: https://atomgit.com/oh-flutter/share_handler.git
      path: share_handler        # 指向仓库内的主包子目录,不要漏
      ref: 0.0.25-ohos-1.0.0-beta.1

TAG 对照:

包TAG/引用是否需要手动声明share_handler(主包)0.0.25-ohos-1.0.0-beta.1是,即上面的 refshare_handler_ohos(鸿蒙端实现)随主包引入否,主包已通过 ohos.default_package 注册

兼容性说明:

  • 鸿蒙端实现包 share_handler_ohos 通过主包 pubspec 的 plugin.platforms.ohos.default_package 注册,git 依赖场景下会自动随主包一起解析,无需(也不应)单独声明。
  • 在 Android/iOS 上行为与上游官方版本一致,不影响已有平台逻辑。
  • 该仓库未发布到 pub.dev,只能以 git 依赖使用。

五、代码接入

5.1 宿主配置 module.json5 skills(鸿蒙特有步骤)

这是鸿蒙端使用与 Android 端最大的差异:Android 端的 ACTION_SEND intent-filter 由插件自带,应用无需配置;鸿蒙端要求应用在 module.json5 的 ability 中自行声明接收分享的 skills。将下面第二个 skills 条目加到 entry/src/main/module.json5 的主 ability 中(与系统默认的 home skills 并列):

"skills": [
  {
    "entities": ["entity.system.home"],
    "actions": ["action.system.home"]
  },
  {
    "actions": ["ohos.want.action.sendData"],
    "uris": [
      { "scheme": "file", "utd": "general.plain-text", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.text", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.hyperlink", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.image", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.video", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.audio", "maxFileSupported": 100 },
      { "scheme": "file", "utd": "general.file", "maxFileSupported": 100 }
    ]
  }
]

要点:

  • 分享面板按 utd(Uniform Type of Data) 匹配目标应用,需穷举你支持的数据类型;上面七条对齐上游 Android 端 ACTION_SEND / ACTION_SEND_MULTIPLE 支持的范围。
  • 每个含 utd 的 uri 条目务必带 scheme,否则 hvigor 构建报 Schema validate failed(00303038)
  • maxFileSupported: 100 表示单次分享最多接收 100 个文件,可按需调整。

5.2 冷启动接收:getInitialSharedMedia

用户分享时你的应用没在运行 → 系统拉起应用,分享内容通过"initial media"交付。在应用初始化时取用(取用即清空,见 5.5):

import 'package:share_handler/share_handler.dart';

final sharedMedia = await ShareHandler.instance.getInitialSharedMedia();
if (sharedMedia != null) {
  // sharedMedia.content:分享的文本/链接(可能为 null)
  // sharedMedia.attachments:附件列表(可能为 null)
  print('文本: ${sharedMedia.content}');
  for (final att in sharedMedia.attachments ?? []) {
    print('附件: ${att!.path} (${att.type})');
  }
}

5.3 运行中接收:sharedMediaStream

应用存活期间(前台或后台驻留)再次收到分享时,通过事件流交付:

late final StreamSubscription _sharedMediaSub;

@override
void initState() {
  super.initState();
  _sharedMediaSub = ShareHandler.instance.sharedMediaStream.listen((SharedMedia media) {
    // 与 5.2 相同的方式消费 media
    setState(() => _latestMedia = media);
  });
}

@override
void dispose() {
  _sharedMediaSub.cancel();
  super.dispose();
}

建议 5.2 与 5.3 同时接入:冷启动那次走 getInitialSharedMedia,后续的都走流。

5.4 记录会话:recordSentMessage

调用后,系统分享面板在"发送到会话"场景下可推荐你的应用/会话:

await ShareHandler.instance.recordSentMessage(
  conversationIdentifier: 'user-1001',
  conversationName: '张三',
  conversationImageFilePath: avatarPath, // 可选
  serviceName: 'MyApp',
);

鸿蒙端语义说明:conversationIdentifier 会持久化到应用 preferences,用户下次通过分享面板分享到你的应用时,该标识会随 SharedMedia.conversationIdentifier 一起带回,帮你定位目标会话。与 Android/iOS 的 shortcuts/share suggestions 不同,鸿蒙端没有等价的系统推荐 API,这里是"记录 + 回带"的语义。

5.5 清空:resetInitialSharedMedia

getInitialSharedMedia 在鸿蒙端是读取即清的语义(与上游一致),通常无需手动调用。如果你的业务需要"取消消费"某次分享,能:

await ShareHandler.instance.resetInitialSharedMedia();

5.6 实战场景:按附件类型分发

一个典型的接入(合并 5.2/5.3,按类型处理附件):

class _MyHomePageState extends State {
  SharedMedia? _media;
  late final StreamSubscription _sub;

  @override
  void initState() {
    super.initState();
    _bootstrap();
    _sub = ShareHandler.instance.sharedMediaStream.listen((media) {
      setState(() => _media = media);
    });
  }

  Future _bootstrap() async {
    final media = await ShareHandler.instance.getInitialSharedMedia();
    if (media != null && mounted) setState(() => _media = media);
  }

  void _handleMedia(SharedMedia media) {
    final text = media.content;
    final attachments = media.attachments ?? [];
    for (final att in attachments) {
      switch (att!.type) {
        case SharedAttachmentType.image: // 图片:预览/上传
          break;
        case SharedAttachmentType.video: // 视频:转码/上传
          break;
        case SharedAttachmentType.audio: // 音频
          break;
        case SharedAttachmentType.file:  // 任意文件
          break;
      }
    }
    if (text != null && attachments.isEmpty) {
      // 纯文本/链接分享
    }
  }

  @override
  void dispose() {
    _sub.cancel();
    super.dispose();
  }
}

SharedMedia 可用字段(鸿蒙端):

字段鸿蒙端是否有值说明content有(文本类分享)general.text / hyperlink / plain-text 的内容attachments有(文件类分享)path 为应用 cache 下真实本地路径,type 为四类枚举conversationIdentifier有(如调用过 recordSentMessage)来自 ShareKit 的 contactIdrecipientIdentifiers / serviceName / senderIdentifier / imageFilePath / speakableGroupName无(iOS only 字段)保持 null

六、运行与验证

没有现成宿主应用时,可直接跑仓库自带的 example(它已做好 5.1 的宿主配置):

git clone https://atomgit.com/oh-flutter/share_handler.git
cd share_handler/share_handler/example

flutter pub get
flutter build hap --debug
# DevEco Studio 安装 entry hap 后,或用 hdc 安装
hdc install entry/build/default/outputs/default/entry-default-signed.hap

验证步骤:

1. 启动应用,确认首页显示 Shared media: null(首次无内容)。
2. 打开图库,选一张图片,点分享,分享面板应冒出来你的应用图标(若没有,回查 5.1 的 skills)。
3. 选择你的应用 → 冷启动,首页展示附件路径,SharedAttachmentType.image
4. 保持应用驻留后台,再次从图库分享 → 应用回到前台,UI 立即刷新(事件流生效)。
5. 点击 Record message 按钮 → recordSentMessage 持久化成功。

七、工作原理

理解冷/热两条路径,排查问题会容易很多:

┌──────────┐  分享面板选择目标应用   ┌───────────────────────────┐
│ 图库/浏览器 │ ──────────────────→ │ want(ohos.want.action.sendData)│
└──────────┘                      └────────────┬──────────────┘
                                               │
                            冷启动: launchWant   │   热启动: onNewWant 回调
                                               │
                                   ┌───────────▼────────────┐
                                   │ ShareHandlerOhosPlugin  │
                                   │ (AbilityAware, 宿主零改造)│
                                   └───────────┬────────────┘
                                               │ systemShare.getSharedData(want)
                                               │ 文本取 content / 文件按 UTD 分类
                                               │ 附件拷贝到 cacheDir/share_handler/
                                   ┌───────────▼────────────┐
                                   │ Dart: SharedMedia 解码   │
                                   │ getInitialSharedMedia /  │
                                   │ sharedMediaStream        │
                                   └────────────────────────┘

要点:

  • 插件通过 Flutter 的 AbilityAware 机制自动挂接 onNewWant 与冷启动 want,宿主 EntryAbility 保持模板代码即可,无需手写任何 want 处理。
  • 附件不是直接透传 URI:插件把只读 URI 拷贝到 cacheDir/share_handler/ 下,SharedAttachment.path 给的是可直接 File() 读的本地路径。要注意缓存可能被系统清理,要及时消费。
  • 文本类分享(文本/链接)直接从 want 数据取 content;文件类按 UTD 层级归属(belongingToTypes)归类为 image/video/audio/file 四类,与 SharedAttachmentType 枚举一一对应。

八、常用问题

Q1:分享面板里找不到我的应用?
检查 5.1 的 skills 是否已加入宿主 module.json5 且 utd 类型覆盖了分享内容的数据类型;重新全量构建(改 module.json5 后增量编译可能不生效)。

Q2:附件路径过会儿读不到了?
SharedAttachment.path 指向应用 cache 目录,系统可能清理,且分享内容是"读取即清"的。收到后应立即拷贝到自己的业务目录再做后续处理。

Q3:recordSentMessage 调了但分享面板没有"发送到会话"推荐?
鸿蒙端该接口是"持久化会话标识 + 分享时回带"的语义,没有 Android/iOS 的系统级推荐位。conversationIdentifier 会在用户分享到你的应用后随 SharedMedia 带回,在应用内用它定位会话即可。

Q4:同样的分享收到了两次?
getInitialSharedMediasharedMediaStream 是两条通道,冷启动那一次只会从 initial media 拿到;确认你的代码没有在冷启动路径上也去消费事件流的缓存。如需防重,可在消费后调用 resetInitialSharedMedia()

Q5:分享多个文件只收到部分?
检查 skills 条目的 maxFileSupported 是否大于实际文件数;插件端单次分享支持上限就是按这个值声明的(示例配置为 100)。

Q6:build hap 报 Schema validate failed(00303038)?
module.json5 中含 utd 的 uri 条目缺了 scheme 字段,补上 "scheme": "file"(见 5.1)。

九、结语与相关链接

share_handler 的鸿蒙适配补齐了 Flutter 生态在"接收系统分享"上的鸿蒙缺口:上游 API 完全对齐,跨平台代码无需任何 if (Platform.isOhos) 分支,唯一的平台差异是宿主要多一步 skills 声明(5.1)。


这篇笔记就先到这里,后面用到新的思路或者发现有问题再补充。

评论 (0)

暂无评论