在这里插入图片描述

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀

前言

App 崩溃是每个开发者都不想碰见却又绕不开的问题。Crash 发生的瞬间,用户正在填写的表单、刚刚切换到的页面状态、未来得及提交的操作——全部丢失。用户重启 App,一切从头。

HarmonyOS 从 API version 9 起,在 Ability Kit 中提供了 appRecovery 模块,允许应用在故障前保存关键状态,并在下次被框架重新拉起时恢复这些状态。这篇文章用一个最小实验把这个流程跑通。

一、这个能力要解决什么问题??

先说清楚它的范围。

appRecovery 不是捕获异常、不是 try-catch 的替代品,也不会阻止 Crash 发生。它做的事情是:

在 Crash 发生的那一刻,框架介入,保存页面栈和开发者指定的自定义数据;然后重新拉起 App;App 启动时,用保存的数据把界面状态还原回去。

对用户来说,体验上的变化是:Crash 之后不再是冷启动落在首页,而是重新回到接近崩溃前的页面状态。

这个能力覆盖两类故障:

  • JS_CRASH:ArkTS 运行时抛出的未捕获异常;
  • APP_FREEZE:应用无响应(ANR 类问题)。

二、先把官方约束弄清楚

支持的版本和设备

条件说明
API 版本API version 9 及以上
应用模型Stage 模型,不支持 FA 模型
支持设备phone / tablet / 2in1
Ability 类型UIAbility;ServiceExtensionAbility、DataShareExtensionAbility 等非 UI 类 Ability 不支持

多进程、多 UIAbility 的限制

官方文档明确说明:

当前只支持单进程单 UIAbility 的应用进行故障恢复。若应用存在多个 UIAbility,只能恢复发生故障的那个 UIAbility。

如果应用存在多个进程,需要在每个进程对应的 AbilityStage 的 onCreate 中分别调用 enableAppRecovery,才能使该进程的恢复功能生效。

这一点容易被忽略,后面还会单独提。

核心接口

所属模块:appRecovery,来自 @kit.AbilityKit。

接口说明API 版本
enableAppRecovery(restart?, saveOccasion?, saveMode?)开启故障恢复能力9+
restoreAppState()恢复上一次保存的状态9+
saveAppState(context?)主动保存当前状态10+(旧版无参数的接口已废弃)
setRestartWant(want)指定故障重启时使用的 Want11+

配合使用的生命周期回调:

回调位置说明
onSaveState(reason, wantParam)UIAbility故障或迁移触发时,框架回调此方法让开发者写入自定义数据

三、流程图:崩溃到恢复的完整路径

AbilityStage.onCreate()
  └─ appRecovery.enableAppRecovery()   ← 开启恢复能力

       │
       │(某一时刻发生 JS_CRASH 或 APP_FREEZE)
       ▼

UIAbility.onSaveState()              ← 框架回调,保存自定义数据
  └─ return AbilityConstant.OnSaveResult.APP_RECOVERY

       │
       │(框架自动重新拉起 App)
       ▼

UIAbility.onCreate()
  └─ launchParam.launchReason === AbilityConstant.LaunchReason.APP_RECOVERY
       └─ appRecovery.restoreAppState()   ← 恢复状态

整个链路分布在三个地方:AbilityStage、UIAbility 的 onSaveState、UIAbility 的 onCreate。

四、最小实验:三步跑通完整流程

目标是验证下面这件事:开启恢复能力之后,触发一次可控的 Crash,观察 App 被重新拉起并尝试恢复状态。

第一步:在 AbilityStage 中开启恢复

// entry/src/main/ets/myabilitystage/MyAbilityStage.ets
import { AbilityStage, appRecovery } from '@kit.AbilityKit';

export default class MyAbilityStage extends AbilityStage {
  onCreate(): void {
    // 发生任意故障时重启;故障发生前保存状态;写入本地文件缓存
    appRecovery.enableAppRecovery(
      appRecovery.RestartFlag.ALWAYS_RESTART,
      appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR,
      appRecovery.SaveModeFlag.SAVE_WITH_FILE
    );
  }
}

这里真正需要关注的是三个枚举参数的含义,下面单独说明。

module.json5 中需要把 srcEntry 指向这个 AbilityStage:

// entry/src/main/module.json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    "srcEntry": "./ets/myabilitystage/MyAbilityStage.ets",
    ...
  }
}

