鸿蒙应用开发实战【06】— 应用入口与生命周期管理(EntryAbility 深度解析)

鸿蒙应用开发实战【06】--- 应用入口与生命周期管理(EntryAbility 深度解析)

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

HarmonyOS NEXT 取消了 Activity / ViewController 的概念,使用 UIAbility 作为应用的运行载体。每个 UIAbility 实例拥有独立的窗口、UI 栈和生命周期,是应用交互的基本单元。号码助手只有一个 UIAbility------EntryAbility,它承担着初始化数据库、加载首页、处理系统事件三大职责。

本篇深入讲解 EntryAbility 的完整实现,以及 UIAbility 各生命周期钩子的使用场景。

本篇涵盖:UIAbility 生命周期全图、EntryAbility 完整实现、onWindowStageCreate 窗口加载、DatabaseService 异步初始化时序、onForeground / onBackground 资源管理。


一、UIAbility 是什么

1.1 HarmonyOS 应用模型演进

版本 模型 特点
HarmonyOS 2.x FA 模型(Feature Ability) 类 Android Activity,已废弃
HarmonyOS 3.x+ Stage 模型(UIAbility) 面向多端、窗口化,当前主流
HarmonyOS NEXT Stage 模型(纯 ArkTS) 无 Android 兼容层,性能更优

Stage 模型的核心设计:UIAbility 负责生命周期管理,Page(ArkUI 页面)负责 UI 渲染,两者职责分离。

1.2 号码助手的 Ability 结构

复制代码
号码助手应用
└── EntryAbility(唯一 UIAbility)
    ├── WindowStage(窗口舞台)
    │   └── MainWindow(主窗口)
    │       └── ArkUI 页面栈
    │           ├── LoginPage(栈底)
    │           ├── HomePage
    │           ├── CardManagePage
    │           └── ...(最多 32 层)
    └── DatabaseService(后台服务,随 Ability 初始化)

1.3 UIAbility 与页面的关系

typescript 复制代码
// EntryAbility 负责:
// 1. 创建窗口(WindowStage)
// 2. 加载第一个页面
// 3. 初始化全局服务(数据库)

// 页面(Page)负责:
// 1. 渲染 UI
// 2. 响应用户交互
// 3. 通过 router 管理页面栈

二、UIAbility 生命周期全图

2.1 完整生命周期图

图1:UIAbility 完整生命周期状态机 --- Create → Foreground ↔ Background → Destroy

2.2 各钩子触发时机

钩子方法 触发时机 适合做什么
onCreate Ability 首次创建 初始化全局状态、注册系统监听
onWindowStageCreate 窗口舞台就绪 加载首页、初始化数据库
onWindowStageDestroy 窗口舞台销毁 清理窗口相关资源
onForeground 应用进入前台 恢复数据、刷新状态
onBackground 应用进入后台 暂停耗时任务、保存草稿
onDestroy Ability 销毁 释放所有资源
onNewWant 热启动(已存在实例) 处理新的启动参数
typescript 复制代码
// 生命周期调用顺序示例(冷启动)
onCreate()
  → onWindowStageCreate()
    → [用户操作应用]
  → onWindowStageDestroy()
onDestroy()

三、EntryAbility 完整实现解析

3.1 基础骨架

typescript 复制代码
// entry/src/main/ets/EntryAbility.ets
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { window } from '@kit.ArkUI'
import { DatabaseService } from './features/data/DatabaseService'

const DOMAIN = 0x0000        // hilog 域值(自定义,16进制)
const TAG = '[EntryAbility]'  // 日志 TAG,便于过滤

export default class EntryAbility extends UIAbility {
  // ...
}

3.2 onCreate --- 应用创建钩子

typescript 复制代码
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // want:启动参数(包含 action、uri、parameters 等)
  // launchParam:启动原因(NORMAL / CALL / SHARE 等)
  hilog.info(DOMAIN, TAG, 'onCreate, launchReason: %{public}d',
    launchParam.launchReason)

  // ✅ 适合:初始化全局配置、注册系统事件监听
  // ❌ 不适合:初始化数据库(context 可用,但窗口未就绪)
  // ❌ 不适合:操作 UI(窗口还没创建)
}

