鸿蒙开发之应用接续开发

95 阅读7分钟

鸿蒙应用接续开发指导

一、概述

1.1 什么是应用接续

应用接续(App Continuation)是HarmonyOS提供的一项跨设备协同能力,允许用户在一个设备上操作某个应用时,能够在另一个设备上快速切换并继续该应用的任务,实现跨设备无缝体验。

通过应用接续,用户可以:

  • 从手机阅读文章切换到平板,继续从中断位置阅读
  • 在PC编辑文档后,用手机继续编辑任务
  • 在手机上观看视频,无缝切换到平板继续播放

1.2 核心特性

应用接续基于HarmonyOS的分布式状态同步技术,具备以下核心特性:

  • 数据无缝同步:支持存储及恢复自定义数据(业务内容)
  • 页面状态保持:支持存储及恢复页面路由信息和控制状态
  • 版本兼容检测:提供应用版本兼容性检测机制
  • 灵活配置:支持动态设置迁移状态、选择页面栈恢复、控制源端应用退出行为

二、适用场景

2.1 推荐接续场景

应用类型推荐场景同步内容
浏览器网页内容详情页浏览进度、页面位置
备忘录详情页浏览进度、编辑内容
新闻应用文章阅读页阅读进度、滚动位置
视频应用播放页播放进度、播放列表
音乐应用播放页和歌单页播放进度、当前歌曲
阅读应用小说阅读页阅读进度、章节位置
办公应用编辑页面编辑内容、格式状态
地图应用路线查询和导航界面当前路线、导航状态

三、前置条件

3.1 系统要求

  • 系统版本:HarmonyOS NEXT Developer Preview0及以上
  • 设备要求
    • 双端登录同一华为账号
    • 打开Wi-Fi和蓝牙
    • 建议接入同一局域网

3.2 应用要求

  • 接续只能在同一应用(UIAbility)的不同设备实例间触发
  • onContinue回调中wantParam传输数据需控制在100KB以下
  • 对于超过100KB的大数据,建议使用分布式数据对象进行同步

四、应用接续架构

4.1 整体架构流程

应用接续涉及源端设备和目标端设备的协同工作:

sequenceDiagram
    participant 用户
    participant 源端设备
    participant 系统服务
    participant 目标端设备

    用户->>源端设备: 触发接续操作
    源端设备->>源端设备: 调用onContinue()
    源端设备->>源端设备: 保存状态数据到wantParam
    源端设备->>源端设备: 版本兼容性检测
    源端设备->>系统服务: 返回AGREE/MISMATCH

    alt 版本兼容
        系统服务->>目标端设备: 传输wantParam数据
        目标端设备->>目标端设备: 调用onCreate()/onNewWant()
        目标端设备->>目标端设备: 判断LaunchReason.CONTINUATION
        目标端设备->>目标端设备: 从want.parameters恢复数据
        目标端设备->>目标端设备: 调用onWindowStageRestore()
        目标端设备->>目标端设备: 恢复页面栈和UI状态
        目标端设备->>用户: 展示接续后的界面
    else 版本不兼容
        系统服务->>用户: 提示版本不兼容
    end

4.2 生命周期调用流程

不同启动模式下,应用接续的生命周期函数调用顺序有所不同:

sequenceDiagram
    participant 系统
    participant UIAbility(源端)
    participant UIAbility(目标端)

    Note over 系统,UIAbility(目标端): 源端保存数据阶段
    系统->>UIAbility(源端): 触发接续
    UIAbility(源端)->>UIAbility(源端): onContinue(wantParam)
    UIAbility(源端)->>UIAbility(源端): 保存数据到wantParam
    UIAbility(源端)->>UIAbility(源端): 检查版本兼容性
    UIAbility(源端)-->>系统: 返回 OnContinueResult.AGREE

    Note over 系统,UIAbility(目标端): 目标端恢复数据阶段(冷启动)
    系统->>UIAbility(目标端): onCreate(want, launchParam)
    UIAbility(目标端)->>UIAbility(目标端): 检查 launchReason == CONTINUATION
    UIAbility(目标端)->>UIAbility(目标端): 从 want.parameters 获取数据
    系统->>UIAbility(目标端): onWindowStageRestore(windowStage)
    UIAbility(目标端)->>UIAbility(目标端): 恢复页面栈和UI状态
    Note over UIAbility(目标端): ⚠️ 不执行 onWindowStageCreate()

    Note over 系统,UIAbility(目标端): 目标端恢复数据阶段(单实例热启动)
    系统->>UIAbility(目标端): onNewWant(want, launchParam)
    UIAbility(目标端)->>UIAbility(目标端): 检查 launchReason == CONTINUATION
    UIAbility(目标端)->>UIAbility(目标端): 从 want.parameters 获取数据
    系统->>UIAbility(目标端): onWindowStageRestore(windowStage)
    UIAbility(目标端)->>UIAbility(目标端): 恢复页面栈和UI状态