如果不配置 srcEntry,AbilityStage 就不会被加载,enableAppRecovery 也就不会执行。

第二步:在 UIAbility 中保存自定义数据并检测恢复启动

// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want, appRecovery } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class EntryAbility extends UIAbility {

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 判断是否为故障恢复启动
    if (launchParam.launchReason === AbilityConstant.LaunchReason.APP_RECOVERY) {
      hilog.info(0x0000, 'AppRecovery', '检测到故障恢复启动,开始恢复状态');
      // 恢复上次保存的页面栈和自定义数据
      const success = appRecovery.restoreAppState();
      hilog.info(0x0000, 'AppRecovery', `restoreAppState 结果: ${success}`);
    }
  }

  // 故障(或跨设备迁移)触发时,框架回调此方法
  onSaveState(
    reason: AbilityConstant.StateType,
    wantParam: Record<string, Object>
  ): AbilityConstant.OnSaveResult {
    // 只处理故障恢复触发的保存
    if (reason === AbilityConstant.StateType.APP_RECOVERY) {
      // 把需要恢复的关键数据写进 wantParam
      wantParam['lastPage'] = 'detail';
      wantParam['inputText'] = '用户正在填写的内容';
      hilog.info(0x0000, 'AppRecovery', '故障前状态已保存');
    }
    // 告知框架:同意进行故障恢复保存
    return AbilityConstant.OnSaveResult.APP_RECOVERY;
  }
}

真正需要关注的几个地方:

  1. launchParam.launchReason 的值必须和 AbilityConstant.LaunchReason.APP_RECOVERY 对比,不要硬编码数字;
  2. onSaveState 的返回值必须是 AbilityConstant.OnSaveResult.APP_RECOVERY,返回其他值框架不会保存故障现场;
  3. wantParam 里写入的自定义数据,在 restoreAppState() 之后,框架会通过 Want 传回给 Ability,可以在 onCreate 的 want.parameters 中读取。

第三步:触发一次可控崩溃用来验证

这里只是验证流程,在页面上放一个按钮,点击时制造一个未捕获异常:

