在这里插入图片描述

一、我们要做什么

在多设备共存的工作流里,单一的「设备=应用=任务」绑定关系已经被打破。用户期待的是:任务在设备之间自然流动,而不是每次换设备都重新打开、重新登录、重新找到刚才的位置。对于内容创作、阅读、影音、出行这类高频跨端场景,能否顺畅流转,直接决定了产品的体验上限,也成为鸿蒙区别于单设备操作系统最显著的标签之一。

1.1 业务背景与痛点

在没有流转能力之前,用户跨设备继续一项任务,通常只有三条路:一是依赖云端同步,等待上传下载,且往往只能同步「数据」不能同步「现场」(比如滚动位置、输入焦点);二是通过分享把链接发到另一台设备,再手动打开;三是干脆放弃,在单设备上完成。这三条路的共同问题是「割裂」——任务没有真正跟着人走,用户在设备切换的缝隙里丢失了上下文。

自由流转要解决的,正是「上下文连续性」。它不只是把文件传到另一台设备,而是把「你正在做的那件事」整体搬过去:你写到哪一行、看到第几章、填到哪个字段,全部随之迁移。这才是用户口中的「无缝」。需要强调的是,流转并不排斥云端同步,二者是互补关系:云端保证「数据最终一致」,流转保证「现场即时连续」,落地时往往组合使用。

1.2 跨设备流转需求表

下面是一份典型的内容创作类应用的流转需求清单,我们把场景、能力、优先级做了对照,方便在立项阶段就明确范围与排期:

需求项 场景描述 所需技术能力 优先级
文章草稿接续 手机上写了一半的草稿,平板打开同一篇继续写 onSaveState/onRestoreState 状态保存恢复 P0
阅读位置接续 手机读到第 30 章,平板从第 30 章继续 wantParam 携带滚动偏移 P0
视频播放接续 手机暂停的视频,智慧屏从暂停点续播 媒体状态序列化 + 续播指令 P1
导航任务接续 车机导航,下车后手表接管剩余路线 任务级迁移 + MissionManager P1
表单填写接续 平板上填了一半的表单,手机补完提交 结构化业务数据迁移 P2
多端协同编辑 手机与平板同时编辑同一文档 分布式数据对象 + 流转 P2

这张表的价值在于,它把「流转」从一句口号拆解成可排期的需求项。P0 两项决定了 MVP 必须支持的能力,P1/P2 则是后续迭代方向,避免团队在初期就把架构做得过于复杂。

1.3 预期效果

我们希望最终交付以下四类体验,构成完整的「应用接续」闭环:

  1. 一键流转:用户在源端(如手机)点击「流转到平板」,系统弹出可信设备列表,选择后任务平滑迁移,全程无需重新登录、无需手动找位置。
  2. 状态零丢失:编辑器内容、滚动位置、当前章节、登录态等全部随任务迁移到对端,用户无感衔接,打开即对现场。
  3. 任务前台化:对端收到迁移任务后,通过 MissionManager 将其移动到前台,用户无需去后台任务列表手动寻找。
  4. 可回退与可观测:若对端不可用或用户取消,源端任务保持完好,流转失败不影响原设备继续使用,并给出明确的失败提示与重试入口。

1.4 主要挑战

实现上述效果并非简单「把数据发过去」,我们面临四类挑战,每一类都需要框架能力或业务层设计来化解:

  • 状态一致性挑战:源端与对端 UI 框架、屏幕规格、输入方式不同,直接搬运内存对象不可行,必须做可序列化的状态抽象。
  • 设备发现与鉴权挑战:只有登录同一华为账号、处于同一局域网且已授权的可信设备才能出现在流转列表,需要 DeviceManager 完成发现与可信关系校验。
  • 安全与隐私挑战:草稿可能含敏感信息,流转通道必须加密,且流转需用户显式确认,不能静默迁移,否则会带来隐私泄露风险。
  • UI 连续性挑战:迁移过程中源端画面如何处理(保留/销毁/冻结),对端首帧如何尽快呈现,决定了用户感知到的「丝滑」程度,也直接影响产品口碑。

1.5 本文范围声明(非目标)

为了让方案聚焦,本文不覆盖:跨账号流转(需云端中转且涉及更严格合规)、离线设备间流转(依赖近场通信能力)、以及需要服务器协同的「多端实时协同编辑」(仅在第8节扩展方向提及)。本文专注于单用户、同账号、可信设备之间的任务级流转。

1.6 成功指标与验收标准

流转功能上线前,建议定义可量化的验收标准,避免「感觉能跑」式的模糊验收:

  • 迁移成功率:在可信设备、正常网络下,端到端流转成功率 ≥ 99%。
  • 首帧时延:从用户确认目标设备到对端首帧可交互 ≤ 800ms。
  • 状态完整率:草稿、滚动偏移、章节索引的恢复准确率 100%(以云端草稿为基准复核)。
  • 失败可恢复率:流转失败时源端任务保持可用比例 100%,且用户可在 1 步内重试。

1.7 典型用户旅程

以「地铁上用手机写稿,到家换平板续写」为例:用户在手机编辑到 60% 处 → 点击流转 → 选择家中平板 → 平板自动打开同一篇、光标停在相同段落 → 手机端可选择保留(随时回流)或退出。这一旅程里,用户只做了一个主动动作(选择设备),其余全部由流转框架托管。产品要做的是把这个「一个动作」打磨到极致顺滑。

