鸿蒙五层架构:应用 / UIAbility / 窗口 / 页面 / 组件的关系与生命周期

鸿蒙五层架构:应用 / UIAbility / 窗口 / 页面 / 组件的关系与生命周期

参考:HarmonyOS应用架构关系总结

适用版本:HarmonyOS NEXT / API 12+

语言:ArkTS


一、五层架构总览

鸿蒙应用从宏观到微观分为五个层级,层层包含、逐级管理:

复制代码
┌─────────────────────────────────────────────┐
│              Application(应用)              │
│  ┌───────────────────────────────────────┐  │
│  │          UIAbility(任务单元)          │  │
│  │  ┌─────────────────────────────────┐  │  │
│  │  │      WindowStage(窗口舞台)      │  │  │
│  │  │  ┌───────────────────────────┐  │  │  │
│  │  │  │      Page(页面)@Entry    │  │  │  │
│  │  │  │  ┌─────────────────────┐  │  │  │  │
│  │  │  │  │  Component(组件)   │  │  │  │  │
│  │  │  │  │  @Component         │  │  │  │  │
│  │  │  │  └─────────────────────┘  │  │  │  │
│  │  │  └───────────────────────────┘  │  │  │
│  │  └─────────────────────────────────┘  │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘
层级 装饰器 / 类 核心职责
Application 无(系统管理) 应用进程,全局资源和存储
UIAbility extends UIAbility 系统调度单元,管理任务和窗口
WindowStage 系统创建,onWindowStageCreate 中使用 管理绘制窗口,加载根页面
Page @Entry + @Component 路由页面的根节点
Component @Component / @ComponentV2 可复用 UI 单元

二、各层的职责与连接关系

2.1 Application → UIAbility

Application 是整个应用进程的容器,一个应用可以包含多个 UIAbility。Application 层主要提供:

  • 全局状态存储AppStorageAppStorageV2PersistenceV2
  • 全局上下文getContext() 获取应用级 Context

UIAbility 是系统任务调度的基本单元,对应任务列表(最近任务)中的一个卡片。

typescript 复制代码
// 在任意组件中获取 Application 级别上下文
const appContext = getContext(this).getApplicationContext();

// AppStorageV2:跨 UIAbility、跨页面的全局状态(推荐 V2 写法)
import { AppStorageV2 } from '@kit.ArkUI';

class UserState {
  isLogin: boolean = false;
  userId: string = '';
}

// 任意位置存入
AppStorageV2.connect(UserState, () => new UserState())!.isLogin = true;

// 任意位置取出(同一进程内共享)
const userState = AppStorageV2.connect(UserState, () => new UserState())!;

使用场景选择:

场景 方案
单任务应用(微信主界面) 1 个 UIAbility + 多页面
需要系统任务列表多入口 多个 UIAbility
分屏 / 多窗口 多个 UIAbility

2.2 UIAbility → WindowStage

UIAbility 启动时,系统自动创建 WindowStage(窗口舞台),它是 UIAbility 拥有的窗口管理器,负责:

  • 管理该 UIAbility 的主窗口
  • 通过 loadContent() 加载根页面
  • 提供沉浸式、避让区等窗口能力

两者通过 onWindowStageCreate 生命周期回调连接:

typescript 复制代码
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // UIAbility 创建,此时 WindowStage 还不存在
    // 适合做:初始化全局数据、读取持久化存储
    console.info('UIAbility onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // WindowStage 创建完成,在这里加载根页面
    // 适合做:初始化窗口属性、设置沉浸式
    
    // ① 获取主窗口
    const mainWindow = windowStage.getMainWindowSync();
    
    // ② 设置沉浸式(状态栏透明)
    mainWindow.setWindowLayoutFullScreen(true);
    
    // ③ 获取安全区域高度
    const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
    const topHeight = avoidArea.topRect.height;
    AppStorage.setOrCreate('statusBarHeight', px2vp(topHeight));

    // ④ 加载根页面(连接 WindowStage 与 Page 层)
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`加载页面失败: ${err.message}`);
        return;
      }
      console.info('页面加载成功');
    });
  }

  onWindowStageDestroy(): void {
    // WindowStage 即将销毁
    // 适合做:释放窗口相关资源、保存页面状态
    console.info('WindowStage 销毁');
  }
}

2.3 WindowStage → Page

windowStage.loadContent('pages/Index')pages/Index.ets@Entry 页面)渲染到窗口中。这是 WindowStage 与 Page 层的唯一连接点

一个 UIAbility 只有一个 WindowStage,但可以通过 Router 或 Navigation 在同一窗口内切换多个页面。

