HarmonyOS 跨设备应用接续教程:图片浏览无缝迁移

一、概述

应用接续(App Continuation)是 HarmonyOS 分布式能力的标志性功能,允许用户在一个设备上使用应用时,无缝地将整个应用状态迁移到另一台设备上继续操作。与简单的数据同步不同,应用接续迁移的是完整的运行时上下文——当前页面、浏览位置、数据列表,用户在另一台设备上看到的是"同一个应用在同一瞬间的延续"。

本教程以「云星图」项目的图片浏览场景为例,讲解:

  1. module.json5 接续配置 – 声明 continueType 与 continuable
  2. 源端数据序列化 – onContinue 回调中打包当前页面状态
  3. 目标端冷启动恢复 – onCreate 中解析接续数据 + 恢复窗口
  4. 目标端热启动恢复 – onNewWant 中处理热启动接续
  5. UI 层状态恢复 – 导航栈跳转 + 页面数据还原
  6. 实时状态保持 – 页面切换时实时更新接续数据源

整体接续流程如下:

手机B(目标端) 系统 手机A(源端) 手机B(目标端) 系统 手机A(源端) 用户正在浏览图片 用户点击接续按钮 alt [冷启动] [热启动] 用户看到同一张图片 Swiper.onChange 实时更新 CURRENT_PIC_VIEW_URLS / INDEX 触发接续流程 回调 onContinue(wantParam) 读取 AppStorage 序列化数据 返回 AGREE 拉起应用(携带 wantParam) onCreate(launchReason=CONTINUATION) 解析参数写入 AppStorage restoreWindowStage onNewWant(launchReason=CONTINUATION) handleContinuationData TabsView 检测接续标记 导航栈 push 到 PicView PicView 读取接续数据恢复

二、前置准备

2.1 设备与账号要求

应用接续依赖 HarmonyOS 分布式能力,需要满足以下条件:

条件 说明
设备要求 两台均支持分布式能力的 HarmonyOS 设备(手机/平板/PC)
账号登录 两台设备需登录同一华为账号
网络连接 两台设备需联网(Wi-Fi 或移动网络)
蓝牙开启 设备间通过蓝牙发现,需保持蓝牙开启

2.2 相关文档

2.3 依赖导入

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

2.4 核心概念

应用接续涉及三个核心环节,理解它们的关系是掌握接续开发的关键:

目标设备(B)

系统分发

源设备(A)

onContinue(wantParam)
序列化当前状态

系统跨设备拉起
携带 wantParam

onCreate / onNewWant
launchReason = CONTINUATION

解析参数 → AppStorage

restoreWindowStage
恢复窗口

UI 层读取 AppStorage
恢复页面状态

环节 触发方 核心回调/方法 作用
序列化 源设备 onContinue(wantParam) 打包当前页面状态,返回 AGREE/REJECT
分发 系统 跨设备拉起目标应用,携带 wantParam
恢复 目标设备 onCreate / onNewWant 检测 CONTINUATION 启动原因,解析参数
UI 还原 目标设备 页面 aboutToAppear 读取 AppStorage 中的接续数据

三、module.json5 配置

3.1 声明接续能力

module.json5 的 Ability 配置中添加两个关键字段:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        // 声明接续类型(快速接续)
        "continueType": ["EntryAbility_ContinueQuickStart"],
        // 启用应用接续能力
        "continuable": true
        // ... 其他配置
      }
    ]
  }
}

配置说明:

字段 作用 取值
continueType 声明接续类型标识,系统据此匹配可接续的设备 字符串数组,自定义命名
continuable 是否允许该 Ability 被接续 true / false

module.json5

continueType
声明接续类型

continuable: true
启用接续

系统匹配:
同账号 + 同 continueType 的设备

系统允许:
该 Ability 可被接续拉起

接续就绪

注意: continueType 的值是自定义的标识符,两台设备上安装的相同应用会自动匹配。如果应用有多个 Ability 需要接续,每个 Ability 可以设置不同的 continueType。

四、定义接续数据结构

4.1 接续参数接口