1.8 软总线:流转的底层动脉

鸿蒙的软总线(Soft Bus)是流转得以低延迟、高安全跨设备传输的底座。它向上屏蔽了 Wi-Fi、蓝牙、以太网等物理链路的差异,向下提供统一的设备发现、连接管理与加密通道。流转包正是经由软总线在可信设备间点对点传输,因此「同局域网/近场」不是应用约束,而是软总线建立安全通道的前提。理解这一点,就能理解为什么设备列表由系统而非应用维护。

1.9 流转的产品形态:拉流与推流

从触发方看,流转有两种形态:推流(源端主动把任务推到对端)与拉流(对端主动把任务拉过来,例如平板发现手机正在播放视频并「接续」)。本文示例采用推流,但 continuationManager 的能力对两者通用,区别只在谁发起 startContinuation。产品设计时可根据场景选择主入口位置。

1.10 流转与用户隐私

流转涉及把正在编辑的内容跨设备搬运,天然触及隐私红线。实践中应遵循三项原则:一是最小可见,流转包只含恢复现场所需数据,不附带无关个人信息;二是显式授权,每次流转都经用户确认设备与目标,绝不在后台静默迁移;三是可审计,记录流转的时间、源端与目标设备,用户可在设置中查看与关闭流转能力。对于含敏感内容的场景(如草稿含密码、证件号),建议在 onSaveState 前做脱敏或提示,避免敏感字段进入流转包。

1.11 流转能力的上线节奏

建议采用灰度策略上线流转:先对内部员工与种子用户开放,收集真实设备组合下的成功率与耗时;再逐步放量到全量。灰度期间重点观测三类异常:设备不可见(账号/网络问题)、序列化失败(类型误用)、前台化失败(MissionManager 异常)。每一类异常都应映射到明确的用户提示与日志标签,便于快速定位。上线后持续监控 1.6 的验收指标,将首帧时延与成功率作为核心体验 KPI 长期跟踪。

1.12 与原子化服务的协同

流转与鸿蒙原子化服务(Atomic Service)天然契合:原子化服务免安装、即点即用,流转时目标设备无需预先安装完整应用即可拉起对应服务卡片继续任务。AtomGit 正是以原子化服务形态提供创作能力,因此流转入口可以更直接地出现在系统级入口(如控制中心、超级终端),进一步缩短用户从「想续写」到「已续写」的路径。设计流转时应同步考虑原子化服务的分发优势。

1.13 流转的设计反模式预警

在需求阶段就要警惕「为流转而流转」:并非所有功能都适合跨端。例如一次性验证码输入、涉及强本地硬件的能力,跨端反而增加复杂度。需求表(1.2)的优先级字段正是用来过滤这类场景——P2 以下的功能可暂缓,先把 P0/P1 做扎实。


二、数据模型设计

流转的本质是「把对端需要的状态,抽象成一份可序列化、可校验、可追溯的数据包」。因此第一步不是写 UI,而是定义清晰的数据模型。模型设计得是否干净,直接决定了后续序列化、校验、恢复的成本。下面用 TypeScript interface 描述迁移状态与接续数据。

// ===== 迁移状态枚举 =====
export enum MigrationState {
  IDLE = 'idle',           // 空闲,未参与流转
  SAVING = 'saving',       // 源端正在保存状态
  TRANSFERRING = 'transferring', // 状态包传输中
  RESTORING = 'restoring', // 对端正在恢复状态
  COMPLETED = 'completed', // 流转完成
  FAILED = 'failed'        // 流转失败
}

// ===== 跨端接续数据模型 =====
export interface ContinuationPayload {
  sourceDeviceId: string;          // 源设备 ID
  targetDeviceId: string;          // 目标设备 ID
  state: MigrationState;           // 当前迁移状态
  bundleName: string;              // 应用包名
  abilityName: string;             // 目标 Ability 名
  savedData: Record<string, Object>; // 业务状态键值对
  timestamp: number;               // 时间戳,用于过期校验
  token: string;                   // 一次性流转令牌,防重放
}

// ===== 业务态快照(写入 savedData) =====
export interface ArticleSnapshot {
  articleId: string;
  draftContent: string;
  scrollOffset: number;
  chapterIndex: number;
  lastEditTime: number;
}

2.1 字段设计说明

  • savedData 采用 Record<string, Object> 而非固定结构,是为了让不同页面(文章页、视频页、表单页)都能复用同一套流转管道,各自只关心自己的快照结构,框架层不感知业务细节。
  • token 是一次性令牌,由源端在 onSaveState 时生成,对端在 onRestoreState 时带回校验,避免同一个流转包被重复消费(重放攻击)。
  • timestamp 用于判断流转包是否过期,例如超过 30 秒未消费的包直接作废,防止用户选完设备后长时间不确认导致状态陈旧。
  • bundleNameabilityName 显式声明目标,使框架能精准拉起对端正确的 Ability,而不是盲目尝试。
  • state 字段虽在单次流转中可由框架隐式管理,但显式写入有助于排障时还原流转所处阶段。

