鸿蒙自由流转:一次开发,多端部署,你的应用真的会"跑"吗?

大家好啊~我是那个在代码海洋里扑腾了 10+ 年的老水手,目前主业是"鸿蒙应用开发+Web 全栈开发"双面间谍。

这些年写过的 BUG 能绕地球半圈,填过的坑能养活一个施工队,当然也攒了点有用的经验(毕竟吃一堑长一智嘛)。

平时最大的爱好就是把复杂的技术掰碎了、嚼烂了,做成普通人也能看懂的小甜点分享给大家。

如果你也喜欢折腾代码、踩坑、填坑,或者想找个人唠唠技术嗑,欢迎关注我一起交流~毕竟,独乐乐不如众乐乐,一起进步才是正经事!


你肯定遇到过这种场景:地铁上用手机刷到一个超长的技术视频,看到一半到家了。你想在电视大屏上接着看,结果呢?得重新搜索、定位进度,体验直接断档。

或者更糟,你在平板上写了一半的文档,临时要用手机出门,只能截图或者发给自己,到手机上再对着敲一遍。

说白了,这就是"设备孤岛"——每个设备都像一座孤岛,数据和应用状态被困在里面,流不动。

但鸿蒙喊出的"自由流转"、“多设备协同”,不就是想解决这个痛点吗?今天咱们就扒开看看,这玩意儿到底是怎么让应用"跑"起来的,以及你该怎么在代码里实现它。

一、自由流转,不只是"多端适配"那么简单

很多人一听"多设备协同",第一反应就是做响应式布局——让界面在不同屏幕尺寸上都能正常显示。这当然重要,但只是最基础的一层。

真正的自由流转,核心是"任务跟着人走"

你想啊,你在地铁上用手机看视频,这个"看视频"是一个任务。回到家,你人到了电视前面,这个"任务"应该能无缝地"流"到电视上继续,包括视频源、播放进度、甚至弹幕和收藏状态。

这背后的技术栈,鸿蒙官方文档里其实画得很清楚。为了让概念更直观,我根据其核心思想,用 Mermaid 绘制了下面这张自由流转核心运作机制图:

关键使能技术

用户在本端设备操作

场景变化或主动触发

本端设备发起接续

分布式框架与软总线

数据传输与指令同步

远端设备接收并恢复

用户在新设备无缝继续

应用接续 UIAbility 迁移

跨设备互通如相机、文件

统一数据管理分布式对象/文件

统一交互如拖拽、剪贴板

从上图可以看到,一次完整的流转,底层依赖分布式框架和软总线来搬运"任务状态"。对开发者来说,我们主要跟图中右侧的几项关键使能技术打交道。

所以,当你打算开发一个支持自由流转的应用时,心里得先有张谱:我的应用,需要在哪些设备之间、以什么方式、流转哪些"状态"?

二、基石能力:应用接续(Application Continuation)

应用接续是自由流转最基础、最核心的能力。说白了,就是让同一个应用在不同设备间"热迁移"。

它的运作机制,文档里有一张非常经典的应用接续运作机制图。核心就三步:

  1. 本端保存:用户在设备 A 上触发流转,系统回调你的应用,让你把当前状态(比如正在编辑的文档内容、浏览的列表位置)打包。
  2. 平台搬运:鸿蒙的分布式框架和软总线负责把数据包安全、高效地传到设备 B。
  3. 远端恢复:设备 B 上的同一个应用被拉起,拿到数据包,恢复成和之前一模一样的状态。

代码怎么写?三步搞定

第一步:声明你的 Ability 支持接续

module.json5 文件里,给你想让用户能"流转"的 UIAbility 打个标签。

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        // ... 其他配置
        "continuable": true, // 关键!设为 true,表示这个 Ability 可以被迁移
        "orientation": "auto_rotation" // 通常建议跟随系统自动旋转
      }
    ]
  }
}

(代码来源:官方"应用接续"章节的 module.json5 配置示例)

第二步:在本端设备保存状态

当用户点击流转图标(比如 Dock 栏上的流转按钮),系统会调用你 Ability 的 onContinue() 方法。你在这里把要带走的东西打包进一个 Want 对象。

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { distributedObject, distributedDataObject } from '@kit.DistributedDataKit'; // 用于分布式对象
import { fileIo } from '@kit.CoreFileKit'; // 用于处理文件

