欢迎加入 CPF-Flutter 鸿蒙社区: CPF-Flutter - 开源代码托管,代码协作 - AtomGit

写完一篇攻略、挑好一件商品,想让用户顺手把它转到微信、备忘录或者邮件里,应用自己得先把内容递给系统分享面板。面板是系统现成的,应用要做的只是把要分享的东西交出去。在鸿蒙应用里做这件事,可以接一个现成的分享库。

这次选了 share_plus,做了一个叫 ShareDeck 的小 Demo。功能很集中:左边是输入区,写好分享文本和标题,点"分享文本"大按钮就拉起系统分享面板;右边一组预设内容的快捷按钮,链接、多行周报、现生成的 txt 文件,点了直接分享;底部一张结果卡片,记录每次调用返回的 ShareResult。活动分享、邀请好友、内容转发这类功能,都可以照这个方式接。

ShareDeck 的完整源码已经放到 AtomGit,文章里的分享台和测试代码都在这里:

项目仓库: sharedeck - AtomGit

分享功能用的是 AtomGit 上 CPF-Flutter 维护的鸿蒙版本(flutter_plus_plugins 聚合仓库里的 share_plus 包),仓库链接统一放在文末。接入代码不多,这次花时间核对的有两块:一是主包和平台接口包要指到同一个提交;二是这个库的鸿蒙实现走的是华为系统分享 kit,编译 SDK 的选择和别的插件不太一样,第一次构建就栽在这里。下面把实际跑通的配置和过程记下来。

环境和设备

还没搭好 Flutter 鸿蒙环境的话,可以先看《Flutter OHOS 开发环境搭建指南》,链接见文末。

我这边用的环境如下,测试日期9月12号。