接续传输的数据通过 wantParam 携带,需要定义清晰的数据结构:

interface ContinuationParams {
  imgUrls?: string;      // 图片 URL 列表的 JSON 字符串
  currentIndex?: number;  // 当前浏览的图片索引
}

为什么 imgUrlsstring 而非 string[] wantParam 的值类型有限制,复杂数据结构需要先 JSON.stringify 序列化为字符串,目标端再 JSON.parse 还原。

4.2 AppStorage 状态键

StateKeys 中定义接续相关的全局状态键,分为两组:

export class StateKeys {
  // ─── 应用接续:传输通道(源端 → 目标端)───
  /** 接续的图片 URL 列表 */
  static readonly CONTINUATION_IMG_URLS: string = 'continuationImgUrls';
  /** 接续的当前图片索引 */
  static readonly CONTINUATION_INDEX: string = 'continuationIndex';
  /** 接续目标页面 */
  static readonly CONTINUATION_TARGET_PAGE: string = 'continuationTargetPage';

  // ─── 应用接续:实时状态源(供 onContinue 读取)───
  /** 当前 PicView 的图片 URL 列表(用于接续序列化) */
  static readonly CURRENT_PIC_VIEW_URLS: string = 'currentPicViewUrls';
  /** 当前 PicView 的图片索引(用于接续序列化) */
  static readonly CURRENT_PIC_VIEW_INDEX: string = 'currentPicViewIndex';
}

两组状态键的分工:

接续传输(一次性)

运行时状态(实时维护)

onContinue 读取

onContinue 读取

PicView aboutToAppear 读取

PicView aboutToAppear 读取

TabsView 读取

CURRENT_PIC_VIEW_URLS
当前图片列表

CURRENT_PIC_VIEW_INDEX
当前浏览索引

CONTINUATION_IMG_URLS
接续传入的列表

CONTINUATION_INDEX
接续传入的索引

CONTINUATION_TARGET_PAGE
接续目标页面

恢复页面状态

导航跳转

五、源端:onContinue 序列化状态

5.1 实现 onContinue 回调

当用户触发接续时,系统回调源设备的 onContinue,在此方法中读取当前页面状态并打包:

export default class EntryAbility extends UIAbility {

  // 应用接续:源设备序列化数据
  onContinue(wantParam: ContinuationParams): AbilityConstant.OnContinueResult {
    // 从 AppStorage 读取 PicView 实时维护的状态
    const imgUrls = AppStorage.get<string[]>(StateKeys.CURRENT_PIC_VIEW_URLS) ?? [];
    const currentIndex = AppStorage.get<number>(StateKeys.CURRENT_PIC_VIEW_INDEX) ?? 0;

    hilog.info(DOMAIN, 'EntryAbility',
      `onContinue 数据准备: imgUrls长度=${imgUrls.length}, currentIndex=${currentIndex}`);

    // 无有效数据时拒绝接续
    if (imgUrls.length === 0) {
      return AbilityConstant.OnContinueResult.MISMATCH;
    }

    // 序列化数据写入 wantParam
    wantParam.imgUrls = JSON.stringify(imgUrls);
    wantParam.currentIndex = currentIndex;

    return AbilityConstant.OnContinueResult.AGREE;
  }
}

返回值说明:

返回值 含义 使用场景
AGREE 同意接续,系统开始迁移 有有效数据可迁移时
MISMATCH 数据不匹配,拒绝接续 当前状态不适合接续(如无数据)
DATA_READY 数据已就绪 配合异步数据准备时使用

onContinue(wantParam)

读取 CURRENT_PIC_VIEW_URLS

读取 CURRENT_PIC_VIEW_INDEX

imgUrls 为空?

返回 MISMATCH
拒绝接续

JSON.stringify(imgUrls)

wantParam.imgUrls = json

wantParam.currentIndex = index

返回 AGREE
同意接续

关键点: onContinue 是同步回调,不能做异步操作。如果需要异步准备数据,应在页面运行期间就实时维护好状态(见第六节),而不是等到 onContinue 时才去异步获取。

5.2 实时维护接续数据源