typescript 复制代码
// pages/Index.ets ------ 被 WindowStage.loadContent 加载的根页面
@Entry
@ComponentV2
struct Index {
  // @Entry 标志这是一个页面根节点(可被路由管理)
  
  build() {
    Navigation(router) {
      Column() {
        Text('首页')
      }
    }
    .mode(NavigationMode.Stack)
    .hideTitleBar(true)
  }
  
  // Page 层生命周期(仅 @Entry 页面有)
  onPageShow() {
    console.info('页面出现在前台');
  }
  
  onPageHide() {
    console.info('页面退到后台');
  }
  
  onBackPress(): boolean {
    // 返回 true 自行处理,false 执行默认返回
    return false;
  }
}

2.4 Page → Component

Page 是组件树的根节点,其 build() 方法中嵌套的所有自定义组件都属于 Component 层。

typescript 复制代码
// Page 内包含多个 Component
@Entry
@ComponentV2
struct HomePage {          // ← Page 层(@Entry)
  build() {
    Column() {
      HeaderBar()          // ← Component 层
      BannerSwiper()       // ← Component 层
      ProductList()        // ← Component 层
      BottomTabBar()       // ← Component 层
    }
  }
}

// Component 示例
@ComponentV2
struct HeaderBar {         // ← Component 层(无 @Entry)
  build() {
    Row() {
      Text('首页').fontSize(20)
      Image($r('app.media.ic_search')).width(24)
    }
  }
  
  // 组件生命周期(与 Page 生命周期不同)
  aboutToAppear(): void {
    console.info('HeaderBar 即将出现,开始初始化');
  }
  
  onDidBuild(): void {
    console.info('HeaderBar 首次渲染完成');
  }
  
  aboutToDisappear(): void {
    console.info('HeaderBar 即将销毁,释放资源');
  }
}

三、UIAbility 完整生命周期

3.1 生命周期函数一览

复制代码
冷启动(首次启动):
onCreate → onWindowStageCreate → onForeground → [用户使用中] → onBackground → onWindowStageDestroy → onDestroy

热启动(从任务列表恢复):
onNewWant → onForeground

切到后台:
onBackground

启动到后台(如:被其他应用拉起但不显示界面):
onCreate → onBackground

3.2 完整代码示例

typescript 复制代码
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {

  /**
   * UIAbility 第一次被创建时调用(冷启动)
   * 时机:进程创建后
   * 用途:初始化应用级数据,读取持久化配置
   */
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info('[UIAbility] onCreate');
    // ✅ 可以做:初始化 SDK、读取本地配置
    // ❌ 不能做:UI 操作(窗口还未创建)
  }

  /**
   * UIAbility 被再次拉起时调用(热启动,不重走 onCreate)
   * 时机:应用已在后台,被重新激活
   * 用途:根据新的 want 参数更新页面或数据
   */
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info('[UIAbility] onNewWant');
    // 常见场景:从通知、小程序、分享链接跳入应用
    const targetPage = want.parameters?.['targetPage'] as string;
    if (targetPage) {
      // 通知页面跳转(配合 router 或 EventHub)
      this.context.eventHub.emit('navigateTo', { page: targetPage });
    }
  }

  /**
   * WindowStage 创建完成
   * 时机:onCreate 之后(或热启动 onNewWant 后首次显示)
   * 用途:加载根页面、设置窗口属性
   */
  onWindowStageCreate(windowStage: window.WindowStage): void {
    console.info('[UIAbility] onWindowStageCreate');

    // 初始化安全区域数据
    const mainWin = windowStage.getMainWindowSync();
    const topArea = mainWin.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
    const bottomArea = mainWin.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
    AppStorage.setOrCreate('statusBarHeight', px2vp(topArea.topRect.height));
    AppStorage.setOrCreate('bottomBarHeight', px2vp(bottomArea.bottomRect.height));

    // 加载根页面
    windowStage.loadContent('pages/Index');
  }

  /**
   * UIAbility 切到前台
   * 时机:onCreate/onNewWant 后,或从后台恢复
   * 用途:恢复动画、重连网络、刷新数据
   */
  onForeground(): void {
    console.info('[UIAbility] onForeground');
    // 恢复播放、刷新用户状态等
  }

  /**
   * UIAbility 切到后台
   * 时机:用户按 Home 键、切换应用
   * 用途:暂停动画、保存草稿、停止定位
   */
  onBackground(): void {
    console.info('[UIAbility] onBackground');
    // 暂停视频、上传草稿
  }

  /**
   * WindowStage 销毁前
   * 时机:UIAbility 即将销毁,窗口关闭前
   * 用途:释放窗口资源、保存页面状态
   */
  onWindowStageDestroy(): void {
    console.info('[UIAbility] onWindowStageDestroy');
  }

  /**
   * UIAbility 销毁
   * 时机:系统回收或用户主动关闭任务
   * 用途:释放全局资源、关闭长连接
   */
  onDestroy(): void {
    console.info('[UIAbility] onDestroy');
  }
}

