本文主线落在 HarmonyOS 6.1.0(23) 的调用策略扩展,并顺带讲清算 6.0.0(20) 新增的视频选择器。文中的 API 名称、枚举取值与版本号来自华为官方文档;结构、代码示例、调用方向矩阵与自检清单为本人整理编写,未在真机逐行验证,涉及真机表现的部分以真机实测为准。
引子:一句话需求,三个隐藏前提
产品需求单上常出现这么一句:
在平板上填表,需要一张证件照,平板没好镜头,直接调手机的相机拍一张传回来。
听起来像"弹个菜单 + 选设备 + 收图片"。V哥按这个直觉开干,第一天就被泼了冷水——菜单弹出来,设备列表是空的。不是代码写错,是三个前提少了一个。
这里有个画面感:你按了按钮,系统应该弹出一个设备选择菜单,里面躺着同一账号下、开了 WLAN 和蓝牙的远端正列表。结果V哥的菜单空空如也,只有一个孤零零的"无可用设备"。那一刻最容易犯的错,是回头怀疑createCollaborationServiceMenuItems写错了、怀疑@Builder没挂对。其实接口一个都没错,是网络那头的灯没亮。
这篇就把这三个前提、谁能调谁、怎么收数据一次讲清,顺带把最容易混淆的版本线划明白。
一、先校准版本:别把 5.x 的写法当成 6.x 的能力
这是本选题最容易踩的坑。官方《跨设备互通开发指导》的"约束与限制"里,调用策略分了两截:
调用策略:PC/2in1 设备可以调用 Tablet 和 Phone,Tablet 可以调用 Phone;从 API 6.1.0(23) 开始,TV、Phone、Tablet 或 PC/2in1 设备可调用具备拍照、扫描及图库能力的远端设备。(Service Collaboration Kit 简介)
注意后半句——6.1.0(23) 之前,接口只能在 PC/2in1、Tablet 上正常调用,在 Phone、TV 上直接"无法展示设备列表、无法使用能力"。6.1.0(23) 之后,Phone 和 TV 也能当"主控"了。
V哥自己的判断:这套能力的"主控方"是被系统写死的,不是你能看见谁就调谁。6.1.0(23) 是它第一次把主控方从"平板/2in1"扩到"电视/手机也能发起"。所以写 6.0+ 的文章,主线就该落在这个扩展上,而不是只讲"平板调手机"这条老链路。
另一个 6.0 切口在过滤器上:createCollaborationServiceMenuItems的businessFilter在6.0.0(20)起新增了视频选择器(VIDEO_PICKER)与图文混合选择器(IMAGE_VIDEO_PICKER),之前只有拍照、扫描、图库三种。
顺带说两句排错心得:设备列表为空时,先按"三道门槛"排查,再对照调用方向矩阵——同类型设备互调是全版本都不行的,不是你权限没配对。这两张表对不上,代码写得再好也是白跑。另外,StateDialog 回调拿到的 ArrayBuffer 要先判空再解码,远端设备中途取消时它就是空的,这种情况该给用户一个"对方已取消"的提示,别当异常直接抛出去。
二、三道门槛:同账号、WLAN、蓝牙,缺一列表就空
不管你用 ArkTS 还是 NDK,设备列表能不能出来,先看这三件:
| 门槛 | 要求 | V哥的理解 |
|---|---|---|
| 华为账号 | 双端登录同一个华为账号 | 不是"都登录了"就行,是同一个。换账号列表直接空 |
| 网络 | 双端打开 WLAN | 建议接同一局域网,唤醒相机更快;蓝牙也得开 |
| 蓝牙 | 双端打开蓝牙开关 | 和 WLAN 是并列条件,不是二选一 |
双端设备需要登录同一华为账号;双端设备需要打开 WLAN 和蓝牙开关。(Service Collaboration Kit 简介)
第一个自创口诀:远端没登同一个账号,设备列表直接是空的——三件套缺一,列表就是空的。调试时别先怀疑代码,先看这三盏灯。模拟器不支持本能力,验证必须在真机双端上做。
三、调用方向矩阵:谁能动谁,系统说了算
把官方策略拆成一张"本端 → 远端"的表,配上V哥的使用建议:
| 本端(主控) | 可调用远端 | 版本边界 | V哥的理解 |
|---|---|---|---|
| PC/2in1 | Tablet、Phone | 5.0 起即支持 | 最稳的主控方,能力全开 |
| Tablet | Phone | 5.0 起即支持 | "平板调手机镜头"的经典链路 |
| TV | 有拍照/扫描/图库的 Phone、Tablet;有图库的 PC/2in1 | 6.1.0(23) 起 | 电视也能当主控,但被控方要带相机 |
| Phone | 同上(带相机/图库的远端) | 6.1.0(23) 起 | 手机反向调别家镜头,这是 6.x 新开的口 |
| 同类型设备(如手机调手机) | 不可调用 | 全版本 | 同形态互斥,别指望手机调手机 |
第二个自创判断:同类型设备不可互相调用,是一条硬规则,不是网络没连上。你想"用另一台手机补拍",系统不会给你列出来——这跟账号、网络都没关系。
四、ArkTS 主线:把设备选择器塞进 Menu
ArkTS 侧两个组件必须配合使用:createCollaborationServiceMenuItems(拿设备列表)和CollaborationServiceStateDialog(收远端状态)。前者是@Builder自定义构建函数,必须在Menu内调用。
// RemoteShoot.ets —— 自写的跨设备拍照封装(命名/注释均为本人整理)import{createCollaborationServiceMenuItems,CollaborationServiceStateDialog,CollaborationServiceFilter}from'@kit.ServiceCollaborationKit';import{image}from'@kit.ImageKit';// 复用官方约定的回传结果码:0 成功,其余为异常分支constCOLLAB_OK=0;constCOLLAB_PEER_CANCEL=1001202001;// 远端取消constCOLLAB_FRAMEWORK_ERR=1001202002;// 框架内部错误constCOLLAB_LOCAL_CANCEL=1001202003;// 本端取消@Entry@Componentstruct RemoteShootPage{@Statesnapshot:image.PixelMap|undefined=undefined;// 设备选择菜单:挂在 Menu 里,弹出时由系统拉起设备列表@BuilderdeviceMenu(){Menu(){// 只匹配"跨端拍照"能力;要图库可换成 IMAGE_PICKERcreateCollaborationServiceMenuItems([CollaborationServiceFilter.TAKE_PHOTO]);}}// 把远端回传的 ArrayBuffer 解码成 PixelMapprivateasyncdecodeToPixelMap(buf:ArrayBuffer):Promise<image.PixelMap|undefined>{if(buf.byteLength===0){returnundefined;}try{constsource=image.createImageSource(buf);returnawaitsource.createPixelMap();}catch(e){console.error('解码远端图片失败');returnundefined;}}build(){Column({space:16}){// 状态弹窗:全局组件,挂页面即可,不占布局CollaborationServiceStateDialog({onState:(stateCode:number,bufferType:string,buffer:ArrayBuffer):void=>{if(stateCode===COLLAB_OK&&buffer.byteLength>0){this.decodeToPixelMap(buffer).then((pm)=>{this.snapshot=pm;});return;}console.info('跨设备互通结束,stateCode='+stateCode);}})Button('用手机镜头拍一张').bindMenu(this.deviceMenu)if(this.snapshot){Image(this.snapshot).width('80%').height(300).objectFit(ImageFit.Contain)}}.padding(20).width('100%')}}两个要点:createCollaborationServiceMenuItems还有个带canReceiveNumber(1~50)的重载,用来限制图库可选图片张数;businessFilter在 6.0.0(20) 起支持VIDEO_PICKER/IMAGE_VIDEO_PICKER,老版本传这两个值不会报错,但能力不匹配。
为什么这个组件必须是"放进 Menu"而不是你自己在页面画一个列表?因为设备列表的拉起、可信设备校验、对端应用唤起,全是系统统一兜底的——你自己画列表既拿不到可信设备,也绕不开账号/WLAN/蓝牙那三盏灯。换句话说,createCollaborationServiceMenuItems不是"给你一个 API 让你造界面",而是"把系统已经做好的设备选择器借你挂一下"。理解到这一层,你就不会想着去自定义列表样式了,那是系统不让动的。
五、收数据:onState 的三个参数别接反
CollaborationServiceStateDialog的核心就是onState回调,三个参数顺序是固定的:
stateCode:完成状态,0 成功,其余见上方常量;bufferType:回传数据类型,目前官方文档标注仅支持general.image;buffer:回传的原始数据,ArrayBuffer格式,失败时为空。
第三个自创判断:把"解码"当成一道独立关卡,而不是在 onState 里顺手写。远端回传的是裸ArrayBuffer,你要么用image.createImageSource转成PixelMap,要么自己落盘。数据坏了、为空、解码异常三种情况要分开处理——别一句 Toast 掩盖掉,否则用户只会说"拍了没反应"。
六、NDK 三件套:Get / Start / Stop
如果你的相机页是 C/C++ 渲染管线,用 NDK 更直接。头文件service_collaboration/service_collaboration_api.h,链接库libservice_collaboration_ndk.z.so,能力起点5.0.0(12)。核心三件套:
HMS_ServiceCollaboration_GetCollaborationDeviceInfos:按能力类型拿设备列表;HMS_ServiceCollaboration_StartCollaboration:拉起远端能力;HMS_ServiceCollaboration_StopCollaboration:主动取消。
// collab_entry.cpp —— 自写的 NDK 调用骨架#include"service_collaboration/service_collaboration_api.h"staticint32_tOnEventProc(ServiceCollaborationEventCode code,uint32_textra){return0;}staticint32_tOnDataProc(ServiceCollaborationEventCode code,ServiceCollaborationDataType type,uint32_tsize,char*data){return0;}voidshootFromPhone(){// 1. 先要设备列表:传 TAKE_PHOTO 能力,系统回匹配的远端ServiceCollaborationFilterType filters[]={TAKE_PHOTO,SCAN_DOCUMENT,IMAGE_PICKER};ServiceCollaboration_CollaborationDeviceInfoSets*info=HMS_ServiceCollaboration_GetCollaborationDeviceInfos(3,filters);if(info==nullptr||info->size==0){return;// 列表空:回头查三道门槛}// 2. 选第一台设备,构造回调ServiceCollaboration_SelectInfo task={TAKE_PHOTO,{0}};auto*dev=&(info->deviceInfoSets[0]);// deviceNetworkId 拷贝进 task,长度上限见 COLLABORATIONDEVICEINFO_DEVICENETWORKID_MAXLENGTHServiceCollaborationCallback cb={.OnEvent=OnEventProc,.OnDataCallback=OnDataProc};// 3. 拉起远端相机uint32_tid=HMS_ServiceCollaboration_StartCollaboration(&task,&cb);// 需要视频回传时,用 StartCollaborationV2(支持 IMAGE_VIDEO_PICKER 的远端)HMS_ServiceCollaboration_StopCollaboration(id);// 不再需要时取消}NDK 侧的ServiceCollaborationFilterType取值为TAKE_PHOTO=1、SCAN_DOCUMENT=2、IMAGE_PICKER=3、VIDEO_PICKER=5、IMAGE_VIDEO_PICKER=6;回传数据类型ServiceCollaborationDataType有IMAGE=1、VIDEO=2。要视频回传请用StartCollaborationV2,它是 6.0.0(20) 视频能力在 NDK 上的对应入口。
ArkTS 还是 NDK,怎么选?如果业务逻辑就在 ArkTS 页面里、要的也只是"弹菜单选设备 + 收一张图",那 ArkTS 两个组件足够,省去 NDK 工程化成本。只有当你本来就是 C/C++ 渲染管线(比如相机预览、图像处理全在 native 侧),或者要精细控制设备网络 ID、做视频流回传,才值得上 NDK。二者底层走的是同一套协同框架,能力范围一致,区别只在你代码停在哪一层。
七、上线自检清单
- 三道门槛:双端同一华为账号、WLAN、蓝牙都开了吗?模拟器不支持,必须在真机双端验证
- 调用方向合规吗?本端是不是在 6.1.0(23) 允许的主控范围(TV/Phone/Tablet/PC-2in1)?同类型设备互调会被系统拒
- 主版本够吗?要视频选择器(VIDEO_PICKER/IMAGE_VIDEO_PICKER)必须6.0.0(20)起;主控方含 TV/Phone 必须6.1.0(23)起
createCollaborationServiceMenuItems放在Menu里了吗?@Builder写对了吗?CollaborationServiceStateDialog挂在页面build里了吗?onState三参数顺序对了吗?buffer为空和"解码失败"两种情况分开处理了吗?bufferType只认general.image有兜底吗?- 图库多选场景,
canReceiveNumber落在 1~50 了吗? - NDK 侧链接
libservice_collaboration_ndk.z.so了吗?deviceNetworkId拷贝长度没越界吗? - 视频回传走了
StartCollaborationV2吗? - 远端取消(1001202001)、本端取消(1001202003)都有用户提示吗?
参考与出处
本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档,文中的结构、代码示例、调用方向矩阵与自检清单为本人整理编写:
- Service Collaboration Kit 简介(约束与调用策略)
- 跨设备互通开发指导
- CollaborationService(ArkTS API)
- 跨设备互通 NDK 开发指导
- service_collaboration_api.h(NDK C API)
最后一句:跨设备互通真正难的不是"调接口",是承认它有一张写死的主控表、有一组非黑即白的门槛。把"三件套缺一列表就空"刻进调试习惯,剩下就是把onState三个参数接对、把ArrayBuffer解码成图。