onContinue 依赖的 CURRENT_PIC_VIEW_URLSCURRENT_PIC_VIEW_INDEX 不是凭空产生的,而是 PicView 页面在运行期间实时写入 AppStorage 的:

// PicView.ets

@Entry
@Component
export struct PicView {
  @State imgUrls: string[] = [];
  @StorageLink(StateKeys.CURRENT_IMAGE_INDEX) currentIndex: number = 0;

  async aboutToAppear(): Promise<void> {
    // ... 接续数据恢复逻辑(见第七节)...

    // 实时保存当前状态供 onContinue 使用
    AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_URLS, this.imgUrls);
    AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_INDEX, this.currentIndex);

    // ... 其他初始化 ...
  }
}

在 Swiper 组件的 onChange 回调中,每次切换图片都更新索引:

Swiper(this.controller) {
  ForEach(this.imgUrls, (imgUrl: string, index: number) => {
    // ... 图片内容 ...
  })
}
.index(this.currentIndex)
.onChange((index: number) => {
  this.currentIndex = index;
  // 实时更新接续状态
  AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_INDEX, index);
  // ... 其他逻辑 ...
})

为什么要实时维护?

触发接续时

正常浏览时

随时可接续

用户滑动图片

Swiper.onChange

AppStorage 更新
CURRENT_PIC_VIEW_INDEX

onContinue 被调用

读取 AppStorage
(已是最新值)

序列化并返回 AGREE

最佳实践: 在页面 aboutToAppear 时写入完整状态(URL 列表 + 索引),在交互回调中增量更新变化的字段(索引)。这样无论用户何时触发接续,onContinue 都能拿到最新状态。

六、目标端:冷启动恢复

6.1 onCreate 中处理接续

目标设备收到接续请求后,如果应用未在运行(冷启动),系统会通过 onCreate 拉起应用,launchParam.launchReason 会标记为 CONTINUATION

export default class EntryAbility extends UIAbility {

  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
    // ... 其他初始化(StorageUtil、网络监听、云存储等)...

    // 应用接续相关
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      const params = want.parameters as ContinuationParams;
      if (params) {
        const imgUrlsJson: string = params.imgUrls ?? '';
        const currentIndex: number = params.currentIndex ?? 0;
        if (imgUrlsJson) {
          try {
            // 反序列化图片 URL 列表
            const imgUrls: string[] = JSON.parse(imgUrlsJson);
            // 写入 AppStorage,供 UI 层读取
            AppStorage.setOrCreate<string[]>(StateKeys.CONTINUATION_IMG_URLS, imgUrls);
            AppStorage.setOrCreate<number>(StateKeys.CONTINUATION_INDEX, currentIndex);
            // 标记接续目标页面,供导航层读取
            AppStorage.setOrCreate<string>(StateKeys.CONTINUATION_TARGET_PAGE, 'PicView');
          } catch (e) {
            hilog.error(DOMAIN, 'EntryAbility', `解析接续数据失败:${JSON.stringify(e)}`);
          }
        }
      }
      // 恢复窗口阶段
      this.context.restoreWindowStage(this.storage);
    }

    // ... 其他初始化(实况窗、AppLinking 等)...
  }
}

冷启动接续恢复流程:

系统拉起应用
launchReason = CONTINUATION

onCreate(want, launchParam)

launchReason
== CONTINUATION?

正常启动流程

want.parameters as ContinuationParams

imgUrlsJson
非空?

跳过接续恢复

JSON.parse 反序列化

AppStorage 写入三组数据

CONTINUATION_IMG_URLS = 图片列表

CONTINUATION_INDEX = 当前索引

CONTINUATION_TARGET_PAGE = 'PicView'

restoreWindowStage(storage)

窗口恢复,UI 层读取接续数据

关键设计点:

  • launchReason 判断:必须先判断 launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION,否则会把正常启动的 Want 误当接续数据处理。
  • restoreWindowStage:接续冷启动时,必须调用此方法恢复窗口阶段,否则 UI 不会正常加载。传入的 LocalStorage 对象需要与 onWindowStageCreate 中使用的一致。
  • 写入三个 AppStorage 键CONTINUATION_IMG_URLSCONTINUATION_INDEX 供 PicView 读取恢复数据,CONTINUATION_TARGET_PAGE 供 TabsView 读取决定导航跳转。

