在这里插入图片描述

一、为什么 Stage 模型值得专门讲

很多从 FA 模型迁移过来的开发者,最不适应的不是语法变化,而是生命周期的思维方式。FA 模型的生命周期以页面为中心,一个 Ability 对应一个页面,启动和销毁都围绕页面可见性展开。Stage 模型把这个关系打碎了——Stage 管理窗口,Ability 管理业务,窗口和业务是两条独立的主线。

这种设计带来了很大的灵活性,但理解成本也更高。什么时候该在 onWindowStageCreate 里初始化?什么时候该在 onForeground 里刷新数据?窗口最小化和应用切后台有什么区别?多 Ability 之间怎么通信?这些问题在 FA 模型下几乎不存在,Stage 模型下却需要开发者主动理解。

一个常见的困惑是:为什么应用退到后台时没有触发 onDestroy?为什么从后台切回来时有些数据消失了?为什么窗口缩小后布局没有自动调整?这些问题的答案都藏在生命周期回调的设计逻辑里。理解了这个设计逻辑,很多看似奇怪的行为其实都有合理的解释。

这篇文章用一张完整的生命周期图作为主线,把每个回调函数的触发时机、适用场景、常见误区逐一讲清楚。所有代码基于 HarmonyOS NEXT API 12+,复制到 DevEco Studio 即可运行。

二、FA 模型 vs Stage 模型:本质区别在哪里

FA 模型是早期 HarmonyOS 设计的产物,结构简单,上手快,适合单页面应用。但当应用规模变大、需要同时支持多种设备形态时,FA 模型的局限就开始显现。

FA 模型的核心问题是窗口管理和业务逻辑没有分离。 Ability 既要处理生命周期事件,又要管理窗口状态,还要承载页面渲染逻辑。当同一个应用需要在手机和平板上展示不同的页面结构时,要么写大量条件分支判断设备类型,要么做多套 Ability 分别打包——两种做法都不优雅。

Stage 模型对应用架构做了重新设计。它引入了一个中间层——WindowStage。原来由 Ability 直接管理的窗口操作(创建、销毁、大小变化)全部交给 WindowStage 处理,Ability 只负责自己的业务逻辑和页面加载决策。两者的职责边界清晰了,代码自然就不乱了。

这种设计还有一个实际好处:同一个 Ability 可以对应多个窗口。 比如文档应用打开多个文档,每个文档一个窗口,但它们共享同一个 Ability 实例。FA 模型下这几乎不可能实现,Stage 模型下只是普通的多窗口管理。

