鸿蒙 Stage 模型与 Ability 生命周期:完整图解与实战

一、为什么 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 完全销毁前执行
用于释放所有资源、取消所有监听
理解这张图的关键是区分两条独立的触发线:onForeground 和 onBackground 反映的是应用是否在前台,由系统根据应用可见性自动调度;onWindowStageCreate 和 onWindowStageDestroy 反映的是窗口是否存活,通常由用户关闭应用或切换多窗口模式触发。
四、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 会在自己的 onCreate → onWindowStageCreate → onForeground 流程中完成初始化。
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 简单得多,只有 onCreate → onRun → onDestroy 三个阶段:
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 仍然存在,onWindowStageCreate 和 onWindowStageDestroy 都不会被触发。只有当窗口被真正关闭时,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 因为某种原因崩溃后,系统会自动重启它。重启时 onCreate 和 onWindowStageCreate 会再次被调用。此时 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 级别的生命周期回调。pushUrl 和 replaceUrl 都只是替换页面栈中的页面组件,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 跨应用调用 | ✅ | ✅ | ✅ |
从这张表可以清晰地看出几个规律:onCreate 和 onDestroy 只在进程启动和终止时各执行一次;onForeground 和 onBackground 在每次前后台切换时都会被调用;窗口操作(最小化、最大化、旋转)不会触发任何 Ability 生命周期回调。
十一、实战建议
第一条:生命周期回调里只做必要的操作。 onCreate 和 onDestroy 各只执行一次,不要在这里放任何和页面渲染相关的逻辑。只做全局初始化和清理。
第二条:页面加载放在 onWindowStageCreate,不要放在 onCreate。 onCreate 执行时窗口 Stage 还不存在,无法加载页面。把页面加载逻辑放在 onWindowStageCreate 里,这是官方推荐的标准做法。
第三条:前后台切换只做状态恢复,不做页面重建。 onForeground 里刷新数据就够了,不需要重新加载页面。重新加载页面会导致界面闪烁,用户体验很差。
第四条:不要依赖 onBackground 保存关键数据。 系统可能在没有任何回调的情况下杀死进程。关键数据要在变化时就保存,而不是等到某个生命周期回调才保存。
第五条:窗口监听必须配对注册和取消。 每次 onWindowStageCreate 注册的监听,必须在 onWindowStageDestroy 中取消。这个原则是内存安全的底线,违反了就一定会出问题。
第六条:区分页面路由和 Ability 跳转。 页面栈操作(router.pushUrl、router.back)是同一个 Ability 内部的行为,不触发任何 Ability 生命周期回调。只有 startAbility 才启动新的 Ability 实例,触发对应的生命周期。
第七条:多入口启动用 launchReason 做分支。 同一个 Ability 可能被桌面图标、快捷方式、分享面板、推送通知等不同入口触发,每个入口的初始化逻辑可能不同。根据 launchReason 做分支处理,保持入口逻辑清晰。
第八条:ServiceExtensionAbility 不要操作 UI。 后台服务没有窗口,不能访问 UI 上下文。任何需要展示结果的逻辑,都要通过回调、广播或 AppStorage 传递给 UIAbility 来处理。
第九条:Ability 之间的数据传递用 Want.parameters,不建议用全局变量。 多个 Ability 共享同一个进程,全局变量虽然可以传递数据,但容易导致状态不一致和内存泄漏。通过 Want 的 parameters 传递数据,生命周期清晰,数据流可追踪。
第十条:多窗口场景下注意状态隔离。 同一个 Ability 在多个窗口中共享实例变量,一个窗口改变的状态会影响另一个窗口。如果需要窗口间隔离,应该使用独立的 AppStorage 键名来管理各自的状态。
更多推荐




所有评论(0)