一、我们想解决什么问题
生活里的三个"卡壳"瞬间
先不说技术,聊几个你一定经历过的场景。
场景一:视频接力。你在地铁上用手机刷完了半小时的视频精华片段,回家后想舒舒服服用平板的大屏幕继续看同一个视频的后续内容。这时候你要怎么做?大概率是:打开平板 → 打开视频 App → 登录账号 → 搜索视频 → 拖进度条。步骤不少,而且如果 App 不支持登录多设备,还要忍受奇怪的提示。
场景二:文档接力。你在手机上随手拍了一张会议白板的照片,回到工位想用电脑上的修图工具处理一下。以前的选择是:微信文件传输助手、AirDrop、或者数据线直连。每一种都有点麻烦,尤其是当你身边没有 WiFi 的时候。
场景三:导航接力。你在车上用手机导航,快到家了但导航还在继续,下车后想把导航"甩"到手表上继续步行指引。想象一下这个画面——你拎着东西站在小区门口,手机屏幕亮了半天,定位飘来飘去,结果还得重新输入目的地。
这三个场景的共同点是:任务在一台设备上开始,但用户想在另一台设备上结束。没有跨设备流转,你就得手动"交接";有了跨设备流转,系统自动帮你把上下文传递过去,对用户来说感觉就像——视频从来没有停过。
技术上的"卡点"在哪里
好,有了需求,我们来看看技术上卡在哪里。
第一,设备发现。你要把视频传给平板,首先得知道平板在哪。手机周围可能有电视、耳机、音箱、平板……系统要能认出谁是你的"自己人",而不是邻居的设备。这需要一个可靠的设备发现机制。
第二,数据传递。视频文件动不动几百 MB,直接走蓝牙太慢,走云端太贵,最好能走局域网直连。更重要的是,传递的不只是文件本身——还有"播放到第几分钟第几秒"这样的状态信息。
第三,一致性。两台设备看到的内容要一致,手机上暂停了,平板上也得暂停;手机上切到了某个进度,平板上打开也要从同一个位置开始。
第四,权限与安全。不是所有设备都值得信任,也不是所有数据都应该被分享。HarmonyOS 通过"同一华为账号 + 同一网络"的双重校验来确保安全。
理解了这四个卡点,后面的设计你就明白为什么这么做了。
二、数据模型设计
先建模,再写代码
技术方案的第一步,永远是先把数据模型定义清楚。就像盖房子之前先画结构图,先把"有什么东西""东西长什么样"说清楚,后面的代码才有根可循。
我们不需要搞什么复杂的类继承,TypeScript 的 interface 就是干这个的——清晰、直观、够用。
设备信息:DeviceInfo
想象你走进一个会议室,里面坐了好几个人,你要先认识他们,才能决定跟谁合作。DeviceInfo 就是设备的"身份证":
// 设备信息接口interfaceDeviceInfo{deviceId:string;// 设备唯一标识,UUID 格式deviceName:string;// 用户给设备起的名字,如"小明的小米平板"deviceType:DeviceType;// 设备类型:手机/平板/PC/手表等networkType:NetworkType;// 网络类型:WiFi / 5G / 离线isTrusted:boolean;// 是否为可信设备(同账号且已授权)batteryLevel:number;// 当前电量,0~100}// 设备类型枚举enumDeviceType{PHONE='phone',TABLET='tablet',PC='pc',WATCH='watch',TV='tv',UNKNOWN='unknown'}// 网络类型枚举enumNetworkType{WIFI='wifi',CELLULAR='cellular',DISCONNECTED='disconnected'}流转会话:TransferSession
设备认识完了,接下来要建立一条"任务通道"。就像你和同事之间建一个工作群,群里定义好:这个任务从哪来、传给谁、传什么内容、进度如何。
// 流转会话接口interfaceTransferSession{sessionId:string;// 会话唯一 IDsourceDevice:DeviceInfo;// 发送端设备targetDevice:DeviceInfo;// 接收端设备contentType:ContentType;// 内容类型:视频/图片/文档/链接contentUri:string;// 内容 URI(可以是本地路径或分布式路径)contentMeta:ContentMeta;// 内容元数据status:TransferStatus;// 当前状态:准备/传输中/完成/失败progress:number;// 传输进度,0~100createdAt:number;// 创建时间戳expiresAt:number;// 会话过期时间(避免资源泄漏)}// 内容元数据interfaceContentMeta{title:string;// 内容标题duration?:number;// 视频/音频时长(秒)position?:number;// 播放位置(秒),用于视频续播fileSize?:number;// 文件大小(字节)mimeType:string;// MIME 类型}// 传输状态枚举enumTransferStatus{IDLE='idle',PREPARING='preparing',TRANSFERRING='transferring',COMPLETED='completed',FAILED='failed',CANCELLED='cancelled'}内容类型:ContentType
流转的内容五花八门,笼统传一个大文件包不够细致,我们需要细分:
// 内容类型枚举enumContentType{VIDEO='video',// 视频文件IMAGE='image',// 图片文件DOCUMENT='document',// 文档(PDF/Word/Excel 等)URL='url',// 链接(网页/App 页面)AUDIO='audio',// 音频文件LOCATION='location',// 地理位置(用于导航接力)}小结:模型设计的思路
你可能发现了,这三个接口层层递进:DeviceInfo解决"设备是谁"的问题,TransferSession解决"任务怎么跑"的问题,ContentType解决"传的是什么"的问题。先把架子搭好,后面的代码就像填空题一样简单。
三、核心设计决策
方案对比:为什么我们这样设计
技术选型不是"最先进最好",而是"最适合当前场景"。我们来看几个关键决策点。
决策一:传输协议选 SoftBus 还是 HTTP?
| 对比维度 | SoftBus(分布式软总线) | HTTP 轮询/推送 |
|---|---|---|
| 传输速度 | 内网极快(百兆文件秒传) | 依赖服务器,较慢 |
| 离线支持 | 同一华为账号可离线传递 | 必须有外网 |
| 开发成本 | HarmonyOS 原生封装,开箱即用 | 需要自建服务 |
| 适用场景 | 大文件(视频/图片) | 小数据(状态同步) |
我们的结论是:以 SoftBus 为主,HTTP 作为兜底。大文件走软总线,享受极致速度;小状态走轻量协议,保证可用性。
决策二:状态存储用本地还是分布式数据服务?
跨设备流转最怕的就是"手机停了、平板不知道从哪开始"。HarmonyOS 提供了分布式数据服务(Distributed Data Service),能让多台设备共享同一份数据,而且自动同步。
// 选型对比constisLocalOnly=false;// ❌ 只存本地:另一台设备拿不到状态constisCloudSync=true;// ⚠️ 云同步:慢,依赖网络,还收费constisDistributedDB=true;// ✅ 分布式数据库:本地化速度,跨设备同步决策三:发现设备用蓝牙还是 WiFi?
蓝牙功耗低但速度慢,适合配对阶段;WiFi 速度快,适合大数据传输。HarmonyOS 的做法是:先用蓝牙低功耗广播发现设备,再用 WiFi Direct 建立高速通道传输数据。扬长避短,效率最高。
决策四:用户体验上,推送制还是拉取制?
- 推送制(手机主动推给平板):手机控制感强,但平板可能不想收(比如正在打游戏)。
- 拉取制(平板主动从手机拉数据):平板自主可控,但需要平板主动发起。
- 我们的选择:用户确认 + 智能推荐。检测到用户在两台设备上都有同一个 App 时,弹出"是否继续在平板上观看?"的提示,让用户自己决定。
四、完整代码实现
代码文件一:设备发现服务 DeviceDiscovery.ets
这是整个流转的"入口"。我们用 HarmonyOS 提供的分布式能力,先找到附近的设备:
// DeviceDiscovery.ets - 设备发现服务importdeviceManagerfrom'@ohos.distributedHardware.deviceManager';classDeviceDiscovery{privatedmInstance:deviceManager.DeviceManager|null=null;privatedeviceList:DeviceInfo[]=[];// 初始化设备管理器initialize(){deviceManager.createDeviceManager('com.atomgit.app',(err,dm)=>{if(err){console.error('设备管理器创建失败:',err);return;}this.dmInstance=dm;this.startDiscovery();});}// 开始发现可信设备startDiscovery(){if(!this.dmInstance)return;// 获取同一华为账号下的可信设备列表consttrustedDevices=this.dmInstance.getTrustedDeviceListSync();this.deviceList=trustedDevices.map((device:any)=>({deviceId:device.deviceId,deviceName:device.deviceName,deviceType:this.mapDeviceType(device.deviceType),isTrusted:true,networkType:NetworkType.WIFI}));console.info('发现可信设备数量:',this.deviceList.length);}// 获取可用设备列表(过滤掉自己)getAvailableDevices():DeviceInfo[]{constmyDeviceId=this.dmInstance?.getLocalDeviceInfoSync()?.deviceId;returnthis.deviceList.filter(d=>d.deviceId!==myDeviceId);}privatemapDeviceType(type:number):DeviceType{constmap:Record<number,DeviceType>={0x00:DeviceType.PHONE,0x01:DeviceType.TABLET,0x02:DeviceType.PC,0x03:DeviceType.WATCH,0x04:DeviceType.TV,};returnmap[type]||DeviceType.UNKNOWN;}}代码文件二:流转服务 TransferService.ets
设备找到了,接下来就是建一条流转通道,把内容送过去:
// TransferService.ets - 跨设备流转服务importdistributedChannelfrom'@ohos.distributedHardware.channel';importfileIOfrom'@ohos.fileio';classTransferService{privatesession:distributedChannel.DistributedChannel|null=null;// 创建流转会话asynccreateSession(source:DeviceInfo,target:DeviceInfo,content:ContentMeta):Promise<TransferSession>{constsessionId=this.generateUUID();// 建立跨设备通信通道(走 SoftBus)this.session=awaitdistributedChannel.createSession({sessionName:`transfer_${sessionId}`,peerDeviceId:target.deviceId,dataType:distributedChannel.DataType.FILE,});return{sessionId,sourceDevice:source,targetDevice:target,contentType:ContentType.VIDEO,contentUri:'',contentMeta:content,status:TransferStatus.PREPARING,progress:0,createdAt:Date.now(),expiresAt:Date.now()+30*60*1000// 30 分钟后过期};}// 传输文件(带进度回调)asynctransferFile(session:TransferSession,filePath:string,onProgress:(pct:number)=>void):Promise<void>{if(!this.session)thrownewError('会话未建立');session.status=TransferStatus.TRANSFERRING;// 打开本地文件,通过分布式通道发送constfd=fileIO.open(filePath,fileIO.OpenMode.READ_ONLY);constfileSize=fileIO.statSync(filePath).size;letsent=0;constbuffer=newArrayBuffer(8192);letbytesRead=0;// 分块传输,每传完一块更新进度while((bytesRead=fileIO.read(fd,buffer))>0){awaitthis.session.write(buffer.slice(0,bytesRead));sent+=bytesRead;constpct=Math.floor((sent/fileSize)*100);onProgress(pct);session.progress=pct;}fileIO.close(fd);session.status=TransferStatus.COMPLETED;console.info('文件传输完成,sessionId:',session.sessionId);}privategenerateUUID():string{return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,c=>{constr=Math.random()*16|0;return(c==='x'?r:(r&0x3|0x8)).toString(16);});}}代码文件三:续播接力 VideoContinuity.ets
传完文件还不够,关键是"从哪继续看"。这段代码管理播放位置的记录和恢复:
// VideoContinuity.ets - 视频续播接力importdistributedKVfrom'@ohos.distributedKvStore';classVideoContinuity{privatekvStore:distributedKV.KVStore|null=null;privatereadonlyKV_ID='video_position_db';// 初始化分布式键值数据库asyncinit(){constoptions:distributedKV.Options={createIfMissing:true,kvStoreType:distributedKV.KVStoreType.SINGLEVERSION};this.kvStore=awaitdistributedKV.createKVStore(this.KV_ID,options);}// 记录当前播放位置(每次播放进度变化时调用)asyncsavePosition(videoId:string,position:number,deviceId:string){if(!this.kvStore)awaitthis.init();constrecord={videoId,position,// 秒为单位deviceId,// 记录是哪台设备保存的timestamp:Date.now()};awaitthis.kvStore.put(`pos_${videoId}`,JSON.stringify(record));console.info(`保存播放位置:${videoId}->${position}s`);}// 获取续播位置(打开视频时调用)asyncgetResumePosition(videoId:string):Promise<number|null>{if(!this.kvStore)awaitthis.init();constraw=awaitthis.kvStore.get(`pos_${videoId}`);if(!raw)returnnull;constrecord=JSON.parse(raw);// 只接受 24 小时内的记录(避免返回过时位置)constmaxAge=24*60*60*1000;if(Date.now()-record.timestamp>maxAge)returnnull;returnrecord.position;}// 清除记录(用户主动从头播放时调用)asyncclearPosition(videoId:string){if(!this.kvStore)return;awaitthis.kvStore.delete(`pos_${videoId}`);}}小结:三个文件的职责划分
设备发现找到"谁是我的小伙伴",流转服务负责"怎么把东西送过去",续播接力管理"到了新设备从哪接上"。三个模块各司其职,通过统一的 Session 数据结构串联起来。
五、深度技术原理
软总线:设备之间的"隐形高速公路"
好,理解了代码之后,我们来聊聊背后的原理。
想象一下:你的手机和平板之间,没有一根数据线,也没有云端服务器做中转,那它们是怎么"对话"的?
答案就是HarmonyOS 的分布式软总线(SoftBus)。
打个比方。普通的设备互联就像这样:你从深圳寄一个包裹到广州,先把包裹送到北京的总仓库(北京、上海、深圳三个仓库共用一个大系统),总仓库再转发到广州。绕了一大圈,速度慢,还贵。
软总线不一样,它更像是在深圳和广州之间建了一条隐形直达隧道。两座城市之间直接拉一根光纤,不需要绕到北京,速度快了好几倍。这条"隧道"可以走 WiFi,可以走 USB,甚至未来可以走蓝牙——但对开发者来说,这些细节完全被屏蔽了,你只需要"发送"和"接收",就像读写本地文件一样简单。
超级终端:信任关系的建立
你可能会问:万一邻居的手机偷偷往我平板上发东西怎么办?
这就涉及到"超级终端"的信任体系了。HarmonyOS 的设备信任关系建立在华为账号上。只有满足以下两个条件的设备,才能互相发现和通信:
- 同一华为账号登录。手机和平板登录的是同一个华为账号,系统认定这两台设备属于同一个人。
- 同一局域网或近距离。设备需要在同一个 WiFi 网络下,或者通过 HarmonyOS 的近场感知能力建立连接。
只有同时满足这两个条件,设备才会出现在"可用设备列表"里,你的视频才不会发错地方。
续播的原理:状态是如何跨设备保持的
你可能还有个疑问:手机上的播放位置,平板是怎么知道的?两台设备又没有共享内存。
这就靠Distributed KV(分布式键值数据库)了。
你可以把它理解为一个"共享笔记本"。手机在播放视频时,每隔几秒就在这本笔记本上写一行:“《流浪地球》第 42 分钟 37 秒”;平板打开同一个 App 时,去这本笔记本上一查——哦,原来上次停在这里,那我就从第 42 分 37 秒开始播。
这本"共享笔记本"在本地有一份副本,数据变化时会自动同步到同账号的所有设备上,速度很快,而且不需要云端服务器中转,数据走的就是前面说的那条"隐形直达隧道"。所以即便你坐飞机没有网络,只要手机和平板之前同步过一次位置(最后一次有网的时候),续播依然可以工作。
设备发现的两阶段策略
前面提到 HarmonyOS 用"蓝牙发现 + WiFi 传输"的组合策略,这里展开说说:
第一阶段:发现。设备通过蓝牙低功耗(BLE)广播自己的存在,就像在喊"我在这里!我叫小明的小米平板!“。手机收到广播后,根据华为账号信息判断是不是"自己人”,如果是就记录下来。这个过程功耗极低,手机搜半天也不怎么费电。
第二阶段:传输。确认目标设备可信后,切换到 WiFi Direct 或者同一个局域网,建立高速连接。这时候传输速度就上来了,1GB 的视频文件,在千兆局域网下几十秒就能传完。
BLE 负责"认识你",WiFi 负责"给你搬东西",两个协议各司其职。这是通信领域很常见的"控制面 + 数据面"分离思想,HarmonyOS 只是把它用在了分布式设备场景。
分布式键值数据库的工作机制
Distributed KV 之所以能做到"不需要云端服务器",关键在于它的本地优先 + 增量同步机制。
每台设备的本地都有一份完整的数据副本,数据写入时先落本地数据库,然后通过 SoftBus 将增量变更(只同步变化的部分,而不是整个数据库)推送给同账号设备。推送使用基于版本的乐观锁——如果两台设备同时修改了同一份数据,版本号更高的那次修改优先,冲突概率极低,因为播放位置这类数据天然不存在并发写入。
为什么不用传统的数据库同步?想象一下手机在地铁里没有网络,此时平板也离线,两台设备都独立播放了同一个视频(各自记住了不同的位置)。网络恢复后,谁的位置是正确的?分布式 KV 用"最后写入胜出(Last Write Wins)"策略配合时间戳来解决这个问题,简单高效,适合大多数消费级场景。
流转会话的生命周期管理
一个完整的流转会话,从创建到销毁,经历了五个状态:
① 准备(Preparing):源设备找到目标设备,建立通信通道,协商传输参数。这个阶段用户感知到的是"正在发现设备"。
② 传输(Transferring):文件或状态数据开始搬运。对于小文件(几百 KB 以内),这个阶段几乎瞬间完成;对于大视频文件,则会显示传输进度条。
③ 确认(Confirming):文件到达目标设备后,目标 App 被唤起并验证数据完整性(比如 MD5 校验)。确认无误后进入完成状态。
④ 完成(Completed):流转成功,两台设备上的 App 均显示当前状态,Session 进入"已过期"倒计时(默认 30 分钟),防止资源泄漏。
⑤ 失败/取消(Failed/Cancelled):网络中断、目标设备拒绝接收、磁盘空间不足等情况都会触发失败状态。此时源设备会提示用户原因,目标设备不会启动任何 App。
理解这五个状态,对于调试流转问题非常重要——当你发现"流转卡住了",看一下 Session 日志里卡在哪一步,排查方向就清晰多了。
六、常见问题解答
Q1:跨设备流转支持所有 App 吗?
不是的。跨设备流转需要在应用层主动接入 HarmonyOS 的分布式 SDK。目前主流的华为自带应用(华为视频、华为音乐、华为浏览器等)已经内置支持。如果你用的是第三方 App,目前还无法自动享受流转体验——这也是 HarmonyOS 正在推动生态建设的方向之一。
Q2:传输过程中设备断开网络会怎样?
分两种情况。如果传输的是大文件(视频、图片),SoftBus 传输中途断开,网络恢复后会自动续传,不需要从头开始。如果传输的是小状态数据(播放位置、书签),它们已经存储在分布式 KV 数据库中,断网不影响读取——因为数据在本地有缓存。
Q3:设备之间需要连到同一个 WiFi 吗?
不完全是。同一华为账号的设备,即便不在同一个局域网内,也可以通过 HarmonyOS 的分布式能力进行数据同步(延迟稍高)。但如果要进行大文件的高速传输,还是建议在同一 WiFi 网络下,速度会快很多。当然,如果你用的是 HarmonyOS 3.0 及以上版本,手机和平板在近距离时还能通过HarmonyOS 超级终端一碰连建立直连通道,完全不需要 WiFi。
Q4:哪些设备支持跨设备流转?
理论上所有 HarmonyOS 2.0 及以上版本的设备都支持基础的设备发现能力。但完整的流转体验(如视频续播接力)需要设备同时满足:搭载 HarmonyOS 2.0+、登录同一华为账号、打开"跨设备协同"开关。目前手机、平板、PC、手表、智慧屏都支持,具体能力因设备性能有所差异。
Q5:我的隐私安全有保障吗?
有。HarmonyOS 的跨设备通信默认走端到端加密,只有同一账号的可信设备之间才能建立连接。数据不经过第三方服务器中转,传输路径完全在本地局域网或设备直连通道内。此外,用户可以在设置中随时查看和管理"可信设备列表",随时撤销某台设备的访问权限。
Q6:可以同时往多台设备发送内容吗?
支持,但体验上建议一次只流转到一台设备。想象一下你点了一下分享,结果手机、平板、电视、手表同时开始播放同一个视频——那画面太美我不敢看。技术上系统支持建立多个并发 Session,但从产品体验角度,我们推荐用户在弹出的设备选择列表中一次选一台,确认后再流转。
Q7:流转会消耗很多流量吗?
基本不会。流转走的是局域网直连(WiFi Direct 或同一 WiFi 网络),不消耗移动数据流量。只有在跨网络的特殊场景(如手机使用移动数据、平板连接另一个 WiFi)下,数据才会走云端中转,此时才会消耗流量。但这类场景较少见,大多数流转发生在家里或办公室的同一网络下。
七、运行效果
设备发现与流转全流程
八、扩展方向
1. 从单设备流转到多设备协同
目前的流转是一对一:手机 → 平板。但未来可以扩展为"一对多"接力。比如你在手机上开始导航,走到停车场换成手表继续步行指引,上楼后手表又可以流转到电视展示地图全景。这就需要一个"任务协调器"来管理多设备的交接顺序。
这种多跳流转(Multi-hop Transfer)的实现逻辑并不复杂:每个流转节点保存"上游"和"下游"信息,形成一条链表。每当任务流转到下一台设备,上一台设备就把自己标记为"已完成"并指向新设备。如果用户在某台设备上主动返回上一级,系统就顺着链路反向追溯,找到当前活跃的节点并从那里继续。整个链路最多支持三跳(手机→平板→PC),再长的话用户体验反而会变得混乱。
2. 从视频续播到应用状态整体迁移
视频只是一个小口子。未来完全可以把"流转"扩展到整个应用状态——你正在手机上编辑文档,流转后平板直接打开同一份文档、光标位置一样、批注还在。再进一步,想象一下你在手机上打了半小时字,流转到 PC 后焦点直接跳到打字位置,这才是真正的"工作流不中断"。
3. AI 加持的智能流转决策
目前需要用户手动选择目标设备,但未来可以引入 AI 推理:比如检测到你带着手表离开了手机,自动建议把手表的导航流转过来;检测到你坐到了平板旁边,自动推荐把视频流转到平板大屏看。体验越来越无感,才是流转的终极形态。
4. 跨品牌设备的开放流转
目前跨设备流转依赖 HarmonyOS 的软总线能力,局限性在于只能在 HarmonyOS 生态内工作。随着生态开放协议(如 FIDO、ODCP)的成熟,未来或许能看到 HarmonyOS 设备与 Android、iOS 设备之间的有限流转——比如图片和文档的互传,而不需要云端中转。
一个更务实的中间路线是"云端兜底"模式:当 HarmonyOS 设备发现目标设备不是同生态时,自动降级到云同步(如华为云空间)。虽然速度不如软总线,但在跨品牌场景下提供了可用性保障。这样用户体验就有了一个兜底选项——流转能力不会因为设备生态不同而完全失效。
5. 跨 App 的通用流转协议
目前流转能力主要在单个 App 内部生效(华为视频续播接力只能在华为视频 App 内),但未来可以抽象出一套"跨 App 通用流转协议"。想象一下:你在微信里点开一个视频链接,看了几分钟想流转到平板,流转的不是视频 App 的状态,而是"这个 URL 链接"本身——平板收到后自动用浏览器打开并继续计时。
这套协议需要在系统层面定义"流转上下文"的通用格式:包含 URL、打开时间、停留时长、关联参数(如文章 ID)。任何 App 都可以声明"我支持接收流转上下文",系统负责路由和解析,App 只需要实现一个标准的回调接口即可。这种设计思路和 Android 的 App Link / iOS 的 Universal Link 有相似之处,但加上分布式状态同步后威力更大。