七、目标端:热启动恢复

7.1 onNewWant 中处理接续

如果目标设备上应用已在运行(热启动),系统通过 onNewWant 传递接续数据,而不走 onCreate

export default class EntryAbility extends UIAbility {

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 处理接续启动(热启动场景)
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      this.handleContinuationData(want);
    }

    // ... 处理系统分享、服务卡片、AppLinking 等其他入口 ...
  }

  // 封装接续数据处理(onCreate 和 onNewWant 共用)
  private handleContinuationData(want: Want): void {
    const params = want.parameters as ContinuationParams;
    if (params) {
      const imgUrlsJson: string = params.imgUrls ?? '';
      const currentIndex: number = params.currentIndex ?? 0;
      if (imgUrlsJson) {
        try {
          const imgUrls: string[] = JSON.parse(imgUrlsJson);
          AppStorage.setOrCreate<string[]>(StateKeys.CONTINUATION_IMG_URLS, imgUrls);
          AppStorage.setOrCreate<number>(StateKeys.CONTINUATION_INDEX, currentIndex);
          AppStorage.setOrCreate<string>(StateKeys.CONTINUATION_TARGET_PAGE, 'PicView');
          hilog.info(DOMAIN, 'EntryAbility',
            `接续数据已恢复:${imgUrls.length} 张图片,索引 ${currentIndex}`);
        } catch (e) {
          hilog.error(DOMAIN, 'EntryAbility', `解析接续数据失败:${JSON.stringify(e)}`);
        }
      }
    }
    this.context.restoreWindowStage(this.storage);
  }
}

为什么提取公共方法? 冷启动(onCreate)和热启动(onNewWant)的接续数据处理逻辑完全一致,提取为 handleContinuationData 避免重复代码。冷启动中也可以调用此方法:

async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
  // ...
  if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
    this.handleContinuationData(want);  // 复用公共方法
  }
  // ...
}

冷启动与热启动的对比:

热启动(应用已在运行)

系统复用已有实例

onNewWant(want)

判断 launchReason

handleContinuationData

restoreWindowStage
(刷新窗口)

UI 已存在,直接刷新

冷启动(应用未运行)

系统创建 Ability 实例

onCreate(want)

判断 launchReason

handleContinuationData

restoreWindowStage

onWindowStageCreate
加载 UI

八、UI 层:导航跳转与状态恢复

8.1 路由注册

PicView 页面通过 Navigation 路由表注册,在 resources/base/profile/router_map.json 中声明:

{
  "routerMap": [
    {
      "name": "PicView",
      "pageSourceFile": "src/main/ets/pages/PicView.ets",
      "buildFunction": "PicViewBuilder",
      "data": {
        "description": "this is PicView"
      }
    }
  ]
}

module.json5 中引用路由表:

{
  "module": {
    "name": "Home",
    "type": "har",
    "routerMap": "$profile:router_map"
  }
}

PicView 中的 @Builder 函数需与 buildFunction 对应:

// PicView.ets
@Builder
export function PicViewBuilder() {
  PicView();
}

@Entry
@Component
export struct PicView {
  // ...
  build() {
    NavDestination() {
      // 页面内容
    }
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack;
    })
  }
}

8.2 TabsView:检测接续标记并跳转

TabsView 是应用的主界面,持有 Navigation 导航栈。在 aboutToAppear 中检测接续标记,自动跳转到 PicView:

// TabsView.ets
@Component
export struct TabsView {
  // 订阅接续目标页面标记
  @StorageLink(StateKeys.CONTINUATION_TARGET_PAGE)
  continuationTargetPage: string | undefined = undefined;

  // 导航栈(全局共享)
  pathStack: NavPathStack = AppStorageV2.connect(
    NavPathStack, StateKeys.NAV_STACK, () => new NavPathStack()
  )!;