┌─────────────────────────────────────────────────────────────┐
│                        应用进程                               │
│                                                             │
│  ┌──────────────┐    ┌────────────────────────────────────┐│
│  │  WindowStage │    │          UIAbility (Entry)           ││
│  │ ───────────│    │ ────────────────────────────────────││
│  │ loadContent │    │ onCreate ──→ onWindowStageCreate      ││
│  │ getMainWindow│   │ onForeground ←─┐                     ││
│  │ setFullScreen│   │ onBackground ──┘                     ││
│  │ minimize/max │   │ onWindowStageDestroy                  ││
│  │ on('window   │   │ onDestroy                             ││
│  │   SizeChange')│   │                                       ││
│  └──────────────┘    └────────────────────────────────────┘│
│         ↕                        ↕                           │
│  窗口生命周期           应用 / 业务生命周期                    │
└─────────────────────────────────────────────────────────────┘

上面这张图描述了 Stage 模型下两个生命周期的关系。WindowStage 生命周期围绕窗口的创建和销毁,比如加载页面、窗口大小变化、进入多窗口模式。Ability 生命周期围绕应用在前台和后台之间的切换,以及整个应用的启动和终止。两条线独立演进,互不阻塞。

三、完整生命周期图解

Ability 的生命周期有六个核心回调,按触发顺序排列如下:

[应用启动]
    │
    ▼
onCreate() ─────────────────────────────── 应用冷启动时执行一次
    │                                    用于初始化全局资源
    ▼
onWindowStageCreate() ─────────────────── 窗口 Stage 创建完成后执行
    │                                    只执行一次,是加载页面的最佳时机
    │
    ▼
[应用进入前台] ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←
    │                                    用户从后台切回、或应用首次启动
onForeground() ───────────────────────── 应用进入前台时执行
    │                                    用于刷新数据、重建连接、恢复状态
    │
    ▼
  [用户在前台操作]
    │
    │  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←
    │
    ▼
[应用切到后台]
    │
onBackground() ────────────────────────── 应用进入后台时执行
    │                                    用于保存数据、暂停任务、释放资源
    │
    ▼  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←  ←
    │
    │  [用户切回前台 → onForeground]
    │  [系统内存不足 → Ability 被销毁 → onDestroy]
    │  [应用退出 → onWindowStageDestroy → onDestroy]
    │
    ▼
onWindowStageDestroy() ───────────────── 窗口 Stage 销毁时执行
    │                                    通常对应应用退出或多窗口关闭
    ▼
onDestroy() ──────────────────────────── Ability 完全销毁前执行
                                         用于释放所有资源、取消所有监听

理解这张图的关键是区分两条独立的触发线onForegroundonBackground 反映的是应用是否在前台,由系统根据应用可见性自动调度;onWindowStageCreateonWindowStageDestroy 反映的是窗口是否存活,通常由用户关闭应用或切换多窗口模式触发。

四、EntryAbility 完整代码

一个标准的 EntryAbility 包含上述所有生命周期的实现:

// entry/src/main/ets/entryability/EntryAbility.ets

import UIAbility from '@ohos.app.ability.UIAbility'
import Window from '@ohos.window'
import Want from '@ohos.app.ability.Want'
import AbilityConstant from '@ohos.app.ability.AbilityConstant'

const TAG = '[EntryAbility]'

export default class EntryAbility extends UIAbility {
  // 1. 应用冷启动时调用,整个应用生命周期只执行一次
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info(`${TAG} onCreate, want: ${JSON.stringify(want)}`)
    console.info(`${TAG} launch reason: ${launchParam.launchReason}`)
    this.initGlobalResources()
  }

  // 2. 窗口 Stage 创建完成后调用,是加载页面的最佳时机
  onWindowStageCreate(windowStage: Window.WindowStage): void {
    console.info(`${TAG} onWindowStageCreate`)

    const mainWindow = windowStage.getMainWindowSync()
    const width = px2vp(mainWindow.getWindowProperties().windowWidth)
    const pagePath = width >= 768 ? 'pages/TwoColumnLayout' : 'pages/Index'

    windowStage.loadContent(pagePath, (err) => {
      if (err.code) {
        console.error(`${TAG} loadContent failed: ${JSON.stringify(err)}`)
      } else {
        console.info(`${TAG} loadContent success: ${pagePath}`)
      }
    })

    this.configureWindow(mainWindow)
  }

  // 3. 应用进入前台时调用,可能被调用多次
  onForeground(): void {
    console.info(`${TAG} onForeground`)
    this.refreshApplicationData()
  }

  // 4. 应用进入后台时调用,可能被调用多次
  onBackground(): void {
    console.info(`${TAG} onBackground`)
    this.persistApplicationState()
  }

  // 5. 窗口 Stage 销毁前调用
  onWindowStageDestroy(): void {
    console.info(`${TAG} onWindowStageDestroy`)
    this.cleanupWindowResources()
  }

  // 6. Ability 完全销毁时调用,整个应用生命周期只执行一次
  onDestroy(): void {
    console.info(`${TAG} onDestroy`)
    this.releaseGlobalResources()
  }

  private initGlobalResources(): void {
    console.info(`${TAG} Initializing global resources...`)
  }

  private configureWindow(win: Window.Window): void {
    win.setBackgroundColor('#FFFFFF')
    win.setPreferredStatusBarStyle(AbilityConstant.StatusBarStyle.DARK)
    win.on('windowSizeChange', (size) => {
      console.info(`[Window] Resized: ${size.width} × ${size.height}`)
    })
  }

  private refreshApplicationData(): void {
    console.info(`${TAG} Refreshing application data...`)
  }

  private persistApplicationState(): void {
    console.info(`${TAG} Persisting application state...`)
  }

  private cleanupWindowResources(): void {
    console.info(`${TAG} Cleaning up window resources...`)
    const win = Window.getLastWindowSync(this.context)
    win.off('windowSizeChange')
  }

  private releaseGlobalResources(): void {
    console.info(`${TAG} Releasing global resources...`)
  }
}