Want 参数说明 :当应用被其他 App 通过 startAbility 唤起时,want 携带调用方传递的数据,可用于实现应用间跳转(如分享、扫码跳转等)。

3.3 onWindowStageCreate --- 核心生命周期

typescript 复制代码
onWindowStageCreate(windowStage: window.WindowStage): void {
  hilog.info(DOMAIN, TAG, 'onWindowStageCreate')

  // ── 步骤1:异步初始化数据库(不阻塞 UI)──
  // 数据库初始化需要 context(文件路径),在此处才能获取
  DatabaseService.init(this.context).then(() => {
    hilog.info(DOMAIN, TAG, '数据库初始化成功')
  }).catch((e: Error) => {
    hilog.error(DOMAIN, TAG, '数据库初始化失败: %{public}s', JSON.stringify(e))
    // 即使数据库失败,也不崩溃------各 DAO 调用会在 catch 中处理
  })

  // ── 步骤2:加载应用首页(同步)──
  windowStage.loadContent('pages/LoginPage', (err, data) => {
    if (err.code) {
      hilog.error(DOMAIN, TAG, '加载页面失败,code: %{public}d', err.code)
      return
    }
    hilog.info(DOMAIN, TAG, '首页加载成功')
  })
}

关键设计决策

  1. 数据库初始化使用 .then().catch() 而不是 await,原因:

    • onWindowStageCreate 本身不是 async 方法
    • 不需要等待 DB 就绪才显示 UI(页面有加载状态处理)
  2. 即使数据库初始化失败,应用仍然正常启动------各页面的 DAO 调用有独立的 try/catch

3.4 onForeground 与 onBackground

typescript 复制代码
onForeground(): void {
  hilog.info(DOMAIN, TAG, 'onForeground')
  // 适用场景:
  // - 恢复后台暂停的网络请求
  // - 刷新可能已过期的数据(如云同步后的本地缓存)
  // - 重新连接蓝牙、传感器等硬件
}

onBackground(): void {
  hilog.info(DOMAIN, TAG, 'onBackground')
  // 适用场景:
  // - 暂停视频/音频播放
  // - 停止位置更新
  // - 保存用户未提交的表单草稿
  // 号码助手目前无特殊处理(本地应用,无后台任务)
}

3.5 onDestroy

typescript 复制代码
onDestroy(): void | Promise<void> {
  hilog.info(DOMAIN, TAG, 'onDestroy')
  // 适用场景:
  // - 关闭数据库连接(relationalStore 会自动处理)
  // - 取消所有订阅
  // - 释放大对象(Bitmap、Buffer 等)
  // 号码助手无需特殊处理
}

四、DatabaseService 初始化时序分析

4.1 时序图

复制代码
EntryAbility.onWindowStageCreate()
  │
  ├──[异步]── DatabaseService.init()
  │             │
  │             ├── relationalStore.getRdbStore()    ≈ 50-200ms
  │             ├── executeSql(CREATE TABLE cards)   ≈ 10-30ms
  │             ├── executeSql(CREATE TABLE ...)
  │             └── ✅ DB 就绪
  │
  └──[同步]── windowStage.loadContent('pages/LoginPage')
                │
                ├── LoginPage.aboutToAppear()         ← 此时 DB 可能未就绪
                ├── LoginPage.build()
                └── 用户点击进入首页
                      │
                      └── HomePage.onPageShow()       ← DB 大概率已就绪(≈300ms后)
                            └── CardDao.listAll()     ← 带 try/catch

4.2 为什么各页面必须有 try/catch

typescript 复制代码
// LoginPage 进入首页时
onPageShow(): void {
  this.loadData()   // 如果 DB 未就绪,会抛异常
}

private async loadData(): Promise<void> {
  try {
    this.cards = await CardDao.listAll()  // ← DB 可能还没初始化完成
  } catch (e) {
    // ✅ catch 住了:页面显示空列表,不崩溃
    console.error('[HomePage] loadData:', JSON.stringify(e))
  } finally {
    this.loading = false
  }
}

五、module.json5 中的 Ability 配置

5.1 完整配置解析