  aboutToAppear(): void {
    // ... 其他初始化 ...

    // 处理应用接续
    this.handleContinuationNavigation();
  }

  private handleContinuationNavigation(): void {
    // 检测接续目标页面标记
    if (this.continuationTargetPage === 'PicView') {
      // 清除标记,避免重复跳转
      AppStorage.setOrCreate(StateKeys.CONTINUATION_TARGET_PAGE, '');
      // 通过导航栈跳转到图片详情页
      this.pathStack.pushPathByName('PicView', null);
      console.log('接续跳转:进入图片详情页');
    }
  }
}

导航跳转流程:

EntryAbility 写入
CONTINUATION_TARGET_PAGE = 'PicView'

TabsView.aboutToAppear

handleContinuationNavigation()

continuationTargetPage
== 'PicView'?

正常启动,不跳转

清除标记
CONTINUATION_TARGET_PAGE = ''

pathStack.pushPathByName('PicView', null)

PicView 被创建并加载

PicView.aboutToAppear
读取接续数据恢复

为什么要清除标记? @StorageLink 是响应式的,如果不清除标记,后续任何原因导致 TabsView 重新执行 aboutToAppear(如页面切换回来)都会再次触发跳转,形成死循环。清除标记确保接续跳转只执行一次。

8.3 PicView:恢复图片浏览状态

PicView 在 aboutToAppear 中检测接续数据,优先使用接续传入的数据恢复页面状态:

// PicView.ets
@Entry
@Component
export struct PicView {
  @State imgUrls: string[] = [];
  @StorageLink(StateKeys.CURRENT_IMAGE_INDEX) currentIndex: number = 0;
  @StorageLink(StateKeys.COS_IMAGE_URLS_FOR_VIEW) allImgUrls: string[] = [];

  async aboutToAppear(): Promise<void> {
    // 1. 检测接续数据
    const continuationUrls = AppStorage.get<string[]>(StateKeys.CONTINUATION_IMG_URLS);
    const continuationIndex = AppStorage.get<number>(StateKeys.CONTINUATION_INDEX);

    if (continuationUrls && continuationUrls.length > 0) {
      // 2. 使用接续传入的数据恢复
      this.imgUrls = continuationUrls;
      this.currentIndex = continuationIndex ?? 0;
      // 3. 清除接续标记,避免下次误用
      AppStorage.setOrCreate(StateKeys.CONTINUATION_IMG_URLS, []);
      AppStorage.setOrCreate(StateKeys.CONTINUATION_INDEX, 0);
      hilog.info(0x0000, 'PicView',
        `接续恢复:${this.imgUrls.length}张图片,索引${this.currentIndex}`);
    } else {
      // 4. 非接续场景:从收藏或全部图片进入
      let tempUrls: string[] = AppStorage.get(StateKeys.TEMP_FAVORITE_URLS) || [];
      if (tempUrls && tempUrls.length > 0) {
        this.imgUrls = tempUrls;
      } else {
        this.imgUrls = this.allImgUrls;
      }
    }

    // 5. 实时保存当前状态供下一次 onContinue 使用
    AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_URLS, this.imgUrls);
    AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_INDEX, this.currentIndex);

    // ... 其他初始化 ...
  }
}

PicView 的数据恢复决策:

是(接续恢复)

否(正常进入)

是(从收藏进入)

否(从全部进入)

PicView.aboutToAppear

CONTINUATION_IMG_URLS
非空?

imgUrls = 接续数据

currentIndex = 接续索引

清除接续标记

写入实时状态源

TEMP_FAVORITE_URLS
非空?

imgUrls = 收藏列表

imgUrls = allImgUrls

页面渲染
Swiper 显示 currentIndex 位置的图片

关键点: 恢复数据后必须立即清除接续标记CONTINUATION_IMG_URLSCONTINUATION_INDEX),否则用户返回后再次进入 PicView 时会误用旧的接续数据。同时要写入实时状态源CURRENT_PIC_VIEW_URLSCURRENT_PIC_VIEW_INDEX),使目标设备也能再次发起接续。