3.3 典型场景时序

场景一:冷启动(首次打开应用)

复制代码
用户点击图标
    │
    ▼
onCreate()           ← 初始化数据
    │
    ▼
onWindowStageCreate() ← 创建窗口 + loadContent 加载页面
    │
    ▼
[页面 onPageShow()]   ← 页面可见
    │
    ▼
onForeground()       ← UIAbility 进入前台

场景二:按 Home 键切到后台,再回来

复制代码
用户按 Home 键
    │
    ▼
[页面 onPageHide()]
    │
    ▼
onBackground()       ← 进入后台

---用户从任务列表点击回来---

onNewWant()          ← 热启动(如有新 want 参数)
    │
    ▼
onForeground()       ← 恢复前台
    │
    ▼
[页面 onPageShow()]   ← 页面重新可见

场景三:应用被系统回收后重新打开

复制代码
系统回收进程(低内存)
    │
    ▼
onBackground() → onWindowStageDestroy() → onDestroy()

---用户再次点击图标---

重走冷启动流程:
onCreate() → onWindowStageCreate() → onForeground()

四、Page 生命周期

Page 生命周期只有 @Entry 装饰的组件才有,普通组件没有这三个回调。

typescript 复制代码
@Entry
@ComponentV2
struct ArticleListPage {
  @Local articles: Article[] = [];

  /**
   * 页面显示时触发
   * 触发时机:
   *   - 首次进入该页面
   *   - 从下级页面返回(Router.back)
   *   - UIAbility 从后台恢复到前台
   */
  onPageShow(): void {
    console.info('[Page] onPageShow');
    // 常见用途:
    // 1. 检查登录状态
    // 2. 刷新需要实时更新的数据(如购物车数量)
    this.checkLoginAndRefresh();
  }

  /**
   * 页面隐藏时触发
   * 触发时机:
   *   - 跳转到下级页面(pushUrl / pushPathByName)
   *   - UIAbility 切到后台
   */
  onPageHide(): void {
    console.info('[Page] onPageHide');
    // 常见用途:暂停视频、停止轮播
  }

  /**
   * 用户点击返回键时触发
   * 返回 true:自行处理,不执行默认返回
   * 返回 false/undefined:执行默认返回行为
   */
  onBackPress(): boolean {
    console.info('[Page] onBackPress');
    // 场景:表单有未保存内容,弹对话框二次确认
    if (this.hasUnsavedContent) {
      this.showSaveDialog();
      return true;   // 阻止返回
    }
    return false;    // 允许返回
  }
  
  private hasUnsavedContent: boolean = false;
  
  private checkLoginAndRefresh(): void { /* ... */ }
  private showSaveDialog(): void { /* ... */ }
  
  build() { /* ... */ }
}

Navigation NavDestination 的页面级生命周期(更精细)

使用 Navigation 替代 Router 时,页面是 NavDestination,生命周期更丰富:

typescript 复制代码
@ComponentV2
struct OrderDetailPage {
  @Param param: OrderParam = { orderId: '' };
  @Consumer() stack: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Column() {
        Text(`订单 ${this.param.orderId}`)
      }
    }
    /**
     * 页面创建完成,可获取 NavDestinationContext
     * 对应 Router 的 aboutToAppear + 获取参数
     */
    .onReady((ctx: NavDestinationContext) => {
      console.info('[NavDest] onReady - 页面就绪');
      const param = ctx.pathInfo.param as OrderParam;
      const stack = ctx.pathStack;
    })
    /**
     * 页面出现在屏幕前台(包括从子页面返回)
     * 对应 Router 的 onPageShow
     */
    .onShown(() => {
      console.info('[NavDest] onShown - 页面可见');
      // 刷新数据
    })
    /**
     * 页面被子页面遮挡(跳转到下一页时)
     * 对应 Router 的 onPageHide
     */
    .onHidden(() => {
      console.info('[NavDest] onHidden - 页面不可见');
      // 暂停刷新
    })
    /**
     * 页面即将出现(动画开始前)
     */
    .onWillAppear(() => {
      console.info('[NavDest] onWillAppear');
    })
    /**
     * 页面即将消失(动画开始前)
     */
    .onWillDisappear(() => {
      console.info('[NavDest] onWillDisappear');
    })
    /**
     * 拦截返回键
     * 返回 true:自行处理;false:执行默认返回
     */
    .onBackPressed(() => {
      console.info('[NavDest] onBackPressed');
      return false;
    })
  }
}