json5 复制代码
// entry/src/main/module.json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",    // 主 Ability,应用启动入口
    "deviceTypes": ["phone"],          // 支持设备:仅手机
    "deliveryWithInstall": true,
    "installationFree": false,

    "pages": "$profile:main_pages",    // 指向页面路由配置文件

    "abilities": [{
      "name": "EntryAbility",
      "srcEntry": "./ets/EntryAbility.ets",
      "description": "$string:EntryAbility_desc",
      "icon": "$media:app_icon",
      "label": "$string:EntryAbility_label",
      "startWindowIcon": "$media:app_icon",
      "startWindowBackground": "$color:start_window_background",

      // 系统 skill:使应用出现在桌面和应用列表中
      "skills": [{
        "entities": ["entity.system.home"],
        "actions": ["action.system.home"]
      }]
    }]
  }
}

5.2 main_pages.json 页面路由表

json 复制代码
// entry/src/main/resources/base/profile/main_pages.json
{
  "src": [
    "pages/LoginPage",
    "pages/HomePage",
    "pages/CardManagePage",
    "pages/AddCardPage",
    "pages/CardDetailPage",
    "pages/AddAppPage",
    "pages/AppDetailPage",
    "pages/SearchPage",
    "pages/MePage",
    "pages/PasteImportPage",
    "pages/RebindPage",
    "pages/StatusListPage",
    "pages/BackupPage",
    "pages/AboutPage",
    "pages/WizardPage"
  ]
}

注意 :页面路由表中的路径是相对于 ets/ 的字符串路径。所有在 router.pushUrl 中使用的 url 必须在此列表中注册,否则运行时报「页面不存在」错误。


六、Want 参数处理:多入口支持

6.1 从桌面图标启动(普通冷启动)

typescript 复制代码
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // launchParam.launchReason === AbilityConstant.LaunchReason.NORMAL
  // want.action === 'action.system.home'
  // 正常走 onWindowStageCreate → LoginPage
}

6.2 从通知/分享启动(含参数)

typescript 复制代码
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 处理外部传入的启动参数
  if (want.parameters) {
    const targetPage = want.parameters['targetPage'] as string
    if (targetPage) {
      // 将目标页面存入 AppStorage,供首页跳转
      AppStorage.setOrCreate('launchTargetPage', targetPage)
    }
  }
}

6.3 已在运行时热启动

typescript 复制代码
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 应用已在后台,用户再次点击图标或从通知点击进入
  // 此时 onCreate 不会再触发,onNewWant 代替
  hilog.info(DOMAIN, TAG, 'onNewWant')
  // 处理新的 want 参数,更新应用内导航
}

七、窗口管理:全屏与状态栏

7.1 沉浸式状态栏配置

typescript 复制代码
onWindowStageCreate(windowStage: window.WindowStage): void {
  // 获取主窗口
  const mainWindow = windowStage.getMainWindowSync()

  // 设置窗口全屏(内容延伸到状态栏下方)
  mainWindow.setWindowLayoutFullScreen(true).then(() => {
    // 设置状态栏文字颜色为深色(适合浅色背景)
    const sysBarProps: window.SystemBarProperties = {
      statusBarColor: '#00000000',     // 透明状态栏
      statusBarContentColor: '#14141E' // 深色文字
    }
    mainWindow.setWindowSystemBarProperties(sysBarProps)
  })

  // 加载首页
  windowStage.loadContent('pages/LoginPage')
}

7.2 获取安全区域(避开刘海/圆角)

typescript 复制代码
// 在页面中获取安全区域,避免内容被刘海遮挡
import { window } from '@kit.ArkUI'