const TAG: string = '[EntryAbility]';
const KEY_TEMP_DATA: string = 'tempData';

export default class EntryAbility extends UIAbility {
  // 当本端设备发起接续时调用
  async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {
    // 1. 准备要迁移的数据
    let continueData: Record<string, Object> = {
      'page': 'detail', // 告诉远端要打开哪个页面
      'videoId': '123456', // 业务数据:视频 ID
      'progress': 354 // 业务数据:播放进度(秒)
    };

    // 2. 对于复杂数据或文件,可以使用分布式数据对象
    let distObject: distributedObject.DistributedObject = distributedObject.createDistributedObject(continueData);
    let sessionId: string = distObject.getSessionId();

    // 3. 将关键标识放入 Want,这是系统帮我们传递的"行李票"
    let want: Want = {
      bundleName: 'com.example.myapp',
      abilityName: 'EntryAbility',
      parameters: {
        'sessionId': sessionId, // 分布式对象会话 ID
        'continueData': JSON.stringify(continueData) // 简单数据也可以直接序列化塞进去
      }
    };

    // 4. 告诉系统:我准备好了,行李都打包在 want 里了,搬吧!
    return AbilityConstant.OnContinueResult.AGREE;
  }
}

(代码逻辑融合自官方"应用接续"及"分布式数据对象"相关示例)

第三步:在远端设备恢复状态

设备 B 上的应用被拉起,会在 onCreate()onNewWant() 里收到那个"行李票"(Want 对象),然后取出数据,恢复界面。

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { distributedObject } from '@kit.DistributedDataKit';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  // 远端设备 Ability 被创建时调用(包括接续拉起)
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 判断是否是通过接续拉起的
    if (want.parameters?.sessionId) {
      let sessionId: string = want.parameters.sessionId as string;
      try {
        // 根据"行李票"找到对应的分布式数据对象
        let distObject: distributedObject.DistributedObject = distributedObject.getDistributedObject(sessionId);
        let continueData: Record<string, Object> = distObject.getObject();

        // 恢复业务状态,比如跳转到对应页面并设置进度
        let targetPage: string = continueData['page'] as string;
        let videoId: string = continueData['videoId'] as string;
        let progress: number = continueData['progress'] as number;
        console.info(`${TAG} 接续恢复:打开页面${targetPage}, 视频${videoId}, 进度${progress}`);

        // 这里通常需要将数据传递给 UI 页面组件
        AppStorage.setOrCreate('continueVideoId', videoId);
        AppStorage.setOrCreate('continueProgress', progress);

      } catch (err) {
        let error: BusinessError = err as BusinessError;
        console.error(`${TAG} 恢复分布式对象失败:${error.code}, ${error.message}`);
      }
    }
  }
}

(代码逻辑融合自官方"应用接续"及"分布式数据对象"相关示例)

这就实现了最基础的应用接续。你想想,这不就是"任务跟着人走"吗?代码量其实不大,关键是设计好你的"状态包"里要放什么。

三、协同升级:从"接续"到"互通"

应用接续是应用整体的搬家。但有时候,用户不想搬家,只想从另一个设备"借个东西用用"。

比如,你在平板上写游记,想插入手机刚拍的照片。理想体验是:在平板上点"插入图片",直接选择"使用手机相机拍摄",手机自动亮屏打开相机,拍完照照片直接插入平板文档。

这就是跨设备互通。鸿蒙提供了现成的组件来降低开发难度。

跨设备拍照:不用管传输,只调组件

官方文档"内容编辑多设备协同"案例里,详细展示了如何用两个组件实现跨设备拍照。

import { collaborationService } from '@kit.CollaborationKit'; // 注意引入的 Kit

@Entry
@Component
struct Index {
  // 1. 创建协同服务菜单项(这个组件会帮你列出组网内可用的相机设备)
  @Builder
  collaborationServiceMenuItems() {
    collaborationService.createCollaborationServiceMenuItems({
      menuItems: [
        {
          serviceType: collaborationService.ServiceType.CAMERA, // 服务类型:相机
          maxSelectCount: 5 // 最多可选 5 张照片
        }
      ],
      onSelected: (result: collaborationService.CollaborationResult) => {
        // 用户选择了某个远端设备后的回调
        console.info(`用户选择设备:${result.deviceId}, 服务:${result.serviceType}`);
        // 拿到照片 URI 列表,就可以在本地显示了
        let imageUris: Array<string> = result.uriList;
        // ... 更新你的 UI,显示这些图片
      }
    })
  }