项目版本
Flutter3.35.8-ohos-1.0.3,channel [user-branch]
Dart3.9.2
DevEco Studio26.0.0(本机 E:\DevEco Studio
编译 SDKOpenHarmony SDK 26.0.0;另用到 DevEco Studio 自带的 HarmonyOS SDK 26.0.0(本机 E:\DevEco Studio\sdkshare_plus 编译必需,原因见 FAQ)
验证方式Pura 90 Pro 7.0.0(26.0.0)

先把依赖选对

这次用的是 share_plus 10.1.1,固定到提交 55de300a8627c55cd45ac86e6a26bcae8e0ca4cf(分支 br_share_plus-v10.1.1_ohos)。它在聚合仓库里的路径是 packages/share_plus/share_plus。下面的配置可以直接放进应用的 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  share_plus:
    git:
      url: https://atomgit.com/CPF-Flutter/flutter_plus_plugins.git
      ref: 55de300a8627c55cd45ac86e6a26bcae8e0ca4cf
      path: packages/share_plus/share_plus
​
dependency_overrides:
  share_plus_platform_interface:
    git:
      url: https://atomgit.com/CPF-Flutter/flutter_plus_plugins.git
      ref: 55de300a8627c55cd45ac86e6a26bcae8e0ca4cf
      path: packages/share_plus/share_plus_platform_interface

执行:

flutter pub get

share_plus 在两个地方容易配错。

一处是 pathflutter_plus_plugins 是装了一大堆 *_plus 插件的聚合仓库,不能只写仓库地址,share_plus 要落到 packages/share_plus/share_plus 这个子目录。

另一处是 dependency_overrides。只加主包不加覆盖时,Pub 会去 pub.dev 拉 share_plus_platform_interface,和鸿蒙版主包可能不配套。把平台接口包也指到同一个提交,flutter pub get 之后在 pubspec.lock 里能看到两个包的 resolved-ref 都是 55de300…,主包 10.1.1、平台接口 5.0.1,来源一致。以后升级依赖时,这两个包要一起检查。

接口一共三个,外加一个返回值

share_plus 对外就 Share 这一个类,三个静态方法:shareshareUrishareXFiles。没有要订阅的流,也没有可开关的状态,每次调用就是把内容递出去,等系统分享面板处理。

鸿蒙端底层调的是华为系统分享 kit @hms.collaboration.systemShare:把内容组进一个 SharedData,再交给 ShareController.show() 弹出系统分享面板。Dart 端先导入:

import 'package:share_plus/share_plus.dart';

接口一:分享文本,调用 share

// text 是正文;subject 是选填的标题,建议始终传
final ShareResult result = await Share.share(text, subject: title);

返回 Future<ShareResult>subject 在邮件类分享目标里会变成主题;鸿蒙端的实现里它被直接拿来当分享记录的标题(源码里就是 title: subject!),传空有风险,后文 FAQ 单说。

接口二:分享链接,调用 shareUri

final Uri uri = Uri.parse('https://atomgit.com/CPF-Flutter/flutter_plus_plugins');
final ShareResult result = await Share.shareUri(uri);

参数是一个 Uri 对象,不是字符串。链接类内容各平台会自己做解析,比如 iOS 会抓网页图标放进分享面板。鸿蒙端目前把这个链接转成字符串、按纯文本放进分享记录,所以观感和接口一接近,但语义上还是建议链接走接口二,将来端上实现升级不用改调用方。

接口三:分享文件,调用 shareXFiles

// 从内存直接造一个 txt,插件会先落到临时目录再分享
final XFile file = XFile.fromData(
  utf8.encode(content),
  name: 'sharedeck-notes.txt',
  mimeType: 'text/plain',
);
final ShareResult result = await Share.shareXFiles(
  [file],
  subject: 'ShareDeck 笔记',
  text: '一份顺手生成的 txt 笔记',
);

文件列表是 List<XFile>,可以一次塞多个;可选参数 text 会作为附带的文字一起分享。鸿蒙端的处理要多一步:每次分享前把 cacheDir/share_plus 这个缓存目录清空,把要分享的文件拷进去,再用 fileUri.getUriFromPath 转成 uri 挂到分享记录上,接收方拿到的是拷贝,不是原文件。

返回值怎么读。 ShareResult 有两个字段:raw 是平台返回的原始字符串,status 是三个取值的枚举 ShareResultStatus

枚举含义
ShareResultStatus.success用户选了某个分享动作
ShareResultStatus.dismissed用户直接关掉了分享面板
ShareResultStatus.unavailable当前平台拿不到分享结果

有一点要先说清楚,免得写业务时踩坑:这个版本的鸿蒙端目前拿不到真实的分享结果。翻源码能看到,Dart 侧发出的方法名是 share/shareFiles,鸿蒙侧只在方法名带 WithResult 时才监听面板关闭事件,两边名字对不上,于是调用立即返回 ShareResult.unavailable。也就是说在鸿蒙上,结果回调暂时只能当"面板已拉起"处理,不要拿 status 做业务分支。这条我对照源码确认过,面板上的真实表现还需在真机补实测。

接到分享台上

ShareDeck 就一个主页:左侧输入区一张卡片,装着分享文本输入框、标题输入框和一个"分享文本"大按钮;右侧一张预设内容卡片,链接输入框加三个快捷按钮;窄屏时两卡上下排,宽屏时左右排,用的是 LayoutBuilder 按宽度切布局。底部常驻一张结果卡片,显示最近一次调用的接口名、statusraw

所有分享按钮最后都汇到同一个方法里,调接口、等返回、把结果写进卡片,错误也在这里兜住:

Future<void> _runShare(
  String action,
  Future<ShareResult> Function() call,
) async {
  try {
    final ShareResult result = await call();
    if (!mounted) return;
    setState(() {
      _lastAction = action;
      _lastDetail = 'status = ${result.status.name},raw = "${result.raw}"';
      _hasResult = true;
    });
  } on PlatformException catch (e) {
    // 系统分享服务没起来、参数被拒这类错误都会走到这
    if (!mounted) return;
    setState(() {
      _lastAction = action;
      _lastDetail = '调用失败:code=${e.code},message=${e.message}';
      _hasResult = true;
    });
  }
}

四个按钮各自只是往里传不同的闭包,比如大按钮这边,文本为空时直接拦下弹提示,不发起调用:

Future<void> _shareText() async {
  final String text = _textCtrl.text.trim();
  if (text.isEmpty) {
    _toast('先写点内容,空的没法分享');
    return;
  }
  await _runShare(
    'Share.share(文本)',
    () => Share.share(text, subject: _subjectOr('ShareDeck 分享')),
  );
}

_subjectOr 是个兜底:标题输入框留空时传默认标题。因为鸿蒙端拿 subject 直接当分享记录标题,Demo 里不给空值机会。

这是分享台的初始样子,宽屏下输入区和快捷区左右两栏,底部结果卡片显示"还没分享过":

点"分享文本"后,系统分享面板从底部弹起,输入的正文和标题都出现在面板里,选一个分享目标就能发出去:

关键文件就一个:lib/main.dart,页面布局、输入校验、调用和结果展示全在这一个文件里。share_plus 不需要订阅状态流,也就没有 dispose 里取消订阅的负担,控制器照常释放即可。

下载源码,签名后装到手机

可以直接把 ShareDeck 拉下来运行,依赖已经写进工程:

git clone https://atomgit.com/gcw_uKGkQkkd/sharedeck.git
cd sharedeck
flutter pub get

注意两点。一是 Windows 命令行构建前要设 DEVECO_SDK_HOME 环境变量,指向 DevEco Studio 的 SDK 目录(IDE 里构建则不用管,DevEco 会自己带):

export DEVECO_SDK_HOME='E:\DevEco Studio\sdk'

二是在 DevEco Studio 里完成调试签名:

  1. 用 DevEco Studio 打开 ohos 目录。

  2. 进入 File → Project Structure → Signing Configs

  3. 勾选 Automatically generate signature,给当前工程和设备生成签名,点 Apply/OK

签名完成后构建、安装:

flutter build hap --release
hdc list targets
hdc -t <设备ID> install -r build/ohos/hap/entry-default-signed.hap
hdc -t <设备ID> shell aa start -a EntryAbility -b com.example.sharedeck

平时调页面需要热重载,可用 flutter run -d <设备ID>

手机上实际试了什么

本机静态检查与测试结果:

  • flutter analyze:通过,无问题。

  • flutter test:冒烟测试通过(两条用例:验证分享台页面正常创建、三个接口按钮都渲染到位;以及清空文本点大按钮只弹提示、不发起调用)。

  • DEVECO_SDK_HOME='E:\DevEco Studio\sdk' 下执行 flutter build hap --debug --no-codesign:构建成功,产物 build/ohos/hap/entry-default-unsigned.hap

模拟器上逐项操作后的结果如下:

操作预期结果
进页面双栏布局,输入框有默认文案,底部显示"还没分享过"
清空文本点"分享文本"只弹 SnackBar 提示,不拉起分享面板
写好文本点"分享文本"拉起系统分享面板,标题和正文与输入一致
点"分享这条链接"面板出现链接内容,可选择备忘录等目标
点"分享多行周报"多行文本在面板预览中保留换行
点"生成 txt 并分享"面板出现 sharedeck-notes.txt 文件卡片
面板里选目标或直接关闭底部结果卡片显示 status = unavailable(鸿蒙端当前行为,见接口一节)

FAQ:接入时遇到的问题

CompileArkTS 报 Cannot find module '@hms.collaboration.systemShare'

我第一次构建就报了这个:Execution failed for task 'default@CompileArkTS',日志里还有一句 "@hms.collaboration.systemShare" ... could not be resolved。原因是 share_plus 的鸿蒙实现用的是华为系统分享 kit,@hms. 开头的模块在纯 OpenHarmony SDK(比如 C:\OHSDK)里根本不存在,连带的还有两个 arkts-no-any-unknown 报错,其实都是同一个根因。解决办法是把工程 ohos/build-profile.json5 里 product 的 runtimeOSOpenHarmony 改成 HarmonyOS,再让构建进程能找到带 hms 组件的 SDK。命令行构建要设环境变量 DEVECO_SDK_HOME 指向 DevEco Studio 自带 SDK(本机是 E:\DevEco Studio\sdk,里面 default/openharmonydefault/hms 成对出现)。注意 local.properties 里写 hwsdk.dir 是没用的,hvigor 会把这个键丢掉,只认 DEVECO_SDK_HOME 环境变量。改完重新构建就过了。

平台接口包从 pub.dev 解析,版本对不上

判断办法:flutter pub get 之后打开 pubspec.lock,搜 share_plus_platform_interface,看它的 source 是不是 gitresolved-ref 是否和主包一致。如果 source: hosted,说明 dependency_overrides 没配或 path 写错,指到聚合仓库的 packages/share_plus/share_plus_platform_interface、和主包同一个提交即可。改完要重新 pub get;涉及原生依赖的变化要重建 HAP,热重载不生效。

分享结果 status 一直是 unavailable

先看接口一节的源码分析:Dart 侧发的方法名和鸿蒙侧监听关闭事件的条件对不上,面板关闭后不会把结果回传,调用即刻返回 unavailable。这不是接错了,是这个版本的实现现状。业务上在鸿蒙端把分享当"发出即完成"处理就好,别写依赖 success/dismissed 的逻辑。如果后续版本修了这里,pubspec.lock 里的提交号会变,升级时重新验证一遍即可。

subject 传空会怎样

没实测面板表现,但源码里鸿蒙端构造分享记录写的是 title: subject!subject 的空值被直接塞给了标题字段,行为没有兜底。稳妥的做法是像 Demo 里那样自己兜底:输入框留空就传一个默认标题,永远不给端上递空值。

Git Bash 里构建报 BATCH RECURSION / ohpm install failed

在 Git Bash 里运行 bin/flutter 会把 OS 环境变量改写成 MINGW64_NT-...,DevEco 的 ohpm.bat 依赖 %OS%=="Windows_NT" 的判断,判断失败后内部一段批处理递归不收敛,直接爆栈。绕开的办法是直接调批处理入口:

PUB_CACHE='E:\OhProject\.pub-cache' /e/FLOH/flutter_flutter/bin/flutter.bat build hap --debug --no-codesign

用 PowerShell 或在 DevEco Studio 里构建则不受影响。

发现库的问题,怎么反馈

1. 提交 Issue,说明问题和复现步骤

从文末链接进入 flutter_plus_plugins 仓库,打开 Issues 页面,先搜索有没有相同的问题。如果没有,点击右上角的 新建 Issue,填写问题标题和复现信息。

报告时把 pubspec.yaml 的依赖配置和 pubspec.lock 里实际解析的提交号贴出来,说明预期什么、实际报什么错,以及 Flutter 版本、设备系统。像"分享结果回传"这种问题,最好附上面板操作步骤和日志。

2. Fork 项目,在自己的仓库中修改

如果问题能自己修,点击上游仓库右上角的 Fork。在 Fork 页面确认项目名称、自己的账号和要复制的分支,再点击 创建 Fork 项目

Fork 完成后,把自己的仓库克隆到本地,并添加上游仓库。下面以给 share_plus 的中文说明补一条 SDK 要求为例,<你的账号> 换成自己的 AtomGit 账号:

git clone https://atomgit.com/<你的账号>/flutter_plus_plugins.git
cd flutter_plus_plugins
git remote add upstream https://atomgit.com/CPF-Flutter/flutter_plus_plugins.git
git fetch upstream
git switch -c docs/ohos-share-plus-sdk-note upstream/br_share_plus-v10.1.1_ohos

改好后检查差异并推到自己的 Fork:

git diff --check
git add [想提交的文件]
git commit -m "docs: note HarmonyOS SDK requirement for share_plus ohos"
git push -u origin docs/ohos-share-plus-sdk-note
3. 创建 PR,把修改提交给上游

推送后回到自己的 AtomGit Fork 仓库,点击 同步源项目,选择包含修改的分支。有超前提交时,可以通过弹窗中的 创建并提交 PR 进入 PR 创建页面。

在 PR 页面核对源仓库和源分支,目标仓库选 CPF-Flutter/flutter_plus_plugins 上游,目标分支选 br_share_plus-v10.1.1_ohos。说明里写清原来哪里不对、改了什么、用哪个版本验证过;有对应 Issue 就关联上,确认差异后提交 PR。

相关链接

Logo

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

更多推荐