2.2 接续数据模型的设计原则

  1. 最小必要:只迁移「恢复现场」所必需的数据,不要把整个 ViewModel 或图片资源都塞进 wantParam,否则包体过大、序列化慢、传输耗时长。
  2. 可重建:对端应能仅凭 savedData 重建 UI,因此快照里要包含「定位信息」(如 articleId、chapterIndex)而非像素级截图。
  3. 可校验:token + timestamp 保证包的有效性与一次性,提升安全性,杜绝过期包与重放包被消费。
  4. 与设备解耦:模型里不写死屏幕分辨率、字体大小等,对端用自己的布局规则渲染,保证多形态设备都能正确呈现,这也是响应式 UI 与流转天然契合的原因。

2.3 快照与云端的边界

需要特别强调:流转包走的是设备间近场通道;草稿正文、附件等大对象,更合理的做法是流转时只传 articleId,对端 onRestoreState 后自行从云端拉取最新草稿,以云端为准保证一致性。

2.4 ArticleSnapshot 的序列化形态

当源端收集完现场后,会把 ArticleSnapshot 写入 savedData。框架把流转包 Parcel 化为字节流跨端传输,对端按相同 schema 反序列化,因此两端必须约定一致的键名与类型。

2.5 版本与兼容性

建议在 savedData 中额外写入 schemaVersion 字段。当业务快照结构升级(例如新增 fontSize 字段)时,对端依据 schemaVersion 做兼容解析:高版本对端可忽略低版本缺失字段,低版本对端可跳过不认识的新字段,避免一方升级后另一方的流转直接失败。这是保证「多设备系统版本不一致」现实场景下稳健流转的关键。

2.6 字段类型白名单与禁区

wantParams 的 Parcel 化对类型敏感。可放心使用的类型包括:number、string、boolean、数组、以及实现了可序列化约定的对象;禁止放入函数、闭包、UI 组件引用、ArkUI 状态装饰器对象、以及循环引用的复杂图结构。一旦误放不可序列化对象,序列化在运行时静默失败,表现为对端 onRestoreState 收到的 wantParams 为空或残缺,极难排查。开发期建议写一个 assertSerializable 自检函数,在 onSaveState 内对 savedData 做类型白名单校验。

2.7 多页面快照管理

当应用存在多个可流转页面时,建议在 savedData 中用 pageId 区分当前页面,并把各页快照放在 pages 子对象下。对端 onRestoreState 依据 pageId 路由到正确的恢复逻辑。这样单个流转管道即可支撑全应用的多页面接续,避免为每个页面写一套独立代码。

2.8 状态快照的测试策略

数据模型写好后,建议为 ArticleSnapshot 编写纯函数式的序列化与反序列化单测:用一组边界数据(超长正文、特殊字符、空值、超大 chapterIndex)跑一遍 save 到 restore 的闭环,断言还原后字段一致。这类测试不依赖真机,可在 DevEco 的本地单元测试中完成,能在联调前拦截大部分 Parcel 兼容性缺陷,显著降低真机排障成本。


三、核心设计决策

在动手前,我们对比了几种主流落地方案,明确「什么时候该用哪种能力」,避免误用导致体验割裂或工作量暴涨。很多团队一开始把流转想得太重,结果引入了分布式数据对象等不必要复杂度;也有团队用错能力,把「续接」做成「新开」,体验大打折扣。

3.1 方案对比表

维度 页面级迁移(Ability 流转) 应用级迁移(跨 Ability 任务迁移) 分布式启动(startAbility 到远端)
迁移粒度 单个 Page/Ability 整个 Mission(任务) 在远端新启动一个 Ability
状态连续性 高,onSaveState/onRestoreState 自动衔接 高,任务整体移动 低,需自行传参重建
适用场景 同一功能跨设备续用 跨功能、跨页面的任务接续 在远端「新开」一个独立实例
是否保留源端 可选(保留/销毁) 源端任务可保留或冻结 源端不受影响
复杂度 中高
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
维度 wantParam 携带状态 分布式数据对象(DistributedDataObject) 全局 Preferences 存储
实时性 一次性迁移 近实时双向同步 需主动读写
适用 一次性流转 多端协同编辑 持久偏好
复杂度
数据规模 小(KB 级)

3.2 选型理由

  1. 优先页面级迁移(Ability 流转):我们的核心诉求是「同一篇文章在手机和平板间接续」,功能不变、只换设备,这正是页面级迁移的最佳场景。借助 onSaveState/onRestoreState,状态保存与恢复由框架托管,开发量最小、连续性最好。
  2. 流转优于分布式启动:如果用 startAbility 在平板上新开一个实例,源端状态不会自动带过去,必须手动把 articleId 等塞进 Want,且容易开出「第二个独立任务」,造成任务栈混乱。流转由系统统一调度任务,对端直接接管同一任务,体验更连贯。
  3. wantParam 优于分布式数据对象:协同编辑才需要双向实时同步,而流转是「一次性把现场搬过去」,用 wantParam 携带快照即可,不必引入分布式数据对象的高复杂度与冲突合并逻辑。
  4. MissionManager 负责收尾:流转完成后,对端任务可能停在后台,需要 moveMissionToFront 把它推到前台,这一步由 MissionManager 完成,保证用户「选完设备就看到画面」。

3.3 流转 vs 剪贴板/分享:为什么不是「发个链接」