  // 2. 构建远端相机状态弹窗(可选,用于显示连接状态)
  @Builder
  collaborationServiceStateDialog() {
    collaborationService.CollaborationServiceStateDialog()
  }

  build() {
    Column() {
      // 你的编辑界面...
      Button('插入图片')
        .onClick(() => {
          // 点击后,弹出设备选择菜单
          this.collaborationServiceMenuItems();
        })
    }
    .popup(this.collaborationServiceStateDialog()) // 绑定状态弹窗
  }
}

(代码基于官方"跨设备互通"章节的 createCollaborationServiceMenuItemsCollaborationServiceStateDialog 组件描述重构)

看到了吗?你几乎不用关心手机和平板之间是怎么发现、连接、传数据的。系统通过分布式硬件框架和软总线都搞定了。你作为应用开发者,就是"点单"和"收货"。

四、实战指南:你的应用该如何起步?

聊了这么多能力,到底该从哪下手?我给你画条路线。

第一阶段:先让应用"能跑"

  • 确定流转维度:你的应用,是适合"接续"(整个应用迁移),还是"互通"(借用外设)?视频、阅读类应用优先做接续;工具、办公类可能更需要互通。
  • 实现核心接续:按第二部分的方法,先把 onContinueonCreate/onNewWant 跑通。先迁移最简单的数据(比如当前页面、关键 ID)。
  • 处理好文件:如果涉及图片、文档,记得用分布式文件目录 (distributedFile),把文件 URI 放在分布式数据对象里一起迁移。这是官方推荐的做法。

第二阶段:让体验"顺滑"

  • 状态管理:对于列表、长文章进度,直接用 ScrollListWaterFlow 等组件的 scroller 对象。系统支持将这些滚动位置的"进度标识"在接续时自动保持。
// WaterFlow 示例,scroller 状态可自动接续
WaterFlow({ scroller: this.waterFlowScroller }) {
  // ...
}

(概念源自官方"浏览进度接续"章节)

  • 交互归一:确保你的按钮、列表在手机(触摸)、平板(键鼠)、PC(键盘)上都有合适的交互反馈。用统一的点击事件,而不是分别适配。
  • 测试、测试、再测试:在不同设备组合、不同网络状态下测试流转。特别是中断场景(比如传送一半关掉蓝牙)。

第三阶段:玩点"高级"的

  • 跨设备拖拽:实现手机照片拖到平板文档里。用 draggable()onDrop() 事件,系统会帮你处理键鼠穿越和数据传输。
  • 跨设备剪贴板:复制手机上的文字,在平板上粘贴。利用系统级的分布式剪贴板能力。

五、心里得有点数:限制与坑点

自由流转不是魔法,它有边界。

  • 设备限制:不是所有设备都能互流转。通常需要同账号、已认证、在同一个局域网或通过蓝牙发现。具体看文档的"约束与限制"。
  • 数据安全:流转的数据默认只在信任设备间传输。但敏感数据(如密码、支付信息)你最好在 onContinue 里主动过滤掉,别打包。
  • 性能与体积Want 里的 parameters 别塞太大。大的文件、图片走分布式文件系统,只在 Want 里放个引用 URI。
  • 生命周期:本端应用在接续后,可能会被保留也可能被销毁,你的代码要能应对这两种情况。

说白了,自由流转是一套组合拳。应用接续是基础拳法,跨设备互通、拖拽、剪贴板是各种招式。想打好这套拳,先扎稳马步——把应用接续的原理和代码搞透彻。

当你看到用户真的能在不同设备间无缝切换,而这一切是你用代码实现的,那种感觉,比写个炫酷的 UI 动画爽多了。

这玩意儿正在重新定义"应用"的边界。你的应用,不再属于某一个设备,而是属于用户。想想,是不是有点意思?

好了,关于鸿蒙自由流转,今天就先唠这么多。如果你在实现过程中卡壳了,或者有更妙的场景,欢迎随时来聊聊。独乐乐不如众乐乐,一起折腾,踩坑都能变成段子,不是吗?

Logo

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

更多推荐