鸿蒙应用开发实战【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

参考资料

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


相关资源:

相关推荐
不羁的木木1 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第8篇:智能家居控制——手机控制智能灯实战
智能手机·智能家居·harmonyos
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:用能力分层重构 OpenFileRequest
typescript·harmonyos
春卷同学2 小时前
HarmonyOS掌上记账APP开发实践第21篇:Tabs 底部导航 — 鸿蒙应用的标准导航范式
华为·harmonyos
子非鱼94272 小时前
03-Flutter 鸿蒙实战 03:登录注册与本地会话持久化
网络·flutter·华为·harmonyos
子非鱼94273 小时前
01-Flutter 鸿蒙实战 01:会说话的相册项目架构拆解
flutter·华为·harmonyos
不羁的木木3 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第10篇:多设备协同——构建星闪网络实现数据同步
网络·华为·harmonyos
春卷同学3 小时前
HarmonyOS掌上记账APP开发实践第29篇:条件渲染与 ForEach — 动态 UI 的构建策略
ui·华为·harmonyos
春卷同学3 小时前
HarmonyOS掌上记账APP开发实践第22篇:Scroll + List 虚拟列表 — 长列表性能优化全攻略
性能优化·list·harmonyos
不羁的木木3 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第4篇:广播通信——一对多广播应用实现
华为·harmonyos