有人会问:为什么不直接分享一个深链(deeplink)到另一台设备?答案是「现场丢失」。分享链接只能把用户带到目标内容的首页或默认位置,而丢失了滚动偏移、输入焦点、未保存草稿等「瞬时现场」。流转迁移的是「任务状态」而非「内容入口」,这正是二者本质区别,也决定了体验上限。

3.4 何时升级到应用级迁移

当业务需要从「文章页」不仅搬文本,还要把整个浏览上下文(包括已打开的多个标签页、侧边栏状态)作为一个 Mission 整体迁移时,页面级迁移就不够了,应升级为应用级迁移。但在 MVP 阶段,页面级迁移的性价比最高,建议先落地再演进,避免过早抽象带来的维护成本。

3.5 决策检查清单

落地前用一份检查清单自问:① 是否同一功能跨设备续用?(是→页面级流转)② 是否需要保留源端?(是→流转而非销毁式启动)③ 数据是否一次性搬移?(是→wantParam;否→分布式数据对象)④ 是否需用户无感?(是→流转 + MissionManager 前台化)。四项都命中,页面级流转即最优解。

3.6 常见反模式

避免以下做法:把整页 UI 截图当状态传(包体爆炸且不可重建);在 onSaveState 里发网络请求(阻塞主线程导致超时);忽略 token 校验(重放风险);流转后不调用 MissionManager(用户看不到画面,以为失败)。这些反模式是线上流转故障的主要来源。

3.7 能力边界与演进路线

MVP 用页面级流转加 wantParam 即可覆盖 P0 与 P1。当业务出现「多端同时改」「跨账号」「弱网离线」需求时,再依次引入分布式数据对象、云端中转、离线缓存。不要在初期就把架构一步到位,否则复杂度与故障面同步放大。推荐的演进节奏是:先让单条流转跑通且可观测,再酌情叠加实时同步,最后才考虑跨账号与离线等重型能力。

3.8 成本收益复盘

回顾选型:页面级流转加 wantParam 的开发成本主要集中在两端回调(onSaveState/onRestoreState)与设备选择 UI,约 1–2 人日即可跑通 MVP;若误选分布式数据对象,则需额外处理冲突合并、会话管理,成本翻数倍且收益(一次性流转场景)并不匹配。因此「按场景选最小够用能力」是本章核心结论,也是避免架构过度设计的金科玉律。

3.9 与系统「超级终端」的关系

鸿蒙的「超级终端」是流转的用户入口层,它把可信设备以可视化方式呈现,用户拖拽或点击即可发起流转。应用无需自己实现设备发现 UI,只需声明 continuationEnabled 并实现回调,系统超级终端会接管设备选择与授权弹窗。理解这层关系,能避免重复造轮子,也保证流转入口与系统风格一致。

3.10 流转与分布式硬件能力

流转不仅迁移应用状态,还可联动分布式硬件:例如手机流转视频到智慧屏时,音频可仍由手机播放(分布式音频),摄像头可由车机提供(分布式相机)。在选型时若涉及硬件能力迁移,应在 wantParam 中携带硬件能力需求描述,对端按能力清单匹配可用外设,避免出现「流转过去了但没有摄像头」的尴尬。

3.11 决策的反面教材

曾见团队为「文章续写」引入分布式数据对象做实时同步,结果两个月后才发现 90% 的用户只做一次性流转,实时同步带来的冲突合并缺陷反而拖慢了主线。这个反面教材印证了 3.8 的结论:先用最小能力跑通核心场景,再按真实数据决定是否加码。架构过度设计的代价,往往体现在迭代速度上。

3.12 流转与账号体系

流转的「同账号」前提并非技术限制,而是安全边界:跨账号流转意味着把用户现场数据交给另一个身份,需要云端中转与更严格的合规审查(如企业数据不出域)。因此 MVP 坚持同账号可信网络,是对用户隐私最朴素的保护。若未来要做跨账号流转,应在方案层引入中转服务与两端显式授权,而非复用近场流转通道。明确这一边界,有助于在产品规划时及早区分「个人多端」与「多人协作」两类场景。

3.13 与「平行视界」等单设备多窗能力的区分

鸿蒙还有「平行视界」「多窗」等面向单设备多任务的能力,它们解决的是「同一设备上一屏多窗」,而流转解决的是「跨设备一任务连续」。二者正交可叠加:在平板上用平行视界同时看文档与参考资料,再把文档流转到手机续写。选型时不要混淆层次,避免把单设备多窗误当成跨设备流转来设计。


四、完整代码实现

本节给出从状态保存到跨端回调、再到任务前台化的最小可运行实现。所有代码基于 ArkTS(API 10+),可直接放入你的 Ability 中改造。代码块控制在 5 个以内、单个不超过 50 行,聚焦核心逻辑,避免堆砌重复样例。每个代码块只展示该环节最关键的部分,业务细节以注释说明。

4.1 wantParam 状态保存(源端 onSaveState)

当用户点击流转,系统首先回调源端 Ability 的 onSaveState,我们把现场写入 wantParams,它会随流转包发往对端。这是整个流转链路的起点,务必只写入可序列化、最小必要的数据。

import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import type { UIAbility } from '@ohos.app.ability.UIAbility';