8.4 Swiper 绑定与实时更新

PicView 使用 Swiper 展示图片列表,通过 index 绑定当前索引:

Swiper(this.controller) {
  ForEach(this.imgUrls, (imgUrl: string, index: number) => {
    ImagePreview({ config: {} }) {
      Image(imgUrl)
        .width("100%")
        .height("100%")
        .objectFit(ImageFit.Contain)
    }
  }, (imgUrl: string) => imgUrl)
}
.width('100%')
.height('100%')
.loop(false)
.index(this.currentIndex)           // 绑定当前索引
.indicator(false)
.onChange((index: number) => {
  this.currentIndex = index;
  // 实时更新接续状态
  AppStorage.setOrCreate(StateKeys.CURRENT_PIC_VIEW_INDEX, index);
  // ... 其他逻辑 ...
})

为什么 loop(false) 接续场景下图片列表是确定的,禁用循环避免索引错乱。onChange 中实时更新 CURRENT_PIC_VIEW_INDEX 确保用户滑动后的位置能被后续接续正确恢复。

九、完整接续时序

将所有环节串联起来,完整的接续时序如下:

手机B(目标端) 系统 手机A(源端) 用户 手机B(目标端) 系统 手机A(源端) 用户 ── 正常浏览阶段 ── ── 接续触发阶段 ── ── 目标端恢复阶段 ── alt [冷启动] [热启动] 打开图片详情页 PicView aboutToAppear 写入 CURRENT_PIC_VIEW_URLS / INDEX 滑动到第3张图片 Swiper.onChange 更新 CURRENT_PIC_VIEW_INDEX = 2 点击接续按钮 触发接续 onContinue(wantParam) 读取 CURRENT_PIC_VIEW_URLS / INDEX JSON.stringify 序列化 AGREE 拉起应用(携带 wantParam) onCreate(launchReason=CONTINUATION) 解析 params → AppStorage restoreWindowStage onNewWant(launchReason=CONTINUATION) handleContinuationData TabsView.aboutToAppear 检测 CONTINUATION_TARGET_PAGE = 'PicView' pathStack.pushPathByName('PicView') PicView.aboutToAppear 读取 CONTINUATION_IMG_URLS / INDEX 恢复 imgUrls + currentIndex 清除接续标记 写入实时状态源(供再次接续) 看到第3张图片,无缝继续浏览

十、常见问题与最佳实践

10.1 常见问题

问题 原因 解决
接续后页面停在首页,没进 PicView TabsView 没检测 CONTINUATION_TARGET_PAGE 或标记已被清除 确保在 aboutToAppear 中调用 handleContinuationNavigation,且在 push 前才清除标记
PicView 恢复了旧数据 CONTINUATION_IMG_URLS 未在恢复后清除 恢复后立即 setOrCreate(CONTINUATION_IMG_URLS, [])
接续后图片索引不对 Swiper 的 index 未正确绑定 currentIndex 确保 .index(this.currentIndex)onChange 中更新 this.currentIndex
onContinue 返回 MISMATCH CURRENT_PIC_VIEW_URLS 为空(用户不在 PicView 时触发接续) onContinue 中做空值判断,或为非 PicView 页面提供默认接续数据
热启动接续不生效 只在 onCreate 处理了接续,未处理 onNewWant onNewWant 中也检测 launchReason === CONTINUATION
目标端接续后无法再次发起接续 恢复后未写入 CURRENT_PIC_VIEW_URLS PicView 恢复数据后立即写入实时状态源
restoreWindowStage 后 UI 白屏 传入的 LocalStorageonWindowStageCreate 不一致 确保 onCreateonNewWant 使用同一个 storage 实例
接续数据太大导致传输失败 wantParam 携带的数据量过大 只传必要数据(如 ID),目标端通过 ID 重新拉取;避免传大量 URL