五、开发步骤

5.1 Step 1:开启应用接续能力

module.json5abilities配置中启用接续能力:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "continuable": true,  // 开启应用接续
        "launchType": "singleton"
      }
    ]
  }
}

5.2 Step 2:配置应用启动模式

HarmonyOS提供三种启动模式,影响应用接续的生命周期调用:

启动模式说明目标端恢复接口适用场景
singleton(单实例)系统中只存在一个该UIAbility实例onNewWant()大多数应用
multiton(多实例)每次启动创建新实例onCreate()支持多窗口的应用
specified(指定实例)运行时决定是否创建新实例onCreate() 或 onNewWant()复杂业务场景
{
  "module": {
    "abilities": [
      {
        "launchType": "singleton"  // 或 "multiton" / "specified"
      }
    ]
  }
}

5.3 Step 3:源端保存数据(onContinue)

在源端UIAbility中实现onContinue()接口,完成数据保存和版本检测:

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

const TAG = '[ContinuationAbility]';
const DOMAIN = 0xFF00;

export default class EntryAbility extends UIAbility {

  /**
   * 源端保存接续数据
   * @param wantParam 用于保存数据的参数对象
   * @returns OnContinueResult 返回是否同意接续
   */
  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
    hilog.info(DOMAIN, TAG, '应用接续开始,保存数据');

    // 1. 版本兼容性检测
    const targetVersion = wantParam.version as string;
    const currentVersion = '1.0.0'; // 当前应用版本

    if (targetVersion !== currentVersion) {
      hilog.warn(DOMAIN, TAG, `版本不匹配: 源端=${currentVersion}, 目标端=${targetVersion}`);
      return AbilityConstant.OnContinueResult.MISMATCH; // 版本不兼容,拒绝接续
    }

    // 2. 保存业务数据(控制在100KB以内)
    wantParam['currentPage'] = 'detailPage';
    wantParam['articleId'] = '12345';
    wantParam['scrollPosition'] = 1280; // 滚动位置
    wantParam['readingProgress'] = 65; // 阅读进度
    wantParam['lastUpdateTime'] = Date.now();

    // 3. 保存复杂对象数据
    const userData = {
      userId: 'user001',
      bookmarks: ['pos1', 'pos2'],
      settings: {
        fontSize: 16,
        theme: 'dark'
      }
    };
    wantParam['userData'] = JSON.stringify(userData);

    hilog.info(DOMAIN, TAG, `数据保存完成,大小约 ${JSON.stringify(wantParam).length} 字节`);

    return AbilityConstant.OnContinueResult.AGREE; // 同意接续
  }
}

关键要点:

  • wantParam.version:获取目标端应用版本,用于兼容性检测
  • 返回值:
    • AGREE:同意接续,系统将数据传输到目标端
    • MISMATCH:版本不兼容,拒绝接续
    • REJECT:拒绝接续
  • 数据大小限制:wantParam总大小需控制在100KB以下

5.4 Step 4:目标端恢复数据

5.4.1 冷启动场景(onCreate)

多实例模式或首次启动时,通过onCreate()恢复数据:

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

const TAG = '[ContinuationAbility]';
const DOMAIN = 0xFF00;