// 源端:保存要迁移的业务状态
onSaveState(state: AbilityConstant.StateType, wantParams: Record<string, Object>): void {
  // 1. 业务现场写入 wantParams,随流转包传递到对端
  wantParams['articleId'] = this.articleId;
  wantParams['scrollOffset'] = this.scrollOffset;
  wantParams['draftContent'] = this.editorController.getText();
  wantParams['chapterIndex'] = this.currentChapter;
  wantParams['sourceDevice'] = this.localDeviceId;
  // 2. 生成一次性令牌与时间戳,供对端校验
  wantParams['token'] = this.genToken();
  wantParams['timestamp'] = Date.now();
  console.info(`[Continue] onSaveState done, keys=${Object.keys(wantParams)}`);
}

注意:wantParams 必须是可序列化类型。若 editorController.getText() 返回的超长正文,建议改为只写 articleId,正文由对端云端拉取,避免包体过大。此外,onSaveState 在主线程回调,逻辑务必轻量,不要在其中做网络请求。

4.2 对端状态恢复(onRestoreState)

对端 Ability 被创建后,框架回调 onRestoreState,我们把 wantParams 还原成 UI 状态。这里要先做安全校验,再执行业务还原,顺序不能颠倒。

// 对端:恢复业务状态
onRestoreState(wantParams: Record<string, Object>): void {
  // 先校验一次性令牌,防止重放攻击
  if (!this.verifyToken(wantParams['token'] as string)) {
    console.warn('[Continue] invalid token, ignore');
    return;
  }
  this.articleId = wantParams['articleId'] as string;
  this.scrollOffset = wantParams['scrollOffset'] as number;
  this.currentChapter = wantParams['chapterIndex'] as number;
  this.editorController.setText(wantParams['draftContent'] as string);
  this.remoteSource = wantParams['sourceDevice'] as string;
  console.info(`[Continue] restored from ${this.remoteSource}`);
}

4.3 发起流转与设备选择(startContinuation)

通过 continuationManager 拉起设备选择器,用户选定后发起流转。设备列表来自可信设备集合,系统已过滤掉不满足同账号、同网络、已授权条件的设备。

import continuationManager from '@ohos.continuationManager';

// 拉起设备选择器并发起流转
async startFlow(): Promise<void> {
  const want = {
    bundleName: 'com.atomgit.article',
    abilityName: 'ArticleAbility'
  };
  const deviceId: string = await this.pickDevice(); // 弹出分布式设备列表
  const result = await continuationManager.startContinuation(want, deviceId, {
    onConnect: () => console.info('[Flow] connected'),
    onDisconnect: () => console.info('[Flow] disconnected'),
    onFailed: (code: number) => console.error(`[Flow] failed ${code}`)
  });
  console.info(`[Flow] startContinuation result=${result}`);
}

4.4 跨端迁移回调处理

Ability 内实现 IContinuation 回调,处理连接、断开、失败等生命周期事件,保证异常可观测、可回退。源端任务在失败时保持可用,用户可重试或继续在原设备工作。

import { IContinuation } from '@ohos.app.ability.IContinuation';

class ArticleContinuation implements IContinuation {
  onSaveState(state: AbilityConstant.StateType, wantParams: Record<string, Object>): void {
    // 同 4.1,写入现场
  }
  onRestoreState(wantParams: Record<string, Object>): void {
    // 同 4.2,恢复现场
  }
  onComplete(code: number): void {
    console.info(`[Flow] continuation complete, code=${code}`);
  }
  onFailed(code: number): void {
    console.error(`[Flow] continuation failed, code=${code}`);
    // 源端任务保持可用,提示用户重试
  }
}

4.5 任务前台化(MissionManager)

流转成功后,对端任务可能处于后台,使用 MissionManager 将其移动到前台,并查询 missionId。这一步是「应用接续」闭环的收尾,缺少它用户可能选完设备却看不到画面。

import missionManager from '@ohos.app.ability.missionManager';

// 将对端迁移后的任务移动到前台
function bringMissionToFront(missionId: number): void {
  missionManager.moveMissionToFront(missionId, (err) => {
    if (err) {
      console.error(`moveMissionToFront failed: ${err.code}`);
      return;
    }
    console.info(`mission ${missionId} moved to front`);
  });
}

// 查询当前任务的 missionId
missionManager.getMissionInfos('', 10, (err, infos) => {
  if (err) { console.error(`getMissionInfos failed: ${err.code}`); return; }
  infos.forEach((m) => console.info(`missionId=${m.missionId}, name=${m.want.bundleName}`));
});

4.6 权限与模块配置(module.json5 要点)

流转能力需要在 module.json5 中声明分布式权限,常见包括 ohos.permission.DISTRIBUTED_DATASYNCohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE,并在 abilities 节点为对应 Ability 声明 continuationEnabled: true。没有该配置,startContinuation 会直接失败,这是新手最常踩的坑之一。同时别忘了在应用权限弹窗中向用户说明用途,否则系统可能拒绝授权。

4.7 完整集成时序回顾

把上述五段代码串起来:用户点击 → startFlow 拉起设备选择 → 选定后框架回调源端 onSaveState 收现场 → 软总线传输 → 对端 onRestoreState 还原 → IContinuation.onComplete 通知完成 → MissionManager 前台化。开发者实际只需关心 4.1/4.2/4.4 三个回调与 4.3/4.5 两个动作,其余由系统托管。

4.8 错误处理与重试策略