五、Component 生命周期

组件生命周期是最细粒度的,每个 @Component / @ComponentV2 都有。

typescript 复制代码
@ComponentV2
struct ProductCard {
  @Param productId: string = '';
  @Local productInfo: ProductInfo | null = null;
  private timer: number = -1;

  /**
   * 组件实例创建后、build() 执行前触发
   * 用途:
   *   - 网络请求初始数据
   *   - 订阅事件
   *   - 启动定时器
   * 注意:此时 UI 还未渲染,不能操作 DOM
   */
  aboutToAppear(): void {
    console.info('[Component] aboutToAppear');
    this.loadProduct();
    this.timer = setInterval(() => {
      this.refreshPrice();
    }, 5000);
  }

  /**
   * 首次 build() 完成后触发(重渲染不触发)
   * 用途:需要在 UI 渲染后才能执行的操作
   *   - 获取组件尺寸/位置(通过 @LocalBuilder 引用)
   *   - 播放入场动画
   */
  onDidBuild(): void {
    console.info('[Component] onDidBuild - 首次渲染完成');
    // 入场动画(只在首次渲染后执行)
    animateTo({ duration: 300 }, () => {
      this.opacity = 1;
    });
  }

  /**
   * 组件销毁前触发
   * 用途:
   *   - 取消订阅事件
   *   - 清除定时器
   *   - 释放资源
   * 警告:不能修改状态变量(特别是 @Link),会导致不稳定
   */
  aboutToDisappear(): void {
    console.info('[Component] aboutToDisappear');
    // ✅ 可以:清理副作用
    clearInterval(this.timer);
    emitter.off(1001);
    // ❌ 不能:this.productInfo = null; (修改状态变量)
  }

  @Local opacity: number = 0;
  
  private loadProduct(): void { /* ... */ }
  private refreshPrice(): void { /* ... */ }
  
  build() {
    Column() {
      Text(this.productInfo?.name ?? '加载中...')
    }
    .opacity(this.opacity)
  }
}

六、五层生命周期完整时序图

以下是从冷启动到页面渲染完成的完整调用顺序:

复制代码
应用启动(冷启动)
│
├─ [UIAbility] onCreate()
│       ↓
├─ [UIAbility] onWindowStageCreate()
│       │
│       ├─ windowStage.loadContent('pages/Index')
│       │       ↓
│       │   [Component - Index] aboutToAppear()      ← 根组件初始化
│       │       ↓
│       │   [Component - Index] build()              ← 首次渲染
│       │       ↓
│       │   [Page - Index] onPageShow()              ← 页面可见
│       │       ↓
│       │   [Component - Index] onDidBuild()         ← 渲染完成
│       │       ↓
│       │   [子组件] aboutToAppear() → build() → onDidBuild()
│       │
├─ [UIAbility] onForeground()                        ← 进入前台
│
│
跳转到子页面(router.pushUrl 或 stack.pushPathByName)
│
├─ [Page - Index] onPageHide()                       ← 当前页面隐藏
│       ↓
├─ [Component - DetailPage] aboutToAppear()
│       ↓
├─ [Component - DetailPage] build()
│       ↓
├─ [Page - DetailPage] onPageShow()
│       ↓
├─ [Component - DetailPage] onDidBuild()
│
│
从子页面返回(router.back 或 stack.pop)
│
├─ [Page - DetailPage] onPageHide()
├─ [Component - DetailPage] aboutToDisappear()       ← 子组件销毁
│       ↓
├─ [Page - Index] onPageShow()                       ← 上一页重新可见
│
│
按 Home 键切到后台
│
├─ [Page - Index] onPageHide()
│       ↓
├─ [UIAbility] onBackground()
│
│
从后台恢复
│
├─ [UIAbility] onNewWant()(如有新参数)
├─ [UIAbility] onForeground()
│       ↓
├─ [Page - Index] onPageShow()
│
│
应用被系统回收
│
├─ [UIAbility] onBackground()
├─ [UIAbility] onWindowStageDestroy()
├─ [Component - *] aboutToDisappear()                ← 所有组件销毁
├─ [UIAbility] onDestroy()

七、各层之间的"胶水":上下文与共享数据

各层之间并非孤立,通过以下机制互相连接:

7.1 UIAbility ↔ Page:EventHub