export default class EntryAbility extends UIAbility {

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'UIAbility onCreate');

    // 1. 判断是否为接续启动
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      hilog.info(DOMAIN, TAG, '接续启动,开始恢复数据');

      // 2. 从want.parameters获取接续数据
      const params = want.parameters;
      if (params) {
        const currentPage = params['currentPage'] as string;
        const articleId = params['articleId'] as string;
        const scrollPosition = params['scrollPosition'] as number;
        const readingProgress = params['readingProgress'] as number;

        // 3. 恢复复杂对象数据
        const userDataStr = params['userData'] as string;
        if (userDataStr) {
          const userData = JSON.parse(userDataStr);
          hilog.info(DOMAIN, TAG, `用户数据: ${JSON.stringify(userData)}`);
        }

        // 4. 保存到全局状态或缓存,供页面使用
        AppStorage.setOrCreate('continuationData', {
          currentPage,
          articleId,
          scrollPosition,
          readingProgress
        });

        hilog.info(DOMAIN, TAG, `数据恢复完成: 文章=${articleId}, 进度=${readingProgress}%`);
      }
    } else {
      hilog.info(DOMAIN, TAG, '正常启动');
    }
  }

  /**
   * 接续场景下的页面栈恢复
   * ⚠️ 注意:接续时不会调用onWindowStageCreate()
   */
  onWindowStageRestore(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, TAG, 'onWindowStageRestore 开始');

    // 1. 加载主页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, TAG, `加载页面失败: ${JSON.stringify(err)}`);
        return;
      }

      // 2. 获取接续数据
      const continuationData = AppStorage.get<object>('continuationData');

      // 3. 触发页面恢复逻辑
      if (continuationData) {
        hilog.info(DOMAIN, TAG, '触发页面状态恢复');
        // 通知页面进行UI状态恢复
        AppStorage.setOrCreate('shouldRestore', true);
      }

      hilog.info(DOMAIN, TAG, 'onWindowStageRestore 完成');
    });
  }
}
5.4.2 单实例热启动场景(onNewWant)

单实例模式下,若应用已运行,通过onNewWant()恢复数据:

export default class EntryAbility extends UIAbility {

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'UIAbility onNewWant');

    // 1. 判断是否为接续启动
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      hilog.info(DOMAIN, TAG, '接续启动(热启动),开始恢复数据');

      // 2. 从want.parameters获取接续数据
      const params = want.parameters;
      if (params) {
        const currentPage = params['currentPage'] as string;
        const articleId = params['articleId'] as string;
        const scrollPosition = params['scrollPosition'] as number;

        // 3. 更新全局状态
        AppStorage.setOrCreate('continuationData', {
          currentPage,
          articleId,
          scrollPosition
        });

        // 4. 触发页面更新
        AppStorage.setOrCreate('shouldRestore', true);

        hilog.info(DOMAIN, TAG, `热启动数据恢复完成`);
      }
    }
  }

  // onWindowStageRestore() 实现同上
}

5.5 Step 5:页面层数据恢复

在页面代码中监听恢复标志,执行UI状态恢复:

import { hilog } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct ArticleDetailPage {
  @StorageLink('shouldRestore') shouldRestore: boolean = false;
  @StorageLink('continuationData') continuationData: object | undefined = undefined;

  @State articleId: string = '';
  @State scrollPosition: number = 0;
  private scroller: Scroller = new Scroller();

  aboutToAppear() {
    // 监听接续恢复
    if (this.shouldRestore && this.continuationData) {
      this.restorePageState();
      AppStorage.setOrCreate('shouldRestore', false); // 重置标志
    }
  }

  /**
   * 恢复页面状态
   */
  private restorePageState() {
    const data = this.continuationData as Record<string, object>;

    // 恢复业务数据
    this.articleId = data['articleId'] as string;
    this.scrollPosition = data['scrollPosition'] as number;

    hilog.info(0xFF00, '[ArticlePage]', `恢复文章ID: ${this.articleId}`);

    // 恢复滚动位置
    setTimeout(() => {
      this.scroller.scrollTo({
        xOffset: 0,
        yOffset: this.scrollPosition,
        animation: { duration: 300 }
      });
      hilog.info(0xFF00, '[ArticlePage]', `恢复滚动位置: ${this.scrollPosition}`);
    }, 100);
  }

  build() {
    Column() {
      Scroll(this.scroller) {
        // 文章内容
        Text(`文章内容 - ID: ${this.articleId}`)
          .fontSize(16)
          .padding(20)
      }
      .height('100%')
    }
  }
}

六、高级特性

6.1 动态设置迁移状态

可在运行时动态控制是否允许应用接续:

import { wantConstant } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {

  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
    // 检查当前业务状态
    const isUploading = this.checkUploadStatus();

    if (isUploading) {
      hilog.warn(DOMAIN, TAG, '正在上传文件,暂不支持接续');
      return AbilityConstant.OnContinueResult.REJECT;
    }

    // 检查敏感数据
    const hasSensitiveData = this.checkSensitiveData();
    if (hasSensitiveData) {
      hilog.warn(DOMAIN, TAG, '包含敏感数据,拒绝接续');
      return AbilityConstant.OnContinueResult.REJECT;
    }

    // 正常接续
    wantParam['data'] = 'business data';
    return AbilityConstant.OnContinueResult.AGREE;
  }

  private checkUploadStatus(): boolean {
    // 检查上传状态逻辑
    return false;
  }

  private checkSensitiveData(): boolean {
    // 检查敏感数据逻辑
    return false;
  }
}

6.2 选择性恢复页面栈

默认情况下,系统会自动恢复页面栈。如需自定义页面栈恢复逻辑:

onWindowStageRestore(windowStage: window.WindowStage): void {
  const continuationData = AppStorage.get<object>('continuationData');
  const data = continuationData as Record<string, object>;

  // 根据业务需求决定加载哪个页面
  const targetPage = data['currentPage'] as string;

  switch (targetPage) {
    case 'detailPage':
      windowStage.loadContent('pages/ArticleDetail', (err) => {
        // 恢复详情页
      });
      break;
    case 'editPage':
      windowStage.loadContent('pages/Editor', (err) => {
        // 恢复编辑页
      });
      break;
    default:
      windowStage.loadContent('pages/Index', (err) => {
        // 默认首页
      });
  }
}

6.3 接续后源端应用行为控制

可配置接续完成后,源端应用是否退出:

onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
  // 保存数据
  wantParam['data'] = 'business data';

  // 配置接续后源端不退出(默认会退出)
  wantParam['ohos.extra.param.key.supportContinueSourceExit'] = false;

  return AbilityConstant.OnContinueResult.AGREE;
}

七、数据管理最佳实践

7.1 数据大小控制

100KB限制示例:

onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
  const businessData = this.getBusinessData();
  const dataStr = JSON.stringify(businessData);
  const dataSize = new TextEncoder().encode(dataStr).length;

  if (dataSize > 100 * 1024) { // 超过100KB
    hilog.warn(DOMAIN, TAG, `数据过大: ${dataSize} 字节,使用分布式对象`);

    // 仅传递数据标识
    wantParam['dataId'] = businessData.id;
    wantParam['useDistributedObject'] = true;

    // 使用分布式数据对象同步大数据
    this.syncViaDistributedObject(businessData);
  } else {
    wantParam['data'] = dataStr;
  }

  return AbilityConstant.OnContinueResult.AGREE;
}

7.2 分布式数据对象

对于大数据(>100KB),推荐使用分布式数据对象:

import { distributedDataObject } from '@kit.ArkData';

export default class EntryAbility extends UIAbility {
  private distributedObject?: distributedDataObject.DataObject;

  // 源端:创建分布式对象
  private syncViaDistributedObject(data: object) {
    this.distributedObject = distributedDataObject.create(this.context, data);
    this.distributedObject.setSessionId('session_' + Date.now());

    hilog.info(DOMAIN, TAG, '分布式对象已创建');
  }

  // 目标端:订阅分布式对象
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      const params = want.parameters;

      if (params && params['useDistributedObject']) {
        const dataId = params['dataId'] as string;
        this.subscribeDistributedObject(dataId);
      }
    }
  }

  private subscribeDistributedObject(dataId: string) {
    this.distributedObject = distributedDataObject.create(this.context, {});
    this.distributedObject.setSessionId('session_' + dataId);

    this.distributedObject.on('change', (sessionId, fields) => {
      hilog.info(DOMAIN, TAG, '分布式对象数据已同步');
      // 处理同步的数据
    });
  }
}

八、完整示例

8.1 阅读应用接续示例

场景:用户在手机上阅读小说,切换到平板继续阅读,保持章节和滚动位置。

EntryAbility.ets(完整代码)
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[ReadingAppAbility]';
const DOMAIN = 0xFF00;
const APP_VERSION = '1.0.0';

interface ReadingData {
  bookId: string;
  chapterId: string;
  scrollY: number;
  readingProgress: number;
  lastUpdateTime: number;
}

export default class EntryAbility extends UIAbility {

  // ==================== 源端:保存数据 ====================
  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
    hilog.info(DOMAIN, TAG, '=== 开始应用接续 ===');