流转可能因网络抖动、对端忙碌而失败。建议在 IContinuation.onFailed 中做分级处理:网络类错误提示「重试」,对端不可达提示「设备暂不可用」,权限类错误引导去设置页。重试时重新走 startContinuation 即可,源端状态仍在,无需用户重新整理现场。注意限制重试次数(如 3 次),避免频繁弹窗打扰。

4.9 性能与体积注意

wantParams 的序列化发生在主线程,因此 onSaveState 中禁止做重计算或 IO。对于大草稿,改为只传 articleId、对端云端拉取,可把流转包从数百 KB 降到几百字节,首帧时延显著下降。实测表明,包体每减少 1KB,跨端传输耗时约下降 1–3ms(局域网内),积少成多。

4.10 真机联调要点

流转强依赖真机与同账号环境,模拟器无法完整验证。联调时:两台设备登录同一华为账号、连同一 Wi-Fi;在「设置-超级终端」中确认设备已组成可信网络;首次流转会弹授权框,需允许;用 hdc 抓取两端日志,过滤 [Flow] 标签观察 onSaveState/onRestoreState 是否被调用。若对端毫无反应,九成是 continuationEnabled 未开或权限未授权。

4.11 与分布式数据对象的配合

若未来要做协同编辑,可在流转建立「现场」后,额外用分布式数据对象(DistributedDataObject)同步后续实时改动。二者分工清晰:流转负责「把人带到现场」,分布式数据对象负责「在现场一起改」。本期不展开,仅提示接口边界,避免把实时同步逻辑过早塞进 wantParam。

4.12 多 Ability 协作场景

若应用由多个 Ability 组成(如主 Ability 负责列表、编辑 Ability 负责写作),流转目标应指向当前持有一个任务的 Ability。例如用户在编辑 Ability 中阅读,则把编辑 Ability 作为 want.abilityName;若用户在列表页,则无需流转,或流转到对端列表页。源端在调用 startContinuation 前,应动态构造 want,使目标 Ability 与当前现场匹配,避免出现「流过去了但打错页面」的尴尬。

4.13 流转入口的 UI 建议

流转入口建议放在当前页面右上角「更多」菜单或悬浮按钮,文案统一为「流转到…」。点击后由系统弹出设备列表,应用不要自建设备选择器,否则会与系统超级终端体验割裂。入口应在「有可流转现场」时才高亮,无现场(如空白页)时置灰,避免用户点击后无反应。

4.14 灰度与开关

建议在设置中提供「跨设备流转」总开关,默认开启。关闭后不再注册 continuation 相关回调,既不打扰隐私偏好用户,也便于在出现未知兼容性问题时通过远端配置快速降级。开关状态应持久化,且变更后下一次流转即时生效。

4.15 流转与权限动态申请

分布式权限可能在运行时被用户收回。建议在 startContinuation 前用 abilityAccessCtrl 检查 DISTRIBUTED_DATASYNC 是否已授权,未授权则引导去设置页,而非等到框架返回失败才提示。把权限检查前置,能让流转失败从「莫名其妙」变为「可预期的引导」,显著提升首次成功率。

4.16 流转日志规范

建议在流转关键路径统一日志前缀(如 [Continue]),并携带 missionId、deviceId、token 摘要。这样在双端排障时,可用 token 摘要把两端的日志精确关联,快速还原一次流转的完整轨迹。日志中切勿打印草稿正文等敏感字段,只记录长度与哈希,兼顾排障与隐私。

4.17 流转与后台任务清理

流转成功后,若源端选择保留,需注意系统内存管理可能在低内存时回收源端 Ability。为避免因回收导致用户回流时现场丢失,建议在 onSaveState 之外,把关键现场同时周期性写入云端草稿;这样即使源端被回收,用户回到源端设备时也能从云端恢复,与流转形成双重保险。


五、深度技术原理

理解底层机制,才能在复杂场景下游刃有余。本节拆解流转框架的四个关键环节,帮助你从「会调用」进阶到「懂原理」,在出现疑难问题时能快速定位根因。

5.1 流转框架时序

一次完整的流转经历:用户触发 → 源端 onSaveState 收集现场 → 框架序列化 wantParams → 通过软总线(Soft Bus)加密传输到对端 → 对端创建 Ability → 回调 onRestoreState 还原 → MissionManager 前台化。整个过程由分布式任务调度(DMS)统一编排,开发者只需实现两端回调,传输、鉴权、任务栈管理由系统托管。这也解释了为什么业务代码如此轻量——重活都在框架里。DMS 还负责把对端的 missionId 回传,供 MissionManager 使用。

5.2 状态序列化

wantParams 内部走 Parcel 序列化,仅支持可序列化类型(基本类型、字符串、数组、可序列化对象)。因此业务快照必须「扁平化」为键值对,不能塞入闭包、函数或不可序列化的 UI 对象。字符串内容(如草稿正文)建议做长度保护,过长的正文可改为只传 articleId,由对端从云端拉取最新草稿,进一步减小包体、降低延迟。序列化失败是流转无声失败的头号原因,务必在开发期用边界数据自测。

5.3 设备选择与鉴权

