鸿蒙操作系统实现应用接续跨设备流转实战
一、背景说明
HarmonyOS 的"接续"(Continuation)能力,解决的是同一账号下多设备之间的任务无缝迁移问题:在手机上看到一半的文章、编辑到一半的表单,可以在平板或鸿蒙电脑上从同一步骤继续。近期 HarmonyOS 7.0.0.111SP10 版本向 nova 系列下放了多人互助通信共享等分布式组网能力,跨设备协同的网络底座进一步成熟,而应用侧承接这一切的核心接口就是 UIAbility 的 onContinue 生命周期。本文实现一个完整的"阅读进度跨设备接续"示例:手机上退出页面,平板上拉起同一个应用时自动恢复到该页面并还原滚动位置。
二、技术原理
应用接续的数据流分三步:
- 发起端(源设备):系统流转入口触发接续时,应用所在 UIAbility 的
onContinue(wantParam: Record<string, Object>)回调被调用,应用把需要迁移的状态(页面路径、表单内容、滚动位置等)以键值对形式写入wantParam。数据量上限约 1MB,超出会接续失败。 - 系统传输:
wantParam经系统分布式软总线加密传输到目标设备,应用无需关心网络细节。 - 接收端(目标设备):
- 目标设备上应用未启动:
onCreate中通过launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION判断本次启动来自接续,从want.parameters取出数据恢复状态; - 应用已在运行:走
onNewWant,同样按 launchReason 判断。
- 目标设备上应用未启动:
要让应用参与接续,还需满足两个配置:module.json5 中该 Ability 声明 "continuable": true;源端调用 startAbility 前目标设备与应用需登录同一华为账号、开启多设备协同开关。手动测试时可以在代码里调用 this.context.startAbility() 并通过 startAbilityParameter 携带参数模拟流转验证数据链路。
三、实现步骤
工程结构:
entry/src/main/ets/
entryability/EntryAbility.ets
pages/Index.ets
1. 配置 module.json5
在 module.json5 的 abilities 中声明 continuable,并添加分布式数据所需权限:
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
{ "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" }
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"exported": true,
"continuable": true,
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"skills": [
{ "entities": ["entity.system.home"], "actions": ["action.system.home"] }
]
}
]
}
}
DISTRIBUTED_DATASYNC 是用户授权类权限,首次流转前需在界面里调用 requestPermissionsFromUser 申请。
2. EntryAbility:保存与恢复接续数据
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { readContinuationData, saveContinuationData } from '../common/ContinuationStore';
const TAG = 'ContinuationDemo';
export default class EntryAbility extends UIAbility {
// 目标设备:应用冷启动,启动原因 = 接续
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
const page = want.parameters?.['page'] as string ?? 'Index';
const position = Number(want.parameters?.['position'] ?? 0);
saveContinuationData(page, position);
hilog.info(0, TAG, 'onCreate by continuation: %{public}s %{public}d', page, position);
}
}
// 目标设备:应用热启动,接续走这里
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
const page = want.parameters?.['page'] as string ?? 'Index';
const position = Number(want.parameters?.['position'] ?? 0);
saveContinuationData(page, position);
hilog.info(0, TAG, 'onNewWant by continuation: %{public}s %{public}d', page, position);
}
}
// 源设备:系统触发流转时保存状态,wantParam 会整体搬到对端
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
const page = readContinuationData().page;
const position = readContinuationData().position;
wantParam['page'] = page;
wantParam['position'] = position;
hilog.info(0, TAG, 'onContinue: %{public}s %{public}d', page, position);
return AbilityConstant.OnContinueResult.AGREE;
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0, TAG, 'loadContent failed: %{public}s', JSON.stringify(err));
return;
}
});
}
}
关键点:onContinue 的返回值用 AbilityConstant.OnContinueResult.AGREE 表示同意接续;返回 REJECT 或 MISMATCH(版本不匹配)则系统取消流转。launchReason 判断是接续与普通启动共用一个 Want 的唯一区分手段。
3. 全局状态存储 ContinuationStore
接续数据从 Ability 传到页面需要一个应用级桥,这里用 AppStorage:
// common/ContinuationStore.ets
export interface ContinuationData {
page: string;
position: number;
}
export function saveContinuationData(page: string, position: number): void {
AppStorage.setOrCreate('continuePage', page);
AppStorage.setOrCreate('continuePosition', position);
}
export function readContinuationData(): ContinuationData {
return {
page: AppStorage.get<string>('continuePage') ?? 'Index',
position: AppStorage.get<number>('continuePosition') ?? 0
};
}
4. 页面:还原进度并回写
// pages/Index.ets
import { readContinuationData } from '../common/ContinuationStore';
@Entry
@Component
struct Index {
@StorageLink('continuePosition') position: number = 0;
private scroller: Scroller = new Scroller();
build() {
Column() {
Text('阅读进度接续示例')
.fontSize(22)
.margin(12)
List({ scroller: this.scroller, initialIndex: this.position }) {
ForEach(Array.from({ length: 50 }, (v, i) => i), (item: number) => {
ListItem() {
Text(`章节 ${item + 1}`)
.fontSize(18)
.padding(16)
}
}, (item: number) => item.toString())
}
.layoutWeight(1)
.onScrollIndex((first: number) => {
// 随滚动持续更新进度,onContinue 时取到的就是最新值
this.position = first;
})
}
.width('100%')
.height('100%')
}
}
List 的 initialIndex 在组件创建时生效,正好承接冷启动接续场景;热启动接续时改为调用 scroller.scrollToIndex(this.position) 即可。
四、应用场景示例
除页面进度外,onContinue 常用于迁移表单与播放状态。例如视频应用接续时把播放进度和暂停状态搬过去:
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
wantParam['videoUrl'] = this.currentVideo.url;
wantParam['playTime'] = Math.floor(this.player.currentTime); // 秒
wantParam['isPlaying'] = this.player.state === 'playing';
return AbilityConstant.OnContinueResult.AGREE;
}
接收端在 onCreate/onNewWant 中读出后,创建播放器并 seek 到对应时间。
五、技术要点
- 数据上限:
wantParam整体不超过 1MB,大对象(如列表全量数据)只传 ID 或索引,数据本体走分布式数据对象或网络接口拉取。 - 权限申请:发起流转前申请
DISTRIBUTED_DATASYNC,被拒绝时用requestPermissionsFromUser的返回结果引导用户去设置开启:
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
const permissions: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(this.context, permissions);
if (result.authResults[0] !== 0) {
// 提示用户到设置中开启多设备协同授权
}
- 版本兼容:源端与目标端应用版本不一致时返回
MISMATCH;发布新版本后应保证onContinue写入的键对旧版本可缺省解析,避免接续后页面崩溃。 - 错误处理:
onContinue内不要做耗时操作,序列化控制在毫秒级;对可能失败的数据组装包 try/catch,失败时降级为只传页面路径:
try {
wantParam['detail'] = JSON.stringify(this.detailState);
} catch (e) {
wantParam['page'] = 'Index';
}
六、扩展建议
需要实时双向同步(而不是一次性迁移)时,用分布式数据对象 distributedDataObject:两端创建同一 sessionId 的对象后,任一端修改属性会自动同步到另一端,适合协同编辑、多端购物车等场景:
import { distributedDataObject } from '@kit.ArkData';
let obj: distributedDataObject.ObjectType =
distributedDataObject.create(getContext(), { cart: [] as object[] });
obj.setSessionId('cart-session'); // 两端 set 相同 sessionId 即自动组网同步
进一步可结合 onContinue(迁移导航栈)与分布式数据对象(同步业务数据),实现完整的跨设备工作流。
结语
应用接续的落地成本很低:一个 continuable 配置、一个 onContinue 回调、一处 launchReason 判断,合计不足百行代码,就能让任务在手机、平板、鸿蒙电脑之间无缝续接。随着互助通信共享等组网能力在更多机型下放,这条能力的实用价值会持续放大,建议交互密集型应用尽早接入。
更多推荐



所有评论(0)