在这里插入图片描述

五、每个回调的详细使用场景与常见误区

5.1 onCreate:全局初始化的唯一场所

onCreate 在应用进程启动时调用,整个应用生命周期只执行一次。这是初始化全局资源的最佳时机——比如推送 SDK 连接、数据库连接池、埋点服务注册、全局异常捕获设置。

但要注意,onCreate 的执行时机很早,彼时 WindowStage 还没有创建,UI 相关的操作还不能做。另外,onCreate 里的代码会阻塞应用启动,如果初始化逻辑耗时过长,用户会感觉到明显的启动延迟。

一个常见的优化是把非必要的初始化延迟到首次访问时执行,而不是全部堆在 onCreate 里:

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 必须立即完成:日志系统初始化、崩溃捕获
  this.initCrashHandler()

  // 可以延迟的非必要初始化:推送连接、数据库预热
  // 用 setTimeout 延迟 1 秒,不阻塞首帧渲染
  setTimeout(() => this.initOptionalServices(), 1000)
}

private initCrashHandler(): void {
  process.on('uncaughtException', (err) => {
    console.error('[Crash]', err.message, err.stack)
    // 上报到监控平台
  })
}

5.2 onWindowStageCreate:加载页面的最佳时机

onWindowStageCreate 是整个 Ability 生命周期里使用频率最高的回调。窗口 Stage 创建完成后,页面加载必须在这里执行。离开了这一步,应用启动后只会显示一个空白窗口。

onWindowStageCreate(windowStage: Window.WindowStage): void {
  // 正确:在这里加载页面
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) console.error('Load failed')
  })

  // 错误:不要在这里做耗时操作,会阻塞页面渲染
  // const data = await fetchDataFromNetwork()  // ❌ 不要这样做
}

值得特别注意的是,loadContent 有两种调用方式:回调形式和 Promise 形式。在 API 12+ 中,两种方式都可用,但要注意错误处理。回调形式的错误信息通过 err 参数传递,Promise 形式需要用 try/catch 包裹:

// Promise 写法(推荐)
async onWindowStageCreate(windowStage: Window.WindowStage): Promise<void> {
  try {
    await windowStage.loadContent('pages/Index')
    console.info(`${TAG} Page loaded successfully`)
  } catch (err) {
    console.error(`${TAG} loadContent failed: ${JSON.stringify(err)}`)
  }
}

5.3 onForeground 和 onBackground:前后台切换的核心

onForeground 在每次应用从后台切回前台时触发。这是最适合刷新数据的时机——网络请求、数据库查询、数据同步都可以在这里发起。但要避免做耗时太长的操作,用户刚切回应用时希望立刻看到内容,而不是等待加载。

onBackground 的触发时机是应用完全不可见时。这是保存草稿、暂停定时任务、释放非必要资源的最佳时机。比如文档编辑器可以在此时自动保存所有未提交的修改,音视频应用可以在此时暂停播放。

onForeground(): void {
  console.info(`${TAG} App returned to foreground`)
  // 刷新数据,但不阻塞 UI
  this.fetchLatestData().catch(e => console.error(e))
}

onBackground(): void {
  console.info(`${TAG} App moved to background`)
  // 立刻保存草稿,不要等待用户主动操作
  this.saveAllDrafts()
  // 暂停所有活跃的网络请求
  this.pauseAllRequests()
}