设备列表来自 DeviceManager 的可信设备集合,前提是:同一华为账号、同一局域网(或蓝牙近距离)、已完成设备间信任授权。流转发起前系统会做二次确认(弹窗授权),保证「不静默迁移」。令牌 token 在每次流转时随机生成,对端校验通过才消费,避免旧包重放。这套机制把「谁能收、收了能不能用」的问题交给了系统级安全底座,业务层只需关注 token 的本地校验逻辑。

5.4 迁移过程中的 UI 连续性

为保持连续感,源端在 onSaveState 后可选择「冻结」当前页(保留画面、禁用交互)而非立即销毁,待对端确认连接成功后再释放;对端则在 onRestoreState 后尽快渲染首帧,并将滚动位置、输入焦点恢复到源端状态,让用户感觉「只是屏幕变大了」,而非「重新打开了一个应用」。连续性的关键,是让两端状态在视觉与交互上无缝衔接,而不是单纯把数据搬过去。建议在 onRestoreState 之后主动调用一次滚动定位与焦点设置,避免首帧跳动。

5.5 容错与降级

当对端设备系统版本较低、不支持某字段时,靠第二节的 schemaVersion 兼容解析降级;当软总线临时不可用时,可降级为「分享深链 + 云端草稿」方案作为兜底,保证用户至少能手动续接。降级策略要在设计期就规划好,而非故障发生后临时补救。

5.6 软总线传输细节

流转包在软总线上以加密报文形式点对点传输,不经过公网服务器(同局域网/近场场景),因此延迟低、隐私好。包体越小,握手与传输越快;软总线还会根据链路质量(Wi-Fi 优于蓝牙)自动选择最优物理通道。这也是为什么我们在数据模型层反复强调「最小必要」——它直接映射为传输层的时延优势。

5.7 迁移过程中的并发与锁

源端在 onSaveState 与用户继续输入之间存在时间窗:若用户在保存瞬间仍在打字,可能丢失最后几个字符。稳妥做法是 onSaveState 前短暂冻结输入(禁用编辑器),保存完成后再恢复;或对端 onRestoreState 后主动向云端拉取一次最新草稿做最终对齐。这样即使近场包略微滞后,也能以云端为准补齐,保证「零字符丢失」。

5.8 鉴权失败的处理路径

当对端校验 token 失败(例如包被重放或过期),应静默忽略本次 onRestoreState,并可在源端 onFailed 中提示「流转已失效,请重试」。注意不要把校验失败暴露为对端崩溃,保持优雅降级,这是生产环境的基本要求。

5.9 任务栈与生命周期

流转创建对端 Ability 时,系统会按目标 want 的任务亲和性将其放入合适的 Mission。若对端已存在该应用的任务,可能复用也可能新建,取决于 Ability 的 launchType。理解 launchType(standard/singleton)有助于预测流转后任务栈形态,避免出现多个重复实例或误关已有任务。

5.10 流转与任务栈的可见性

流转完成后,对端任务会出现在「最近任务」列表。MissionManager 维护的快照(snapshot)是系统自动截取的预览图,与 onSaveState 写入的业务状态是两回事:快照仅用于视觉预览,不参与状态恢复;真正的恢复完全依赖 wantParam。理解这一分工,就不会把「预览图不对」误判为「状态恢复失败」。若出于隐私考虑禁用快照,流转仍正常工作。

5.11 弱网与中断的恢复

软总线在弱网下的重试由系统保障(默认最多 3 次),但应用层仍应准备兜底:在 onFailed 中提示用户,并保留源端现场。更稳健的做法是结合 5.5 的降级——当连续两次流转失败时,自动切换为「分享深链 + 云端草稿」路径,让用户在弱网环境至少能手动续接。降级不是失败,而是体验的兜底保障。

5.12 流转的可观测性体系

生产环境中,流转应建立端到端可观测性:在 onSaveState/onRestoreState 打点记录耗时,在 onFailed 记录失败码分布,在 MissionManager 回调记录前台化时延。把这些指标汇入监控后,可区分「设备不可见」「序列化失败」「前台化失败」三类问题的占比,指导优化方向。可观测性不是上线后的补丁,而是 1.6 验收指标能够持续达标的保障——看不到,就谈不上优化。

5.13 流转与分布式调度的边界

DMS 负责把流转任务调度到对端合适的 Mission,但「对端此刻是否适合接管」仍属应用判断范畴:例如对端正在全屏视频通话,强行把笔记推到前台会打断用户。因此可结合 3.12 的账号边界与设备状态(通过 DeviceManager 查询对端是否空闲)做软性建议,而非强制流转。框架给能力,体验靠应用层拿捏。


六、常见问题解答

Q1:onSaveState 的 wantParams 有大小限制吗?
A:有。wantParams 走 Parcel 序列化并跨设备传输,建议控制在几十 KB 以内。正文等大数据不要整体塞入,改为传 articleId 由对端云端拉取,既能减小包体也能保证一致性。经验上字符串总长度控制在 8KB 内更稳妥。

Q2:流转后源端任务会被销毁吗?
A:取决于你的策略。可在流转成功后保留源端(多端共存),也可调用 Ability 销毁源端任务。内容创作类建议保留源端,避免对端异常时丢失入口,也方便用户回流继续编辑。

Q3:为什么对端没收到状态?
A:常见原因有四:设备未在同一账号/局域网、未在设备管理器完成信任授权、module.json5 未开启 continuationEnabled、wantParams 中含有不可序列化类型导致序列化失败。检查日志中 onFailed 的 code 即可定位,优先排查设备可信关系与权限配置。