10.2 最佳实践

  1. 实时维护状态源:不要等 onContinue 时才去获取数据。在页面 aboutToAppear 写入完整状态,在交互回调中增量更新。这样 onContinue 只需同步读取即可。

  2. 冷热启动都处理onCreate 处理冷启动接续,onNewWant 处理热启动接续,两者逻辑一致,建议提取公共方法。

  3. 清除标记防重复:接续标记(CONTINUATION_TARGET_PAGECONTINUATION_IMG_URLS)使用后必须立即清除,避免页面重建时重复触发。

  4. 恢复后写入状态源:目标端恢复数据后,要同步写入 CURRENT_PIC_VIEW_URLSCURRENT_PIC_VIEW_INDEX,使目标端也能发起新的接续,形成闭环。

  5. onContinue 只做同步操作onContinue 是同步回调,不能做网络请求或文件 IO。需要异步准备的数据应在运行期间提前维护好。

  6. wantParam 中的复杂数据要序列化:数组、对象等复杂数据结构需 JSON.stringify 为字符串传输,目标端 JSON.parse 还原。

  7. restoreWindowStage 必须调用:接续冷启动时,onCreate 中需要调用 restoreWindowStage 恢复窗口阶段,否则 UI 不会加载。

  8. 导航栈跳转用 pushPathByName:接续跳转应通过 Navigation 路由栈的 pushPathByName 实现,而不是 router.pushUrl,以保持与正常导航的一致性。

十一、API 速查

11.1 EntryAbility 接续相关方法

方法 触发时机 作用
onContinue(wantParam) 源端用户触发接续时 序列化当前状态到 wantParam,返回 AGREE/MISMATCH
onCreate(want, launchParam) 目标端冷启动 检测 CONTINUATION 启动原因,解析接续数据
onNewWant(want, launchParam) 目标端热启动 检测 CONTINUATION 启动原因,解析接续数据
restoreWindowStage(storage) 接续恢复时 恢复窗口阶段,触发 UI 加载

11.2 AbilityConstant 关键枚举

枚举 说明
LaunchReason.CONTINUATION 启动原因为应用接续
OnContinueResult.AGREE 0 同意接续
OnContinueResult.REJECT 1 拒绝接续
OnContinueResult.MISMATCH 2 数据不匹配
OnContinueResult.DATA_READY 3 数据已就绪

11.3 AppStorage 状态键一览

状态键 方向 作用
CURRENT_PIC_VIEW_URLS 运行时 → onContinue PicView 实时维护的图片列表
CURRENT_PIC_VIEW_INDEX 运行时 → onContinue PicView 实时维护的当前索引
CONTINUATION_IMG_URLS onContinue → 目标端 PicView 接续传输的图片列表
CONTINUATION_INDEX onContinue → 目标端 PicView 接续传输的当前索引
CONTINUATION_TARGET_PAGE onContinue → 目标端 TabsView 接续目标页面标识

十二、总结

应用接续是 HarmonyOS 分布式能力的核心体验,本教程以图片浏览无缝迁移为例,讲解了完整实现:

  • 配置阶段module.json5 中声明 continueTypecontinuable: true
  • 数据准备:定义 ContinuationParams 接口 + 5 个 AppStorage 状态键,分为实时状态源和接续传输两组
  • 源端序列化onContinue 中同步读取 AppStorage,JSON.stringify 序列化后写入 wantParam,返回 AGREE
  • 实时维护:PicView 的 aboutToAppear 写入完整状态,Swiper.onChange 增量更新索引
  • 目标端恢复onCreate(冷启动)和 onNewWant(热启动)检测 CONTINUATION 启动原因,解析参数写入 AppStorage,调用 restoreWindowStage
  • UI 还原:TabsView 检测 CONTINUATION_TARGET_PAGE 跳转导航栈,PicView 读取接续数据恢复 imgUrlscurrentIndex,清除标记并写入实时状态源

开发建议:

  1. onContinue 只做同步读取,运行期间实时维护状态源
  2. 冷启动和热启动都要处理,提取公共方法避免重复
  3. 接续标记用完即清,防止重复触发
  4. 恢复后写入实时状态源,使目标端也能再次发起接续
  5. 复杂数据 JSON.stringify 序列化传输,目标端 JSON.parse 还原
Logo

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

更多推荐