    // 1. 版本兼容性检测
    const targetVersion = wantParam.version as string;
    if (targetVersion !== APP_VERSION) {
      hilog.error(DOMAIN, TAG, `版本不匹配: 当前=${APP_VERSION}, 目标=${targetVersion}`);
      return AbilityConstant.OnContinueResult.MISMATCH;
    }

    // 2. 获取当前阅读数据
    const readingData: ReadingData = {
      bookId: AppStorage.get<string>('currentBookId') || '',
      chapterId: AppStorage.get<string>('currentChapterId') || '',
      scrollY: AppStorage.get<number>('scrollPosition') || 0,
      readingProgress: AppStorage.get<number>('readingProgress') || 0,
      lastUpdateTime: Date.now()
    };

    // 3. 检查数据有效性
    if (!readingData.bookId || !readingData.chapterId) {
      hilog.warn(DOMAIN, TAG, '无有效阅读数据,取消接续');
      return AbilityConstant.OnContinueResult.REJECT;
    }

    // 4. 序列化并保存数据
    wantParam['readingData'] = JSON.stringify(readingData);
    wantParam['appVersion'] = APP_VERSION;

    // 5. 计算数据大小
    const dataSize = JSON.stringify(wantParam).length;
    hilog.info(DOMAIN, TAG, `数据保存完成,大小: ${dataSize} 字节`);
    hilog.info(DOMAIN, TAG, `书籍: ${readingData.bookId}, 章节: ${readingData.chapterId}, 进度: ${readingData.readingProgress}%`);

    if (dataSize > 100 * 1024) {
      hilog.warn(DOMAIN, TAG, '⚠️ 数据超过100KB,建议使用分布式对象');
    }

    return AbilityConstant.OnContinueResult.AGREE;
  }

  // ==================== 目标端:恢复数据(冷启动) ====================
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'onCreate 调用');

    // 判断启动原因
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      hilog.info(DOMAIN, TAG, '=== 接续启动(冷启动) ===');
      this.restoreData(want);
    } else {
      hilog.info(DOMAIN, TAG, '正常启动');
    }
  }

  // ==================== 目标端:恢复数据(单实例热启动) ====================
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'onNewWant 调用');

    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      hilog.info(DOMAIN, TAG, '=== 接续启动(热启动) ===');
      this.restoreData(want);
    }
  }

  // ==================== 恢复数据通用方法 ====================
  private restoreData(want: Want): void {
    const params = want.parameters;
    if (!params) {
      hilog.error(DOMAIN, TAG, 'want.parameters 为空');
      return;
    }

    try {
      // 解析接续数据
      const readingDataStr = params['readingData'] as string;
      const readingData: ReadingData = JSON.parse(readingDataStr);

      // 保存到全局状态
      AppStorage.setOrCreate('currentBookId', readingData.bookId);
      AppStorage.setOrCreate('currentChapterId', readingData.chapterId);
      AppStorage.setOrCreate('scrollPosition', readingData.scrollY);
      AppStorage.setOrCreate('readingProgress', readingData.readingProgress);
      AppStorage.setOrCreate('isContinuation', true); // 标记为接续启动

      hilog.info(DOMAIN, TAG, '✅ 数据恢复成功');
      hilog.info(DOMAIN, TAG, `书籍ID: ${readingData.bookId}`);
      hilog.info(DOMAIN, TAG, `章节ID: ${readingData.chapterId}`);
      hilog.info(DOMAIN, TAG, `滚动位置: ${readingData.scrollY}px`);
      hilog.info(DOMAIN, TAG, `阅读进度: ${readingData.readingProgress}%`);

    } catch (error) {
      hilog.error(DOMAIN, TAG, `数据恢复失败: ${JSON.stringify(error)}`);
    }
  }

  // ==================== 页面栈恢复 ====================
  onWindowStageRestore(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, TAG, 'onWindowStageRestore 开始');

    // 加载阅读页面
    windowStage.loadContent('pages/ReadingPage', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, TAG, `页面加载失败: ${JSON.stringify(err)}`);
        return;
      }

      hilog.info(DOMAIN, TAG, '✅ 阅读页面加载完成');

      // 通知页面进行UI恢复
      AppStorage.setOrCreate('shouldRestoreUI', true);
    });
  }

  // ==================== 正常启动的页面创建 ====================
  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, TAG, 'onWindowStageCreate 开始(正常启动)');

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, TAG, `页面加载失败: ${JSON.stringify(err)}`);
        return;
      }
      hilog.info(DOMAIN, TAG, '首页加载完成');
    });
  }
}
ReadingPage.ets(页面恢复代码)
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[ReadingPage]';
const DOMAIN = 0xFF00;