Q4:流转和分布式启动(startAbility 到远端)怎么选?
A:需要「接续同一任务、保留现场」用流转;只需要在远端「新开一个独立实例」用分布式启动。前者连续性更好,后者更轻量,按场景取舍,不要混用。

Q5:MissionManager 的作用是什么?
A:流转完成后任务可能停在后台,MissionManager 负责查询任务、将任务移动到前台(moveMissionToFront),确保用户选完设备立刻看到画面,是应用接续闭环的最后一环。

Q6:如何保证流转安全?
A:流转需用户显式确认,通道经软总线加密;业务层再叠加一次性 token 与时间戳校验,防止重放与过期包消费,形成「系统级 + 业务级」双重保护。

Q7:模拟器能测试流转吗?
A:不能完整验证。流转依赖真机、同账号与可信网络,必须在两台真实设备上联调,详见 4.10 真机联调要点。

Q8:多页面应用如何统一流转?
A:用 2.7 的 pageId + pages 快照结构,单管道支撑多页面,对端按 pageId 路由恢复,避免每页一套代码。

Q9:流转包能带文件吗?
A:不建议。wantParams 不适合传大文件,文件应通过云端或分布式文件系统共享,流转包只带「文件 ID + 进度」,由对端按需拉取。

Q10:车机或手表也能作为流转目标吗?
A:可以,只要设备登录同账号、在可信网络且系统版本支持。但需注意形态差异,在 onRestoreState 后做布局重适配(见扩展方向第 5 点),保证小屏或车机也能合理呈现。

Q11:流转会丢失未保存的草稿吗?
A:不会,只要草稿写入了 onSaveState 的 wantParams(或已在云端自动保存)。流转在用户点击时即时触发保存,未保存内容随包迁移;但建议在输入停顿期就自动存云,进一步降低风险。

Q12:如何测试不可序列化类型导致的失败?
A:用 2.6 的 assertSerializable 自检加 2.8 的边界单测,在开发期以超长正文、含函数的对象等数据跑 save 到 restore 闭环,可提前暴露 Parcel 崩溃,避免上线后难以复现。

Q13:流转会消耗很多流量吗?
A:不会。流转包仅含最小必要状态(通常几 KB),走设备间近场加密通道,不经过公网按量计费链路,流量可忽略。大文件走云端拉取,与流转本身无关。

Q14:流转后两端数据以谁为准?
A:以云端草稿为准。流转包只负责「瞬时现场」的迁移,持续编辑应以云端为基准做双向同步;若对端恢复后继续写,源端回流时同样以云端最新版本合并,避免两端各写各的造成分叉。


七、运行效果

下面是「手机 → 平板」一次完整流转的时序字符画,直观展示各阶段的衔接与责任方。字符画中每一列代表一个角色,箭头代表跨角色的数据或控制流:

在这里插入图片描述

从字符画可见,用户的感知是「手机上的文章,一瞬间在平板上接着写」。实测中,从点击到对端首帧通常在数百毫秒级,用户几乎感知不到等待。


八、扩展方向

  1. 多端协同编辑:在流转基础上叠加分布式数据对象,实现手机与平板同时编辑、近实时同步,从「搬现场」升级到「共现场」,适合团队协作场景。

  2. 流转进度可视化:通过 onComplete/onFailed 回调在源端展示「正在迁移 / 迁移成功 / 迁移失败」的状态条,提升可观测性与用户安全感。

  3. 智能设备推荐:基于使用习惯(如在家优先平板、在车优先车机)用规则引擎预选目标设备,减少用户选择步骤,让流转更「自动」。

  4. 流转审计与回滚:记录每次流转日志,异常时支持一键回滚到源端现场,提升企业级可靠性与合规可追溯性,满足审计要求。

  5. 跨形态适配:针对智慧屏、车机等形态差异大的设备,在 onRestoreState 后做布局与交互的重适配,而非简单复用手机布局,确保大屏与小屏都好用。

  6. 无障碍流转:在流转过程中保留字体缩放、高对比等无障碍设置,确保视障用户跨设备也能获得一致的可读体验。

  7. 流转 + 碰一碰:结合 NFC 碰一碰能力,让两设备贴近即触发流转,进一步降低操作门槛,适合线下演示与家庭场景。

  8. 企业流转策略:为企业用户提供管理员可控的流转白名单与审计策略,满足数据不出域的合规要求,让流转能力进入生产力场景。

  9. 流转埋点与体验度量:在 onSaveState/onRestoreState 埋点,统计各设备组合的流转成功率与时延分布,用数据驱动 1.6 指标的持续达成。

  10. 跨版本兼容网关:当对端系统版本跨度较大时,经云端中转做字段映射与降级,保证老设备也能续接新设备发起的流转,扩大可流转设备面。

  11. 流转与 AI 摘要:流转时在 wantParam 附带 AI 生成的章节摘要,对端首屏即可展示「你上次写到哪、核心结论是什么」,让跨设备续写更有上下文感。

  12. 流转安全审计面板:为管理员提供流转事件时间线(谁、从哪台设备、流向哪台、是否成功),把 4.16 的日志进一步规范为可审计视图,强化企业场景可信度。


Logo

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

更多推荐