// 页面 Index.ets(仅用于验证,实际项目请勿这样用)
import { hilog } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct Index {
  @State private counter: number = 0;

  build() {
    Column({ space: 20 }) {
      Text(`计数器:${this.counter}`)
        .fontSize(24)

      Button('计数 +1')
        .onClick(() => {
          this.counter += 1;
        })

      Button('模拟 Crash(仅调试用)')
        .fontColor(Color.Red)
        .onClick(() => {
          hilog.info(0x0000, 'AppRecovery', `崩溃前计数器值:${this.counter}`);
          // 制造未捕获异常
          const arr: string[] = [];
          console.log(arr[999].length.toString());
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

点击"模拟 Crash"按钮之后,App 会因未捕获异常触发 JS_CRASH,框架回调 onSaveState 保存数据,然后重新拉起 App。重新拉起后,onCreate 中会检测到 launchReason === APP_RECOVERY,并调用 restoreAppState()。

五、三个枚举参数拆开看

enableAppRecovery 的三个可选参数每个都有独立意义,容易一起传进去但不清楚各自控制什么。

RestartFlag:故障时要不要重启

枚举值说明
ALWAYS_RESTART(默认)发生任意故障时都重启
NO_RESTART_ON_JS_CRASHJS_CRASH 时不重启
NO_RESTART_ON_APP_FREEZEAPP_FREEZE 时不重启
NO_RESTART任意故障都不重启

如果设置为 NO_RESTART,框架依然会触发 onSaveState(如果配置了 SAVE_WHEN_ERROR),但不会重新拉起 App。这种场景适合只想保存 Crash 现场数据、不希望自动重启的应用。

SaveOccasionFlag:什么时候保存状态

枚举值说明
SAVE_WHEN_ERROR(默认)故障发生时才保存
SAVE_WHEN_BACKGROUND应用切入后台时也保存

两个值可以通过位运算组合:

appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR | appRecovery.SaveOccasionFlag.SAVE_WHEN_BACKGROUND

这样做的好处是:即使 Crash 发生在后台,也有最近的一次后台保存数据可以恢复。

SaveModeFlag:保存在哪里

枚举值说明
SAVE_WITH_FILE(默认)写入本地文件缓存
SAVE_WITH_SHARED_MEMORY保存在内存中

选 SAVE_WITH_FILE 的数据在 App 进程被杀死后仍然存在;选 SAVE_WITH_SHARED_MEMORY 的数据进程退出后即消失,只适合进程内重启的场景。


六、容易出错的地方

AbilityStage 没有被加载

enableAppRecovery 必须在 AbilityStage 的 onCreate 中调用,这是官方文档明确的调用时机。如果 module.json5 中没有配置 srcEntry,AbilityStage 不会被实例化,这一步就永远不会执行,恢复能力始终不生效。

这个问题不会有任何报错,只是静默失效,比较难发现。

onSaveState 的返回值写错

如果 onSaveState 返回的不是 AbilityConstant.OnSaveResult.APP_RECOVERY,框架不会保存故障现场数据。有的开发者会误写成 ALL_AGREE(值为 0),这个值用于跨设备迁移保存,不是应用故障恢复场景的正确返回值。

自定义数据的读取位置

onSaveState 写入 wantParam 的数据,恢复后并不是通过 appRecovery 模块读取的,而是通过 onCreate 接收到的 want 对象的 parameters 字段读取。restoreAppState() 负责恢复页面栈,自定义数据要从 want.parameters 中拿。

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  if (launchParam.launchReason === AbilityConstant.LaunchReason.APP_RECOVERY) {
    appRecovery.restoreAppState();
    // 自定义数据从 want.parameters 中读取
    const lastPage = want.parameters?.['lastPage'];
    const inputText = want.parameters?.['inputText'];
  }
}

多 UIAbility 只能恢复故障的那个

官方文档说明,若应用有多个 UIAbility,恢复的只是发生故障的那一个。其他 UIAbility 的状态不会被一并恢复,业务层需要自行处理剩余的状态初始化逻辑。

七、排查恢复没有生效时的检查顺序

如果 App Crash 后没有被重新拉起,或者拉起后没有执行恢复逻辑,按下面顺序检查:

  1. API 版本:确认项目 minApiVersion 不低于 9,Stage 模型。
  2. 设备类型:appRecovery 只支持 phone/tablet/2in1,不支持其他设备形态。
  3. srcEntry 配置:module.json5 中 srcEntry 是否正确指向自定义的 AbilityStage。
  4. enableAppRecovery 调用位置:是否在 AbilityStage 的 onCreate 中调用,不是在 UIAbility 里。
  5. RestartFlag:是否设置了 NO_RESTART 或不匹配故障类型的标志。
  6. onSaveState 返回值:是否返回了 AbilityConstant.OnSaveResult.APP_RECOVERY。
  7. launchReason 判断:onCreate 中是否正确判断了 launchParam.launchReason === AbilityConstant.LaunchReason.APP_RECOVERY。
  8. 日志确认:在 onSaveState 和 onCreate 中打印 hilog,确认两个回调是否都被执行。

开发经验总结

  1. enableAppRecovery 必须在 AbilityStage 的 onCreate 中调用,module.json5 的 srcEntry 是前提条件,缺少这个配置会静默失效。

  2. onSaveState 返回 AbilityConstant.OnSaveResult.APP_RECOVERY 才会触发故障现场保存,返回其他值无效。

  3. 页面栈由 restoreAppState() 恢复;自定义数据通过 want.parameters 读取,两者是分开的。

  4. SaveModeFlag.SAVE_WITH_SHARED_MEMORY 只适用于进程内重启场景,数据在进程退出后消失;如果希望重启后仍能恢复,使用 SAVE_WITH_FILE。

  5. 这个能力目前只支持单进程单 UIAbility,多 UIAbility 应用在设计恢复逻辑时需要考虑未故障的 UIAbility 的状态如何单独处理。


如果你的应用本身有多个 UIAbility,可以留意一下:当某一个 UIAbility 发生 Crash 被恢复重启之后,另外几个 UIAbility 此时的栈状态是否还在你的预期之内——这类情况比单 UIAbility 应用的恢复场景要复杂一些,值得单独梳理一遍。

❤️ 如果本文帮到了你…

  • 请点个赞,让我知道你还在坚持阅读技术长文!
  • 请收藏本文,因为你以后一定还会用上!
  • 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
Logo

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

更多推荐