@Entry
@Component
struct ReadingPage {
  // 状态管理
  @StorageLink('isContinuation') isContinuation: boolean = false;
  @StorageLink('shouldRestoreUI') shouldRestoreUI: boolean = false;
  @StorageLink('currentBookId') bookId: string = '';
  @StorageLink('currentChapterId') chapterId: string = '';
  @StorageLink('scrollPosition') scrollY: number = 0;
  @StorageLink('readingProgress') progress: number = 0;

  // 本地状态
  @State content: string = '';
  private scroller: Scroller = new Scroller();

  // ==================== 生命周期 ====================
  aboutToAppear() {
    hilog.info(DOMAIN, TAG, '页面即将显示');

    // 检查是否需要恢复UI
    if (this.isContinuation && this.shouldRestoreUI) {
      hilog.info(DOMAIN, TAG, '=== 开始恢复UI状态 ===');
      this.restoreUIState();

      // 重置恢复标志
      AppStorage.setOrCreate('shouldRestoreUI', false);
      AppStorage.setOrCreate('isContinuation', false);
    } else {
      hilog.info(DOMAIN, TAG, '正常页面初始化');
      this.loadDefaultContent();
    }
  }

  // ==================== UI状态恢复 ====================
  private restoreUIState() {
    hilog.info(DOMAIN, TAG, `恢复书籍: ${this.bookId}, 章节: ${this.chapterId}`);

    // 1. 加载章节内容
    this.loadChapterContent(this.bookId, this.chapterId);

    // 2. 延迟恢复滚动位置(等待内容渲染)
    setTimeout(() => {
      this.scroller.scrollTo({
        xOffset: 0,
        yOffset: this.scrollY,
        animation: {
          duration: 500,
          curve: Curve.EaseInOut
        }
      });

      hilog.info(DOMAIN, TAG, `✅ 滚动位置已恢复: ${this.scrollY}px, 进度: ${this.progress}%`);
    }, 300);
  }

  // ==================== 内容加载 ====================
  private loadChapterContent(bookId: string, chapterId: string) {
    // 模拟加载章节内容
    this.content = `这是书籍 ${bookId} 的章节 ${chapterId} 的内容...\n\n` +
      '第一段内容...\n'.repeat(50);

    hilog.info(DOMAIN, TAG, '章节内容加载完成');
  }

  private loadDefaultContent() {
    this.content = '欢迎使用阅读应用';
  }

  // ==================== UI构建 ====================
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('阅读中')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)

        Text(`进度: ${this.progress}%`)
          .fontSize(14)
          .margin({ left: 20 })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#f0f0f0')

      // 内容区域
      Scroll(this.scroller) {
        Column() {
          Text(this.content)
            .fontSize(16)
            .lineHeight(28)
            .padding(20)
        }
      }
      .width('100%')
      .layoutWeight(1)
      .onScroll((xOffset: number, yOffset: number) => {
        // 实时保存滚动位置
        AppStorage.setOrCreate('scrollPosition', yOffset);
      })
    }
    .width('100%')
    .height('100%')
  }
}

九、常见问题与解决方案

9.1 onWindowStageCreate不执行

问题:接续启动时,onWindowStageCreate()未被调用,导致初始化逻辑缺失。

原因:接续启动时,系统调用onWindowStageRestore(),而不调用onWindowStageCreate()

解决方案

// ❌ 错误做法:只在onWindowStageCreate中初始化
onWindowStageCreate(windowStage: window.WindowStage): void {
  this.initializeApp(); // 接续时不会执行
}

// ✅ 正确做法:在onWindowStageRestore中也执行初始化
onWindowStageRestore(windowStage: window.WindowStage): void {
  this.initializeApp(); // 确保接续时也初始化
  windowStage.loadContent('pages/Index', (err) => {
    // 页面加载
  });
}

9.2 数据超过100KB

问题:wantParam数据超过100KB,导致接续失败。

解决方案

  1. 精简数据:仅传输必要的状态标识
  2. 使用分布式数据对象:大数据通过分布式对象同步
  3. 数据压缩:对JSON数据进行压缩
