☰
鸿蒙Share Kit视频分享实战:从URI权限到分享面板的完整链路
2026/10/3 3:34:55 网站建设 项目流程

做鸿蒙版社区应用的时候,产品给我提了一个需求:用户看到帖子里的视频之后,想一键把视频转给聊天好友。我第一反应是“这不就是把视频地址塞进分享接口嘛”,但真动手才发现,鸿蒙的分享链路跟安卓完全不是一个套路——光一个分享类型映射就能让人绕半天。后来老老实实用 Share Kit 把流程跑通,才摸清里面那些隐藏规则。这篇是《鸿蒙学习实战之路 Share Kit 系列》的第 5 篇,专门聊分享视频内容这件事,适合已经在鸿蒙工程里写过业务代码、想给应用加系统分享能力的开发者参考。

1. 先把 Share Kit 在视频分享里的“分工”搞清楚

1.1 系统分享面板背后的调度逻辑

Share Kit 在鸿蒙里不是“一个帮你去发 QQ 发微信的工具”,它更像一个快递中转站。你的 App 把要分享的数据打包好,交给系统分享面板,面板根据数据内容、媒体类型和目标应用声明的支持范围,自动匹配合适的接收端。整个过程你的 App 不需要知道用户最终选了哪个应用,也不需要替目标应用准备数据格式,Share Kit 会做一次统一包装。

这个设计对视频分享特别重要。视频不像文本那样有个字符串就行,也不像图片那样系统直接读一遍就行,它涉及格式、编码、体积、权限多个环节。一旦这些环节没处理好,面板能弹出来,但用户在选择目标 App 之后看到的可能是一张“无法打开”的卡片,甚至啥都没有。

1.2 视频与文本、图片分享的三点关键差异

第一点,接收端对视频的识别依赖媒体类型。文本分享基本只分“纯文本”和“富文本”两类,图片分享也就 JPEG、PNG、GIF 这些。视频不一样,同样是一个文件,有的接收端希望它是“视频”可以预览播放,有的接收端只把它当作“普通文件”给个下载入口,有的接收端甚至根本不认这个类型,系统只能降级处理成文件。

第二点,临时文件的授权链路更敏感。分享图片时很多接收端会先读缩略图,真正下载原图靠自己的逻辑;分享视频时绝大多数接收端要拿完整文件,如果分享方给的是无权限的本地路径,读的时候就直接失败,而且失败表现往往很隐蔽,不报错、不提示,就是打不开。

第三点,体积和耗时明显影响体验。视频文件动辄几十上百 MB,在分享面板弹出前系统就可能做类型探测、读取头信息、生成展示摘要,这部分耗时比图片分享长得多。如果视频放在网络链接上,接收端还需要额外的网络拉取逻辑,所以参数里有没有正确的预览地址、标题、摘要,直接决定分享卡片好不好看、能不能确认来源。

1.3 什么时候不必上 Share Kit

我遇到过一些开发者把 Share Kit 当成万能方案,不管什么分享场景都用它。其实如果你的数据有固定的接收方,比如“点了这个按钮就一定要通过某个指定应用发送”,那直接用显式 intent 或者接入对方 SDK 更合适。Share Kit 的核心价值是“让用户自己选、按系统规则匹配”,如果你需要的是流程可控、接收方固定,反而会绕远路。

反过来,只要你的场景里出现“用户可能想发到微信、QQ、网盘、备忘录、蓝牙接收设备”这类不确定目标,就适合用 Share Kit。视频分享尤其典型,因为接收方的能力差异太大了,你自己维护接收方适配列表根本不现实。

2. 接入前的工程准备:权限、SDK 版本和真机校验

2.1 module.json5 里的隐私权限与授权方式

先回到工程配置。很多人以为分享视频要申请一堆读写存储的权限,实际上在 HarmonyOS NEXT 上,官方推荐用系统选择器来做文件授权,让用户明确选一次视频,拿到的是一个有授权范围的 uri。这样既满足合规要求,也能避免在应用市场上因为过度索权被卡。

如果你的业务确实需要主动读取相册里的视频列表,那要在module.json5里声明媒体读取权限:

{ "module": { "requestPermissions": [ { "name": "ohos.permission.READ_IMAGEVIDEO", "reason": "用于读取用户选择的视频并分享", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] } }

但我的建议是优先用PhotoViewPicker。它让用户在系统相册界面里选择,你拿到的是系统授权的 uri,不需要自己维护权限申请流程,后续分享到其他应用时也不会遇到“你这个 App 读得到,目标 App 读不到”的尴尬。

2.2 SDK 与真机系统版本匹配

Share Kit 的能力在 API 12 上已经比较完整,但它的稳定表现和系统版本强相关。我自己用 DevEco Studio 5.x 配合 HarmonyOS 5.0 真机跑通了一套流程,中间发现 4.x 上分享面板的行为、回调语义跟 5.x 有明显差异,这一点后面详细说。

所以建议工程里把compileSdkVersion提到当前 IDE 支持的最高版本,单真机调试选 HarmonyOS 5.0 以上。如果你还在老 SDK 上开发,代码里用到的一些高级字段可能编译不过,甚至编译过了运行也会因为系统不识别而静默失败。

2.3 用能力校验避免低版本直接 crash

分享面板不是每个老版本系统都稳的。我的习惯是在拉起分享前做一次系统能力预检,用一个 try/catch 包住首次调用。一旦发现当前系统不支持或者服务异常,就降级成“复制链接”或“保存到相册”的兜底方案,而不是让用户点击后没有任何反应。

这里有个容易被忽略的点:Share Kit 的校验不能只判断手机是不是华为、系统是不是 5.0,最好在运行时判断关键接口是否存在。开发时我见过同样的代码在 Mate 系列上正常、在部分老旗舰上直接抛“Service not found”的异常,这种差异只能在真机上提前测出来。

3. 分享视频的参数设计:ShareData 与链接媒体类型

3.1 在线视频:ShareData.link 带上的不止是地址

分享在线视频时,核心工作是构建一个ShareData,然后把视频地址填进link字段。写过一次你会觉得这就是“填个链接”,但实际差距很大:title、description、linkSummary这些字段决定接收端展示分享卡片的能力,不填的话很多端显示的就是一串光秃秃的 URL。

我目前用的示例大概是这样的:

import { shareKit } from '@kit.ShareKit'; let shareData: shareKit.ShareData = { title: 'XX产品体验视频', description: '一段真实上手视频,来自社区用户分享', link: 'https://yourdomain.com/video/detail?id=12345', linkSummary: '点击查看完整视频' }; // 给 ShareData 打上视频类型的标签,不同版本SDK对字段方式略有区别 // 以你IDE里shareKit的声明为准,我这里标的是运行时枚举 shareKit.setShareLinkMediaType(shareData, shareKit.LinkMediaType.VIDEO); let result = await shareKit.fulfillShare(shareData); if (result.code === 0) { console.info('分享面板成功唤起,用户完成了分享动作'); } else { console.error('分享失败,错误码: ' + result.code); }

如果你直接把视频地址写进 link 却不设置媒体类型,系统可能会把这段内容当普通网页处理,接收端打开后可能先展示标题摘要而不是直接播放。所以在线视频分享里,“链接地址 + 视频类型”是成对出现的。

3.2 本地文件:file:// 与 datashare:// 的取舍

本地视频分享是另一个套路。这里要认清一个事实:Share Kit 的link字段实际上放的是“统一资源标识”,不光是 http 地址,只要是系统能识别的 uri 都可以往里放。

从相册拿到的视频,一般是datashare://开头的媒体库 uri;从应用沙箱拿到的视频,一般是自己拼接的file://地址。两种 uri 我都试过,经验是:

  • datashare://适合直接从相册、媒体库选择后立刻分享,系统对这类 uri 有一套授权映射,接收端通过系统转发能拿到读取能力。
  • file://适合先把视频复制到应用自己的沙箱目录,再由你分享出去。这种地址语义简单,接收端读起来直接,但你必须保证文件的临时授权能传过去,否则对方一样打不开。

我踩过的坑是用文件管理器的路径直接拼了一个file:///storage/...塞给 Share Kit。面板能弹出来,但接收端完全没权限读这个文件。后面会讲这个问题的完整排查过程。

3.3 缩略图和封面:视频分享比图片分享更需要它

千万注意,系统分享面板对视频的“摘要展示”依赖封面。如果你分享的线上视频链接是直接指向.mp4文件,很多接收端没法直接预览内容,卡片会变得很干。我建议给视频搞一个独立的落地页,并在页面里放og:video、og:image之类的元信息,这样分享到大部分应用时能带出视频信息和封面图。

本地视频同理。如果视频本身没有生成封面,分享后卡片就只剩标题和文件大小,用户根本看不出这是个视频。你需要提前给视频抽一帧作为封面图,放到应用缓存目录,再通过系统支持的字段把封面一起带过去。

3.4 回调结果怎么判断真正分享成功

fulfillShare的返回码为 0 只代表“分享面板被正常唤起,用户完成了这次分享动作”,不代表对方真的把视频成功收下。你要理解这层含义:从你调用到面板弹出,你的 App 只是把数据交给了系统,后续发送环节由系统跟接收端协作,你无法也不应该控制。

所以不要拿回调结果做数据埋点里的“分享成功数”,它更多代表“用户完成了分享动作”。如果你要在产品层面统计“对方真正收到”,得靠接收端自己的回调或落地页的访问数据来验证。我是在做落地页之后才发现,回调成功和对方点开视频之间差了很远的距离。

4. 本地视频从选片到分享面板的完整链路

4.1 用 PhotoViewPicker 选视频的推荐写法

本地视频分享最标准的路径就是让用户先选视频,再拉起分享面板。选视频我推荐直接用系统选择器,代码量小、权限问题少:

import { photoAccessHelper } from '@kit.MediaLibraryKit'; async function pickVideo(): Promise<string> { let picker = new photoAccessHelper.PhotoViewPicker(); let result = await picker.select({ MIMEType: photoAccessHelper.PhotoViewMIMEType.VIDEO_TYPE, maxSelectNumber: 1 }); if (!result.photoUris || result.photoUris.length === 0) { return ''; } return result.photoUris[0]; }

拿到 uri 之后,我一般会先打印一下它的前缀,确认到底是datashare://还是file://。这一步听起来多余,但它能帮你在排查分享问题时缩小范围,因为两种 uri 对接收端的授权路径完全不同。

4.2 拿到的 uri 为什么不能直接到处传

用户选完视频后,你手上的 uri 是在“你的 App 有读取权”的前提下返回的。它不代表任何其他 App 读到,更不代表分享目标能直接读。很多初学者把 uri 直接塞给 Share Kit,面板正常弹出来,结果选择方打不开,然后怎么查都查不出代码问题,因为他们没意识到“自己能读”和“对方能读”是两码事。

我的做法是:如果视频不大、允许临时处理,就先把它复制到应用沙箱的缓存目录,用沙箱里的file://uri 分享;如果视频来自媒体库且不方便复制,就依赖媒体库系统对 urite 的授权扩展,看看当前 SDK 是否提供临时的持久化授权接口。一句话结论:不要裸传媒体库 uri,除非你确认当前系统版本能自动完成授权转发。

4.3 大视频与低内存机型上的预处理

超过 100MB 的视频文件,在分享链路里体验会明显变差:系统在读取文件头、生成摘要时会出现明显卡顿,接收端也可能因为体积限制直接拒绝响应。做产品的人可能在 UI 上设计了“一键分享原片”,但工学上的现实是,原片分享除了特大型网盘类目标端,很多聊天工具都会压缩、转码或直接失败。

所以我在工程里加了一道预处理:超过 50MB 的视频先提示用户“是否压缩后分享”,压缩用系统自带的视频编辑能力或者调用硬件编码器转成低码率版本。这道逻辑在 6.0 的新机型上不算成本,但在老机型上能明显降低分享失败率。

4.4 最后一步:唤起分享面板的完整示例

选完视频、确认 uri 有效之后,就可以把它交给 Share Kit 了:

import { shareKit } from '@kit.ShareKit'; async function shareLocalVideo(uri: string, title: string) { let shareData: shareKit.ShareData = { title: title, description: '来自我的鸿蒙应用', link: uri, linkSummary: '这是一个视频文件' }; shareKit.setShareLinkMediaType(shareData, shareKit.LinkMediaType.VIDEO); try { let result = await shareKit.fulfillShare(shareData); if (result.code === 0) { // 用户完成了分享动作 } else { // 查看错误码,做对应提示 } } catch (error) { // 这里处理系统服务异常,最好降级为复制链接或保存文件 } }

这里需要你留意自己 IDE 里ShareData的类型声明,不同 SDK 版本里媒体类型字段的赋值方式可能从属性变成了方法,但核心逻辑是一样的,就是把视频标识清楚、把链接填对、再交给系统。

5. 我在真机上踩过的几个坑(带排查过程)

5.1 坑一:分享面板能打开,对方却收到打不开的视频

现象特别迷惑:在自己的 App 里点击分享,面板正常弹出,选择微信后提示已发送,但对方点开视频就是转圈、黑屏、甚至直接显示“文件已失效”。

排查链路如下。第一步,把同一个 uri 粘贴到系统文件管理器里直接打开,确认视频文件本身没坏。第二步,从系统相册里选同一个视频,用系统自带的分享入口发给同一个接收端,发现对方能正常打开。这一步已经把范围缩小到“系统自带分享和我的业务分享之间参数不一致”。第三步,我打印出系统自带分享和我这边 ShareData 的差异,发现系统分享的 link 指向的是一个带短时权限令牌的 uri,而我这边塞的是原样媒体库 uri。第四步,把分享参数改为应用沙箱里复制后的file://uri,问题解决。

这个坑告诉我们:接收端读不读得到文件,取决于你的 uri 带了多少权限信息,而不是取决于文件本身是否有效。

5.2 坑二:本地视频 uri 在回调里返回错误码

还有一次是fulfillShare直接抛了错误,辅助排查时发现 uri 指向的文件已经被清理了。原因是我的业务逻辑在分享前先压缩视频,压缩后把临时文件写进cache目录,但分享回调还没有回来,缓存目录就被系统或我的定时任务清掉了一部分。

排查时我先在回调里打印 uri,再用fs.access去判断文件是否存在,发现文件确实没了。后来我把临时文件的清理时机改了:不是压缩完就删除,也不是放在统一清缓存的逻辑里,而是等分享面板关闭、确认业务不再需要这个文件后再删。如果你也遇到类似问题,先确认文件生命周期,别一上来就怀疑 Share Kit。

5.3 坑三:接收端不识别 VIDEO 类型,静默转成普通文件

有一类接收端对系统分享映射表做得很窄,只识别自己能处理的 mime 类型。你的 ShareData 明明是视频类型,对方也可能只当成一个普通附件或网盘文件链接来接收。这种现象在微信、钉钉这类自成生态的 App 里最明显。

排查方法很简单:用同一个参数分别分享到几个不同目标端,观察每个端接收后的展示。如果有的端能直接播放、有的端只显示文件名,那就不是你的问题,而是接收端能力差异。这种差异改不了系统,只能做业务降级:对不支持的目标端,及时给出“对方可能会收到链接,建议在浏览器中打开”的文案提示。

5.4 坑四:多系统版本下分享链路行为不一致

我在 HarmonyOS 4.x 和 5.0 上做了对比。同样一段代码,4.x 的分享面板弹出稍慢,回调code的语义跟 5.0 也不是完全对齐。有一版代码我用“回调为 0 就上报分享成功”,结果在 4.x 上因为用户中途取消也会走到 0,导致上报数据虚高。

排查办法是把分享面板的“用户点击了分享目标”和“用户取消了面板”分开判断,不同系统版本按返回结果里的详细状态码去区分,不要图省事只判断一个code === 0。这件事在同一个工程里同时兼容 4.x 和 5.x 的时候尤其明显,建议在接入初期就把版本兼容测试排进计划。

6. 我做视频分享沉淀下来的几条实操习惯

视频分享跟文本、图片分享的最大差别,是“链路长了太多”。文本分享你只管文字内容,图片分享你只管文件路径,视频分享你要管封装格式、授权范围、封面展示、体积大小、接收端兼容、结果回调语义,一环不行整个就崩。所以我现在做这块,一定会先用线上视频走通完整链路,再做本地文件分享,最后才优化封面、压缩这类体验项。

如果你正在接入 Share Kit 分享视频,我给两个具体建议:

  • 优先用“在线链接 + 视频落地页”的方式分享,链路最干净,接收端兼容性也最好。
  • 本地视频分享一定要对 uri 做权限预处理,并反复在 5.0 真机上看接收端实际读取情况。

我自己的工程里,最终把视频分享拆成了三条分支:秒开在线视频走链接分享,小文件走本地沙箱复制后分享,大文件先提醒压缩再走分享链路。这样跑下来,产品那边再也没因为“分享打不开”“分享没反应”来找过我。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询