有一个常见的陷阱要特别说明:onForeground 被调用不代表窗口已经完全可见和可交互。从 onForeground 返回到页面真正完成渲染之间还有一段时间。如果在 onForeground 中启动动画或弹出对话框,有时会遇到闪烁或布局抖动。更稳妥的做法是在 onForeground 中发起数据请求,然后在数据返回后更新 UI,这样用户看到的就是最终状态,而不是中间的过渡状态。

5.4 onWindowStageDestroy 和 onDestroy:资源释放的最后关卡

onWindowStageDestroy 对应窗口被销毁,通常发生在多窗口模式下关闭某个窗口,或者应用退出时。适合在这里清理窗口级别的监听器和配置。

onDestroy 是 Ability 生命周期的终点,整个应用只执行一次。适合做最终的清理:关闭数据库连接、取消所有定时器、清理所有缓存数据。

onDestroy(): void {
  console.info(`${TAG} Ability is about to destroy`)
  // 取消所有定时器
  this.clearAllTimers()
  // 关闭数据库
  this.closeDatabase()
  // 写入最终日志
  this.flushLogs()
}

六、Want 参数与启动参数

Want 是 Ability 之间相互启动时传递的载体,LaunchParam 描述了本次启动的原因。这两个参数在 onCreate 和 Ability 的其他回调中都可以访问。

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // want.uri: 被调用的资源标识符
  console.info(`${TAG} Want URI: ${want.uri}`)
  console.info(`${TAG} Want parameters: ${JSON.stringify(want.parameters)}`)

  // launchParam.launchReason: 启动原因枚举
  console.info(`${TAG} Launch reason: ${launchParam.launchReason}`)
  // 可能的值包括:
  // - INDEX: 正常启动(从桌面图标)
  // - LAUNCH_FROM_BROWSER: 从浏览器拉起
  // - LAUNCH_FROM_SHORTCUT: 从快捷方式启动
  // - LAUNCH_FROM_SHARE: 从分享面板启动
  // - RESTART: 应用重启
  // - SELF_RESTART: 应用自重启(崩溃恢复)

  // launchParam.lastExitReason: 上次退出原因
  console.info(`${TAG} Last exit reason: ${launchParam.lastExitReason}`)
}

根据 launchReason 做不同的初始化分支,是处理多入口启动的标准做法。比如从快捷方式启动的应用应该直接跳到特定页面,从分享面板启动的应用应该接收分享内容并处理。

下面是一个根据启动原因做分支处理的完整示例:

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  switch (launchParam.launchReason) {
    case AbilityConstant.LaunchReason.INDEX:
      // 正常启动:走标准流程
      console.info(`${TAG} Normal launch from home screen`)
      break

    case AbilityConstant.LaunchReason.LAUNCH_FROM_SHORTCUT:
      // 从快捷方式启动:提取快捷方式参数,跳转到指定页面
      const targetPage = want.parameters?.targetPage as string
      const targetParam = want.parameters?.param as string
      console.info(`${TAG} Launched from shortcut: ${targetPage}, param: ${targetParam}`)
      AppStorage.setOrCreate('launch_target', targetPage)
      AppStorage.setOrCreate('launch_param', targetParam)
      break

    case AbilityConstant.LaunchReason.LAUNCH_FROM_SHARE:
      // 从分享面板启动:提取分享内容并处理
      const sharedContent = want.parameters?.sharedContent as string
      const sharedType = want.parameters?.sharedType as string
      console.info(`${TAG} Shared content: ${sharedContent}, type: ${sharedType}`)
      AppStorage.setOrCreate('pending_share', JSON.stringify({
        content: sharedContent,
        type: sharedType
      }))
      break

    case AbilityConstant.LaunchReason.SELF_RESTART:
      // 应用自重启:从持久化存储中恢复状态
      console.warn(`${TAG} Ability self-restarted, attempting recovery`)
      this.recoverFromStorage()
      break

    default:
      console.info(`${TAG} Launch reason: ${launchParam.launchReason}`)
  }
}