typescript 复制代码
// UIAbility 中发布事件(通知页面刷新)
this.context.eventHub.emit('tokenExpired');

// 页面中订阅(收到后跳转登录)
const ctx = getContext(this) as common.UIAbilityContext;
ctx.eventHub.on('tokenExpired', () => {
  router.replaceUrl({ url: 'pages/LoginPage' });
});

7.2 Page ↔ Component:@Provider / @Consumer

typescript 复制代码
@Entry
@ComponentV2
struct HomePage {
  @Provider() currentTab: string = 'recommend'; // Page 提供状态
  
  build() {
    Column() {
      TabBar()    // 子组件通过 @Consumer 接收并修改
      ContentArea()
    }
  }
}

@ComponentV2
struct TabBar {
  @Consumer() currentTab: string = '';  // 直接接收 Page 的状态
  
  build() {
    Row() {
      Text('推荐').onClick(() => { this.currentTab = 'recommend'; })
      Text('热卖').onClick(() => { this.currentTab = 'hot'; })
    }
  }
}

7.3 全局共享:AppStorageV2

typescript 复制代码
// 在 UIAbility.onCreate 中初始化
AppStorageV2.connect(UserState, () => new UserState());

// 在任意 Component 中读写
@ComponentV2
struct Avatar {
  @Local userState: UserState = AppStorageV2.connect(UserState, () => new UserState())!;
  
  build() {
    Image(this.userState.avatar).width(40).height(40).borderRadius(20)
  }
}

7.4 导航层传参:NavPathStack @Provider

typescript 复制代码
@Entry
@ComponentV2
struct Index {
  @Provider() stack: NavPathStack = new NavPathStack(); // Index 提供路由栈
  
  build() {
    Navigation(this.stack) { /* ... */ }
  }
}

@ComponentV2
struct AnyDeepComponent {
  @Consumer() stack: NavPathStack = new NavPathStack(); // 任意子组件消费
  
  build() {
    Button('跳转').onClick(() => {
      this.stack.pushPathByName('DetailPage', {});
    })
  }
}

八、常见问题与陷阱

❌ 陷阱 1:在 aboutToDisappear 中修改状态变量

typescript 复制代码
aboutToDisappear(): void {
  this.data = []; // ❌ 危险!可能导致 UI 异常或崩溃
  clearInterval(this.timer); // ✅ 清理副作用没问题
}

❌ 陷阱 2:在 onCreate 中操作 UI

typescript 复制代码
onCreate(): void {
  router.pushUrl({ url: 'pages/Login' }); // ❌ 窗口未创建,路由无效
}

// ✅ 正确:在 onWindowStageCreate 或 onForeground 中操作
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', () => {
    // 加载完成后才能操作路由
  });
}

❌ 陷阱 3:混淆 onPageShow 和 aboutToAppear 的触发时机

typescript 复制代码
// aboutToAppear:只在组件首次创建时触发一次
// onPageShow:每次页面出现都触发(包括从子页面返回)

// 一次性初始化 → 放在 aboutToAppear
aboutToAppear(): void {
  this.initData(); // 只执行一次
}

// 需要每次进入都刷新 → 放在 onPageShow
onPageShow(): void {
  this.refreshCartCount(); // 每次进入都刷新购物车数量
}

❌ 陷阱 4:UIAbility 销毁后 EventHub 订阅丢失

typescript 复制代码
// 订阅放在 aboutToAppear,确保页面每次创建都会重新订阅
aboutToAppear(): void {
  const ctx = getContext(this) as common.UIAbilityContext;
  ctx.eventHub.on('refresh', this.onRefresh);
}

aboutToDisappear(): void {
  const ctx = getContext(this) as common.UIAbilityContext;
  ctx.eventHub.off('refresh', this.onRefresh); // ✅ 配对取消
}

九、总结

复制代码
[Application]
    全局存储(AppStorageV2)、应用级 Context
    │
[UIAbility]  ── 生命周期:onCreate → onWindowStageCreate → onForeground
    系统任务单元,通过 EventHub 与页面通信
    │
[WindowStage]  ── 通过 loadContent() 连接到 Page
    窗口管理,沉浸式设置,安全区域获取
    │
[Page / NavDestination]  ── 生命周期:onPageShow / onShown
    路由节点,通过 @Provider 向子组件提供状态
    │
[Component]  ── 生命周期:aboutToAppear → onDidBuild → aboutToDisappear
    UI 单元,通过 @Consumer 消费状态,通过 emitter 通信

每层各司其职,通过 EventHub、@Provider/@Consumer、AppStorageV2、NavPathStack 有机连接,共同构成完整的鸿蒙应用架构。