HarmonyOS社交通讯应用开发17: 跨设备拉取媒体(CollaborationService)

📅 2026/8/25 22:35:12
HarmonyOS社交通讯应用开发17: 跨设备拉取媒体(CollaborationService)
跨设备拉取媒体CollaborationService引言上一篇我们看了从本机相册选图这一篇升级到跨设备用户手机上没有合适的图片能不能直接从平板、电脑甚至另一台手机里挑HarmonyOS 提供了CollaborationService跨端协同服务能力配合hms.collaboration.service三方库可以在应用里直接嵌入选择其他设备上的文件的系统级入口——选中的文件会被系统传输到当前设备应用只需接收即可。ContinuePublish 发布页正是用了这个能力点击虚线加号弹出的菜单里除了本地设备PhotoViewPicker 选图还会出现由系统动态生成的跨设备菜单项当用户从其他设备选定媒体后数据通过CollaborationServiceStateDialog的回调进入doInsertMedia被解析成图片或视频加入编辑区。本篇将基于项目真实代码把菜单如何生成、数据如何接收讲清楚。说明项目代码里只包含菜单创建与数据接收两部分跨设备传输本身由系统/三方库完成这也是该能力设计的初衷——应用无需自己实现设备发现、连接、传输。本篇不会编造项目之外的 API只基于AddMedia.ets中真实出现的用法展开。知识点讲解canIUse能力查询canIUse(SystemCapability.Collaboration.Service)是 HarmonyOS 提供的能力查询函数参数是能力标识SystemCapability返回该设备当前系统是否支持这项能力。写法形如canIUse(系统能力名)。能力名采用SystemCapability 域名 能力名的层级命名——这里域名是Collaboration协同能力名是Service服务合起来就是跨端协同服务。在写跨设备功能前先做能力查询是官方推荐的能力降级写法——支持就在菜单里加跨设备入口不支持就静默隐藏保证应用在低版本或精简设备上不会报错。同样的canIUse调用还出现在FileUtil.fileCopy里SystemCapability.DistributedDataManager.CommonType说明这套先探测、后使用的模式在工程里是通用约定。跨端协同服务是什么CollaborationService跨端协同服务解决的是这样一个场景用户正在手机上编辑却想引用平板/电脑上的一张图或一段视频。传统做法需要应用自己实现设备发现 → 建连 → 传文件一整套协议成本极高协同服务把这三步全部接管——应用调用createCollaborationServiceMenuItems生成入口系统负责弹出设备列表、引导用户选择远端文件、并通过安全通道把文件传输到本机。应用侧要做的只剩两件事把菜单项挂进自己的 UI以及接收传输完成的回调。这种系统承接重活、应用只做集成的模式是鸿蒙跨端能力设计的核心理念本篇开头那段代码正是它的最小示范。CollaborationService 菜单createCollaborationServiceMenuItemshms.collaboration.service库导出了createCollaborationServiceMenuItems作用是动态创建跨设备选择文件的菜单项。调用时传入过滤器数组Filter指定允许从其他设备选择哪类文件。CollaborationServiceFilter.ALL表示全部类型VIDEO_PICKER表示视频选择器专门用于挑视频。两个过滤器并列传入含义是这些类型的入口都要——与PhotoSelectOptions.MIMEType只能选一类的设计相比协同服务支持一次挂多个入口。数量上限最多可从其他设备拉取多少个文件。传MAX_ADD_MEDIA_NUM9与发布页整体上限保持一致选超了系统会按上限截断或拒绝。函数返回的菜单项直接放进Menu容器即可菜单项的点击、设备列表展示、文件传输都由系统处理——应用这边挂上去就不用管了。CollaborationServiceStateDialog接收跨设备数据与菜单配套的是CollaborationServiceStateDialog组件它提供onState回调onState: (stateCode: number, bufferType: string, buffer: ArrayBuffer) void当跨设备传输完成时回调被触发stateCode状态码非 0 表示失败/取消bufferType数据类型标识如general.image图片、general.video视频buffer数据本体。图片场景下是图片字节流可重建 PixelMap视频场景下是 UTF-8 编码的视频 URI 字符串。也就是说**这个 Dialog 是跨设备数据的着陆点**菜单负责把用户引导到其他设备选文件Dialog 负责把文件送到应用手里。结合本项目源码分析菜单组装MyTestMenu文件路径entry/src/main/ets/view/contentEditor/AddMedia.ets。//远程菜单。 Builder MyTestMenu() { Menu() {//菜单项一从本地设备选图 MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r(sys.symbol.picture_2)), content:$r(app.string.local_device)//Local Devices}) .onClick(() {//未达上限才允许继续添加if(this.mediaUriArray.length CommonConstants.MAX_ADD_MEDIA_NUM) { this.selectImage();//上一篇讲解的本地图库选择 }else{//已达上限弹 Toast 提示 this.getUIContext().getPromptAction().showToast({ message:$r(app.string.add_picture_prompt) }); } })//菜单项二跨设备拉取系统动态生成if(canIUse(SystemCapability.Collaboration.Service)) {//支持跨端协同创建从其他设备选择文件的菜单项 createCollaborationServiceMenuItems([CollaborationServiceFilter.ALL, CollaborationServiceFilter.VIDEO_PICKER], CommonConstants.MAX_ADD_MEDIA_NUM)//数量上限9} } }代码揭示了三层设计条件渲染canIUse(SystemCapability.Collaboration.Service)返回 true 才把跨设备菜单项加进Menu不支持该能力的设备上菜单只有本地设备一项功能完整可用——这是典型的能力降级。系统生成菜单createCollaborationServiceMenuItems的两个参数是过滤器与数量上限。传入[ALL, VIDEO_PICKER]表示图片、视频都可以从其他设备选上限传MAX_ADD_MEDIA_NUM9与整体媒体数量上限保持一致。菜单项的图标、文案、点击行为由系统按 HIG人机交互指南规范生成应用无需操心。菜单挂载位置这个菜单绑定在虚线加号上——addDefaultPic()的.bindMenu(this.MyTestMenu())详见下一篇用户点加号即弹出。数据着陆CollaborationServiceStateDialog doInsertMediaMyTestMenu是出口CollaborationServiceStateDialog是入口。它在AddMedia的 build 里注册build() { Column() { // 跨设备数据接收器媒体传输完成后回调onState CollaborationServiceStateDialog({onState: (stateCode: number, bufferType: string, buffer: ArrayBuffer): void this.doInsertMedia(stateCode, bufferType, buffer) }) this.addMedia(); // 图片视频展示区 } ... }doInsertMedia处理收到的数据是跨设备媒体的分拣中心// Remote images or videos fall into.doInsertMedia(stateCode:number,bufferType:string,buffer: ArrayBuffer): void {// 状态码非 0失败/取消直接忽略if(stateCode !0) { return; }// 数量已达上限也不再接收if(this.mediaUriArray.length CommonConstants.MAX_ADD_MEDIA_NUM) { return; }if(bufferTypegeneral.image) {// 图片从字节流重建 PixelMapletimageSource image.createImageSource(buffer); imageSource.createPixelMap().then((pixelMap) {letuuid util.generateRandomUUID();// 随机名避免重名this.PixelMapToBuffer(pixelMap,uuid);// 编码写入分布式文件this.mediaUriArray.push({ imagePixelMap: pixelMap, mediaName: uuid, mediaType: MediaType.MEDIA_IMAGE }); imageSource.release(); }) }elseif(bufferTypegeneral.video) {// 视频buffer 是 UTF-8 编码的 URI 字符串letuuid util.generateRandomUUID();letdecoder util.TextDecoder.create(utf-8);letvideoUriStr decoder.decodeToString(newUint8Array(buffer));// 把远端视频复制进分布式目录URI 形式writeDistributedFile(this.context,uuid, MediaType.MEDIA_VIDEO,undefined,videoUriStr); this.mediaUriArray.push({ videoUri: videoUriStr, mediaName: uuid, mediaType: MediaType.MEDIA_VIDEO }); } }几个实现细节值得记录双重防护stateCode ! 0挡掉失败与取消mediaUriArray.length 9挡掉超量。两道闸门之后才进入真正的数据处理避免无谓的资源消耗。图片走字节流general.image的 buffer 是图片编码数据用image.createImageSource(buffer)创建图像源、createPixelMap()异步解出 PixelMap 用于 UI 展示再交给上一篇讲过的PixelMapToBuffer落盘写入分布式文件目录为接续做准备。注意imageSource.release()释放资源避免句柄泄漏。视频走 URIgeneral.video的 buffer 其实是一段文本——视频的 URI。代码用util.TextDecoder.create(utf-8)解码成字符串然后调用writeDistributedFile的视频分支把源 URI 指向的文件复制进分布式目录。回想FileUtil.ets中该函数的签名正是(context, uuid, MEDIA_VIDEO, undefined, videoUriStr)——第 4 个参数buf传 undefined、第 5 个参数uri传视频地址内部走fileIo.copyFileSync复制文件}elseif(mediaTypeMediaType.MEDIA_VIDEOuri) { srcFile fileIo.openSync(uri,fileIo.OpenMode.READ_ONLY); fileIo.copyFileSync(srcFile.fd,file.fd);// 把远端视频复制到分布式目录}统一命名图片和视频都用util.generateRandomUUID()生成随机文件名避免与本地图库选出的displayName冲突也让后续接续打包时不依赖用户命名。与本地选图的分工现在可以对比一下发布页两条加媒体通路通路入口数据来源接收方式落盘本地选图菜单Local Devices本机相册PhotoViewPicker 返回 URI缩略图 JPEG跨设备拉取系统生成菜单项其他设备文件CollaborationServiceStateDialog.onState图片字节流 / 视频复制两者殊途同归最终都生成MediaInfo推入mediaUriArray由 AddMedia 的横向 List 统一展示。这种入口多样、出口统一的设计让后续所有处理展示、复制、接续打包只需面对一种数据结构。资源管理细节doInsertMedia里有两处容易被忽略的资源管理letimageSource image.createImageSource(buffer); imageSource.createPixelMap().then((pixelMap) {...imageSource.release();// 用完释放 ImageSource 句柄})ImageSource图像源持有底层解码资源用完必须release()否则长时间跨设备拉取媒体会造成句柄泄漏。类似的释放动作在工程里反复出现uri2pixelMap里imageSourceApi.release()、FileUtil.fileCopy里imageSourceApi.release()以及fileIo打开的文件在finally里closeSync——**凡是打开的系统资源都要配对关闭** 是本工程贯彻最彻底的纪律之一。另外图片与视频都使用util.generateRandomUUID()生成随机名跨设备传过来的文件没有可靠的展示名传输层只给字节流/URI随机 UUID 既避免重名覆盖又避免把不可信的文件名拼进路径引发安全问题。对比本地选图用displayName截取命名两条通路在命名策略上的差异反映了数据可信度的不同——本地数据可信任远端数据一律改名收编。交互流程还原把菜单与接收器串起来一次完整的跨设备拉取体验是点击虚线加号 → bindMenu 弹出 MyTestMenu → canIUse 探测通过 → createCollaborationServiceMenuItems 追加系统菜单项 → 用户点击系统菜单项 → 系统弹出设备列表本机无法直接干预 → 用户在远端设备选择图片/视频 → 系统安全通道传输 → CollaborationServiceStateDialog.onState 回调stateCode0表示成功 → doInsertMedia 按 bufferType 分拣 → mediaUriArray.push → 九宫格刷新注意用户点击菜单项之后应用便失联了——设备选择、文件传输全程在系统侧完成应用只能等onState的最终审判。这也解释了为什么stateCode的校验如此重要它是应用判断整个流程成败的唯一依据非 0 即放弃不猜原因、不重试重试应该由用户重新发起而不是代码自动循环。小结跨设备拉取媒体在本项目中呈现的是一个轻量接入样板应用侧只需要做两件事——挂菜单canIUse做能力探测createCollaborationServiceMenuItems让系统生成从其他设备选文件的菜单项收数据CollaborationServiceStateDialog.onState接收传输结果doInsertMedia按bufferType分拣图片/视频统一转换为MediaInfo进入编辑区。设备发现、连接、文件传输这些重活全部由系统协同服务承担应用复杂度被压到最低。对初级开发者而言记住能力查询canIUse 系统菜单createCollaborationServiceMenuItems 状态回调onState这个三步模式就掌握了跨端能力接入的通用套路。最后把这套模式与本地选图第 16 篇放在一起看能提炼出跨端开发的三个通用心法能力探测先行任何依赖系统/设备能力的代码入口处都要canIUse探测做不到优雅降级的功能迟早会变成崩溃现场系统能办的别自己办设备发现、文件传输、剪贴板读取PasteButton、相册选择PhotoViewPicker——凡有系统级入口优先复用既省代码又安全**异步回调要有终态**stateCode、err这类回调参数是流程的最终裁决宁可多校验一层也不要把失败当成功继续往下走。下一篇回到 UI 本身把 AddMedia 的图片视频展示区拆开看——列表布局、数量上限、复制粘贴菜单、虚线加号一应俱全。本文引用源码entry/src/main/ets/view/contentEditor/AddMedia.ets、entry/src/main/ets/utils/FileUtil.ets、entry/src/main/ets/constants/CommonConstants.ets