七、多 Ability 场景:UIAbility 与 ExtensionAbility

大型应用通常不止一个 Ability。主 Ability 负责主界面,其他 Ability 负责特定功能,比如文档预览 Ability、分享 Ability、支付回调 Ability。它们各自有独立的生命周期,但共享同一个应用进程。

7.1 UIAbility 之间的跳转

使用 startAbility 启动另一个 Ability。目标 Ability 会在自己的 onCreateonWindowStageCreateonForeground 流程中完成初始化。

import common from '@ohos.app.ability.common'

const context = getContext(this) as common.UIAbilityContext

const want: Want = {
  deviceId: '',                    // 空字符串表示本设备
  bundleName: 'com.example.myapp',
  abilityName: 'SecondaryAbility',  // 目标 Ability 名称
  parameters: {                    // 传递给目标 Ability 的参数
    fromPage: 'home',
    noteId: '12345'
  }
}

try {
  await context.startAbility(want)
} catch (err) {
  console.error('startAbility failed:', JSON.stringify(err))
}

7.2 接收来自其他 Ability 的返回数据

启动带返回值的 Ability,使用 startAbilityForResult。这在需要用户交互后返回结果的场景中非常有用,比如从列表页跳转到编辑页,编辑完成后返回编辑结果。

async function openNoteEditor(noteId: number): Promise<NoteData | null> {
  const context = getContext(this) as common.UIAbilityContext

  const want: Want = {
    bundleName: 'com.example.myapp',
    abilityName: 'EditorAbility',
    parameters: { noteId: String(noteId) }
  }

  try {
    const result = await context.startAbilityForResult(want)
    if (result.resultCode === 0 && result.want) {
      const data = result.want.parameters?.editedNote as NoteData
      return data
    }
    return null
  } catch (err) {
    console.error('startAbilityForResult failed:', JSON.stringify(err))
    return null
  }
}

被启动的 Ability 在关闭时通过 terminateSelfWithResult 返回数据:

const context = getContext(this) as common.UIAbilityContext

function saveAndClose(note: NoteData): void {
  const result: Want = {
    parameters: { editedNote: note }
  }

  context.terminateSelfWithResult({ resultCode: 0, want: result }, (err) => {
    if (err.code) console.error('terminateSelfWithResult failed')
  })
}

function discardAndClose(): void {
  // 返回错误码,调用方知道用户取消了操作
  context.terminateSelfWithResult({ resultCode: -1 }, (err) => {
    if (err.code) console.error('terminateSelfWithResult failed')
  })
}

7.3 ExtensionAbility:处理后台任务

ExtensionAbility 是不带 UI 的 Ability,专门用于处理后台任务。常见的子类型包括:

  • ServiceExtensionAbility:后台计算任务,比如数据同步、大文件处理。
  • DataShareExtensionAbility:提供跨应用数据共享接口。
  • WorkSchedulerExtensionAbility:定时任务调度。
  • AutoFillExtensionAbility:自动填充服务。
  • FormExtensionAbility:卡片服务,提供桌面小部件。

ServiceExtensionAbility 的生命周期比 UIAbility 简单得多,只有 onCreateonRunonDestroy 三个阶段:

import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility'

export default class MyServiceAbility extends ServiceExtensionAbility {
  onCreate(want: Want): void {
    console.info('[Service] onCreate')
  }

  onRun(want: Want, runWant: Want, startId: number): void {
    console.info('[Service] onRun, startId:', startId)
    this.doBackgroundWork()
  }

  onDestroy(): void {
    console.info('[Service] onDestroy')
  }

  private doBackgroundWork(): void {
    // 后台任务的具体实现
    // 注意:这里不能操作 UI
  }
}

ServiceExtensionAbility 需要在 module.json5 中声明才能生效:

{
  "extensionAbilities": [
    {
      "name": "MyServiceAbility",
      "srcEntry": "./ets/serviceability/MyServiceAbility.ets",
      "type": "service"
    }
  ]
}

八、窗口管理与 Ability 的协同