// 使用 expandSafeArea 让组件自动适应安全区域
Column() {
  // 内容区域
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

八、EntryAbility 完整代码

typescript 复制代码
// EntryAbility.ets --- 号码助手完整入口实现
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { window } from '@kit.ArkUI'
import { DatabaseService } from './features/data/DatabaseService'

const DOMAIN = 0x0000
const TAG = '[EntryAbility]'

export default class EntryAbility extends UIAbility {
  /** 应用冷启动时调用一次 */
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'onCreate, launchReason=%{public}d',
      launchParam.launchReason)
  }

  /** 窗口舞台创建完成 --- 最重要的生命周期 */
  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, TAG, 'onWindowStageCreate')

    // 异步初始化数据库,不阻塞 UI
    DatabaseService.init(this.context)
      .then(() => hilog.info(DOMAIN, TAG, 'DB 初始化成功'))
      .catch((e: Error) => hilog.error(DOMAIN, TAG, 'DB 初始化失败: %{public}s', e.message))

    // 加载登录页(应用视觉入口)
    windowStage.loadContent('pages/LoginPage', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, TAG, 'loadContent 失败: %{public}d', err.code)
        return
      }
      hilog.info(DOMAIN, TAG, 'loadContent 成功')
    })
  }

  onWindowStageDestroy(): void {
    hilog.info(DOMAIN, TAG, 'onWindowStageDestroy')
  }

  onForeground(): void {
    hilog.info(DOMAIN, TAG, 'onForeground')
  }

  onBackground(): void {
    hilog.info(DOMAIN, TAG, 'onBackground')
  }

  onDestroy(): void {
    hilog.info(DOMAIN, TAG, 'onDestroy')
  }
}

九、常见问题与排查

9.1 数据库「尚未初始化」错误

现象:进入首页后 Toast 显示「加载失败」

原因onPageShow 触发时 DatabaseService.init() 尚未完成(通常在低端设备上 DB 初始化超过 300ms)

解决 :各 DAO 方法的 catch 块正确处理,页面显示空状态而不是崩溃:

typescript 复制代码
private async loadData(): Promise<void> {
  try {
    this.cards = await CardDao.listAll()
  } catch (e) {
    // 静默处理:DB 未就绪时显示空列表
    this.cards = []
  } finally {
    this.loading = false
  }
}

9.2 热启动时数据不刷新

现象:应用进入后台再回来,数据没有更新

解决 :使用 onForegroundonPageShow 触发刷新:

typescript 复制代码
// 页面级刷新:从子页面返回时刷新
onPageShow(): void {
  this.loadData()
}

// Ability 级刷新:从后台回到前台时刷新(可选)
// EntryAbility.onForeground → 通过 AppStorage 通知页面

十、本篇小结

知识点 核心要点
UIAbility HarmonyOS NEXT 应用的运行载体,管理生命周期和窗口
onCreate 冷启动时调用,适合初始化全局配置
onWindowStageCreate 窗口就绪,加载首页 + 初始化数据库的正确位置
onForeground/Background 前后台切换,处理资源恢复/暂停
时序注意 DB 初始化是异步的,页面 DAO 调用必须有 try/catch

参考资料

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
云端漫步19873 小时前
HarmonyOS NEXT AI 智能生活助手:AI 日程规划
人工智能·华为·生活·harmonyos
云卷云舒___________4 小时前
MiniMax H3全模态视频模型屠榜、字节跳动Seedance 2.5紧急推出、华为开源盘古2.0 Pro | 8月1日 AI日报
华为·字节跳动·minimax·ai日报·h3·seedance2.5·盘古20pro
达子6666 小时前
第25章_HarmonyOs开发图解之 电话服务
华为·harmonyos
懿路向前7 小时前
【HarmonyOS学习笔记】2026-08-05 | 端插件卡片绑定与跨上下文判断
笔记·学习·ai编程·harmonyos
程序员黑豆10 小时前
鸿蒙应用开发:V1与V2版本数据持久化实战教程
前端·harmonyos
程序员黑豆14 小时前
鸿蒙开发 Navigation 路由教程:从入门到实战
前端·harmonyos
HarmonyOS_SDK21 小时前
借助AR Engine人脸识别与跟踪能力,直播不露脸也生动
harmonyos
程序员黑豆1 天前
鸿蒙应用开发 @BuilderParam 使用教程:实现灵活的 UI 插槽
前端·harmonyos
HMS Core1 天前
基于人体骨骼点识别与跟踪,实现低时延体感游戏
游戏·华为·harmonyos
凡泰AI1 天前
APP同时覆盖了iOS、安卓和鸿蒙,如何选择混合开发架构才能减少重复建设,提高功能上线效率~
android·ios·harmonyos·mpaas·uni·小程序容器