// 方案1:仅传输ID
wantParam['dataId'] = businessData.id; // 仅几个字节

// 方案2:数据压缩(示例)
const compressedData = this.compressData(largeData);
wantParam['compressedData'] = compressedData;

9.3 版本兼容性处理

问题:不同版本应用间接续失败。

解决方案

onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
  const targetVersion = wantParam.version as string;
  const currentMajorVersion = APP_VERSION.split('.')[0];
  const targetMajorVersion = targetVersion.split('.')[0];

  // 仅检查主版本号
  if (currentMajorVersion !== targetMajorVersion) {
    return AbilityConstant.OnContinueResult.MISMATCH;
  }

  // 次版本号不同时,提供降级数据
  wantParam['dataVersion'] = currentMajorVersion;
  wantParam['data'] = this.getCompatibleData(targetVersion);

  return AbilityConstant.OnContinueResult.AGREE;
}

9.4 页面状态丢失

问题:接续后,页面部分状态未恢复。

解决方案

// 使用AppStorage/PersistentStorage保存关键状态
aboutToAppear() {
  if (this.isContinuation) {
    // 恢复所有必要状态
    this.fontSize = AppStorage.get('fontSize') || 16;
    this.theme = AppStorage.get('theme') || 'light';
    this.bookmarks = AppStorage.get('bookmarks') || [];

    // 触发UI更新
    this.restoreUIState();
  }
}

十、性能优化建议

10.1 数据序列化优化

// ❌ 低效:多次序列化
wantParam['data1'] = JSON.stringify(obj1);
wantParam['data2'] = JSON.stringify(obj2);

// ✅ 高效:一次性序列化
const combinedData = { data1: obj1, data2: obj2 };
wantParam['allData'] = JSON.stringify(combinedData);

10.2 异步加载优化

private async restoreUIState() {
  // 先恢复轻量数据,快速展示界面
  this.restoreLightweightState();

  // 异步加载重数据
  Promise.all([
    this.loadChapterContent(this.bookId, this.chapterId),
    this.loadUserPreferences(),
    this.loadBookmarks()
  ]).then(() => {
    hilog.info(DOMAIN, TAG, '所有数据加载完成');
    this.scroller.scrollTo({ yOffset: this.scrollY });
  });
}

10.3 滚动位置恢复优化

// ❌ 立即恢复可能失败(内容未渲染)
this.scroller.scrollTo({ yOffset: this.scrollY });

// ✅ 延迟恢复,确保内容已渲染
setTimeout(() => {
  this.scroller.scrollTo({ yOffset: this.scrollY });
}, 300);

// ✅ 最佳:监听内容加载完成
onContentLoaded() {
  this.scroller.scrollTo({ yOffset: this.scrollY });
}

十一、测试与调试

11.1 日志输出

import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[Continuation]';
const DOMAIN = 0xFF00;

// 源端日志
hilog.info(DOMAIN, TAG, `[源端] 保存数据: ${JSON.stringify(data)}`);

// 目标端日志
hilog.info(DOMAIN, TAG, `[目标端] 恢复数据: ${JSON.stringify(data)}`);

// 性能日志
const startTime = Date.now();
// ... 操作 ...
hilog.info(DOMAIN, TAG, `耗时: ${Date.now() - startTime}ms`);

11.2 测试清单

  • 单实例模式接续测试
  • 多实例模式接续测试
  • 冷启动接续测试
  • 热启动接续测试
  • 版本兼容性测试
  • 大数据(接近100KB)接续测试
  • 网络断开场景测试
  • 快速切换设备测试
  • UI状态完整恢复验证
  • 性能测试(接续耗时<3秒)

十二、总结

应用接续是HarmonyOS提供的强大跨设备协同能力,通过合理使用onContinue()onCreate()onNewWant()onWindowStageRestore()等核心接口,可实现应用在不同设备间的无缝流转。

关键要点:

  1. 配置continuable: true启用接续能力
  2. onContinue()中保存数据,控制在100KB以内
  3. onCreate()/onNewWant()中恢复数据
  4. onWindowStageRestore()中恢复页面和UI状态
  5. 注意接续时不调用onWindowStageCreate()
  6. 使用分布式数据对象处理大数据同步
  7. 做好版本兼容性检测
  8. 充分测试各种启动模式和场景

遵循以上指导,即可为用户打造流畅的跨设备协同体验。