Ability 生命周期和窗口状态是两个独立的维度,理解它们的对应关系是正确处理复杂场景的前提。

窗口最小化和应用切后台不是同一件事。 窗口最小化只是把窗口从屏幕上隐藏,窗口 Stage 仍然存在,onWindowStageCreateonWindowStageDestroy 都不会被触发。只有当窗口被真正关闭时,onWindowStageDestroy 才会触发。

多窗口模式下,多个窗口可能共享同一个 Ability 实例。 比如在平板上分屏使用应用,左边一个窗口、右边一个窗口,两个窗口都来自同一个 Ability。此时 onWindowStageCreate 会被调用两次,Ability 需要能够处理这种场景。

onWindowStageCreate(windowStage: Window.WindowStage): void {
  console.info(`${TAG} WindowStage created`)

  windowStage.getMainWindowSync().isMultiWindow().then(isMulti => {
    if (isMulti) {
      console.info(`${TAG} Running in multi-window mode`)
    }
  })
}

九、窗口边界场景的处理

9.1 应用被系统强制终止

这是最容易被忽视的边界场景。系统内存不足时,应用可能被直接杀死,不会有任何生命周期回调通知。 如果依赖 onBackground 来保存数据,在这种场景下会丢失数据。

正确的做法是尽早保存关键数据,而不是等到 onBackground 才保存。比如用户每编辑完一段内容就自动保存一次,而不是等到退出时再保存。

// 编辑器组件中:内容变化后延迟 2 秒自动保存
@State editorContent: string = ''

build() {
  TextArea({ text: this.editorContent })
    .onChange((value: string) => {
      this.editorContent = value
      this.debouncedSave()
    })
}

private debouncedSave = (() => {
  let timer: number | null = null
  return () => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      AppStorage.setOrCreate('draft_content', this.editorContent)
      console.info('[Editor] Draft auto-saved')
    }, 2000)
  }
})()

在这里插入图片描述

9.2 Ability 异常重启

Ability 因为某种原因崩溃后,系统会自动重启它。重启时 onCreateonWindowStageCreate 会再次被调用。此时 LaunchParam 中的 launchReason 会是 AbilityConstant.LaunchReason.SELF_RESTART,可以根据这个判断是否需要做特殊的恢复逻辑。

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  if (launchParam.launchReason === AbilityConstant.LaunchReason.SELF_RESTART) {
    console.warn(`${TAG} Ability self-restarted, attempting recovery`)
    this.recoverFromStorage()
  }
}

private recoverFromStorage(): void {
  const lastState = AppStorage.get<string>('last_app_state')
  if (lastState) {
    try {
      const parsed = JSON.parse(lastState)
      console.info(`${TAG} Recovered state:`, JSON.stringify(parsed))
    } catch (e) {
      console.error(`${TAG} Failed to recover state`)
    }
  }
}

9.3 横竖屏切换与窗口配置

屏幕旋转时,窗口尺寸发生变化,window.on('windowSizeChange') 会被触发,但 Ability 不会重启。如果应用需要在横竖屏时展示不同的布局,只需要在窗口尺寸变化时更新断点和状态即可。

// 锁定为横屏
async function setLandscape(): Promise<void> {
  const win = Window.getLastWindowSync(getContext(this))
  await win.setPreferredOrientation(Window.Orientation.LANDSCAPE)
}

// 锁定为竖屏
async function setPortrait(): Promise<void> {
  const win = Window.getLastWindowSync(getContext(this))
  await win.setPreferredOrientation(Window.Orientation.PORTRAIT)
}

// 解除方向锁定,随系统旋转
async function setAutoRotate(): Promise<void> {
  const win = Window.getLastWindowSync(getContext(this))
  await win.setPreferredOrientation(Window.Orientation.AUTO_ROTATION)
}

9.4 页面路由与 Ability 生命周期的关系

ArkUI 的页面路由(router 模块)是在同一个 Ability 内部切换页面,不会触发任何 Ability 级别的生命周期回调pushUrlreplaceUrl 都只是替换页面栈中的页面组件,Ability 本身继续运行。

只有跨 Ability 跳转才会触发 Ability 级别的生命周期,比如 startAbility 启动另一个 Ability,或者用户通过系统导航返回上一个 Ability。

import router from '@ohos.router'

// 页面内路由:不会触发 Ability 生命周期回调
router.pushUrl({ url: 'pages/Detail', params: { id: '123' } })

// 替换当前页面:也不会触发 Ability 生命周期
router.replaceUrl({ url: 'pages/Detail', params: { id: '123' } })

// 返回上一页:不会触发 Ability 生命周期
router.back()

// 启动另一个 Ability:会触发目标 Ability 的生命周期
const context = getContext(this) as common.UIAbilityContext
context.startAbility({ bundleName: 'com.example.other', abilityName: 'OtherAbility' })

十、完整生命周期状态机表

下面这张表汇总了所有场景下各回调函数的触发情况:

场景 onCreate onWindowStageCreate onForeground onBackground onWindowStageDestroy onDestroy
应用首次启动(冷启动)
应用从后台切回前台
应用从前台切到后台
应用退出(正常)
应用退出(后台被杀死)
应用异常自重启
窗口最小化
窗口最大化
多窗口模式 ✅(每窗口一次) ✅(每窗口一次)
屏幕旋转
Ability 跨应用调用

从这张表可以清晰地看出几个规律:onCreateonDestroy 只在进程启动和终止时各执行一次;onForegroundonBackground 在每次前后台切换时都会被调用;窗口操作(最小化、最大化、旋转)不会触发任何 Ability 生命周期回调。

十一、实战建议

第一条:生命周期回调里只做必要的操作。 onCreateonDestroy 各只执行一次,不要在这里放任何和页面渲染相关的逻辑。只做全局初始化和清理。

第二条:页面加载放在 onWindowStageCreate,不要放在 onCreate onCreate 执行时窗口 Stage 还不存在,无法加载页面。把页面加载逻辑放在 onWindowStageCreate 里,这是官方推荐的标准做法。

第三条:前后台切换只做状态恢复,不做页面重建。 onForeground 里刷新数据就够了,不需要重新加载页面。重新加载页面会导致界面闪烁,用户体验很差。

第四条:不要依赖 onBackground 保存关键数据。 系统可能在没有任何回调的情况下杀死进程。关键数据要在变化时就保存,而不是等到某个生命周期回调才保存。

第五条:窗口监听必须配对注册和取消。 每次 onWindowStageCreate 注册的监听,必须在 onWindowStageDestroy 中取消。这个原则是内存安全的底线,违反了就一定会出问题。

第六条:区分页面路由和 Ability 跳转。 页面栈操作(router.pushUrlrouter.back)是同一个 Ability 内部的行为,不触发任何 Ability 生命周期回调。只有 startAbility 才启动新的 Ability 实例,触发对应的生命周期。

第七条:多入口启动用 launchReason 做分支。 同一个 Ability 可能被桌面图标、快捷方式、分享面板、推送通知等不同入口触发,每个入口的初始化逻辑可能不同。根据 launchReason 做分支处理,保持入口逻辑清晰。

第八条:ServiceExtensionAbility 不要操作 UI。 后台服务没有窗口,不能访问 UI 上下文。任何需要展示结果的逻辑,都要通过回调、广播或 AppStorage 传递给 UIAbility 来处理。

第九条:Ability 之间的数据传递用 Want.parameters,不建议用全局变量。 多个 Ability 共享同一个进程,全局变量虽然可以传递数据,但容易导致状态不一致和内存泄漏。通过 Want 的 parameters 传递数据,生命周期清晰,数据流可追踪。

第十条:多窗口场景下注意状态隔离。 同一个 Ability 在多个窗口中共享实例变量,一个窗口改变的状态会影响另一个窗口。如果需要窗口间隔离,应该使用独立的 AppStorage 键名来管理各自的状态。


Logo

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

更多推荐