【HarmonyOS应用开发入门】 第二期:Stage模型与应用架构解析

一、Stage模型核心概念

1、设计理念

Stage模型通过进程隔离设计和组件生命周期解耦,实现了:

  • 更好的性能与安全性:UI组件与后台任务分离
  • 更清晰的应用架构:明确的应用组件边界
  • 更便捷的数据共享:跨组件数据共享机制

2、核心概念

1) 应用组件

  • UIAbility:应用的入口和UI载体,代表一个独立的功能单元

  • ExtensionAbility:特定场景的扩展能力(如服务卡片、输入法)

  • WindowStage:UIAbility的窗口舞台,管理应用窗口

2)数据管理

  • AppStorage:应用级状态管理

  • Environment:环境状态管理

  • PersistentStorage:持久化存储

3)架构特点

javascript 复制代码
       ┌────────────────────────────────┐
       │          UIAbility组件          │
       │  ┌─────────┐  ┌─────────────┐  │
       │  │  窗口    │ │  上下文       │  │
       │  │ Window  │  │  Context    │  │
       │  └─────────┘  └─────────────┘  │
       └────────────────────────────────┘
                │                │
                ▼                ▼
       ┌─────────────────┐  ┌─────────────────┐
       │  UI界面(ArkTS)  │    数据/状态管理    │
       │  Pages/Components│ │  AppStorage等   │
       └─────────────────┘  └─────────────────┘

二、UIAbility生命周期详解

UIAbility组件的核心生命周期回调包括onCreate、onForeground、onBackground、onDestroy。作为一种包含UI的应用组件,UIAbility的生命周期不可避免地与WindowStage的生命周期存在关联关系。

UI Ability生命周期示意图:

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

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  // 应用创建时调用
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    try {
      this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
    }
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
    // 初始化应用资源
  }

  // 应用销毁
  onDestroy(): void {
    // 最终清理工作
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  // 窗口舞台创建时调用
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    // 设置UI加载内容
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }
  onWindowStageWillDestroy(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageWillDestroy');
  }
  
  // 窗口舞台销毁
  onWindowStageDestroy(): void {
    // 清理窗口相关资源
    // Main window is destroyed, release UI related resources
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  // 应用从后台回到前台
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onNewWant');
  }

  onWillForeground(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWillForeground');
  }

  // 应用从后台回到前台
  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onWillBackground(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWillBackground');
  }

  // 应用从前台进入后台
  onBackground(): void {
    // Ability has back to background
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

1、运行程序查看对应的日志

当用户启动一个UIAbility时,系统会依次触发onCreate()、onWindowStageCreate()、onForeground()生命周期回调。

bash 复制代码
Ability onCreate
Ability onWindowStageCreate
Ability onWillForeground
Ability onForeground
Succeeded in loading the content.

切到后台:

bash 复制代码
Ability onWillBackground
Ability onBackground

切回前台:

bash 复制代码
Ability onNewWant
Ability onWillForeground
Ability onForeground

杀掉app:

bash 复制代码
Ability onWillBackground
Ability onBackground
Ability onWindowStageWillDestroy
Ability onWindowStageDestroy
Ability onDestroy

2、生命周期回调

  • onCreate():在首次创建UIAbility实例时,系统触发onCreate()回调。开发者可以在该回调中执行UIAbility整个生命周期中仅发生一次的启动逻辑。
  • onWindowStageCreate():UIAbility实例创建完成之后,在进入前台之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,开发者可以在该回调中进行UI加载、WindowStage的事件订阅。

在onWindowStageCreate()回调中通过loadContent()方法设置应用要加载的页面,并根据需要调用on('windowStageEvent')方法订阅WindowStage的事件(获焦/失焦、切到前台/切到后台、前台可交互/前台不可交互)。

javascript 复制代码
  // 窗口舞台创建时调用
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 设置WindowStage的事件订阅(获焦/失焦、切到前台/切到后台、前台可交互/前台不可交互)
    try {
      windowStage.on('windowStageEvent', (data) => {
        let stageEventType: window.WindowStageEventType = data;
        switch (stageEventType) {
          case window.WindowStageEventType.SHOWN: // 切到前台
            hilog.info(DOMAIN, 'testTag', `windowStage foreground.`);
            break;
          case window.WindowStageEventType.ACTIVE: // 获焦状态
            hilog.info(DOMAIN, 'testTag', `windowStage active.`);
            break;
          case window.WindowStageEventType.INACTIVE: // 失焦状态
            hilog.info(DOMAIN, 'testTag', `windowStage inactive.`);
            break;
          case window.WindowStageEventType.HIDDEN: // 切到后台
            hilog.info(DOMAIN, 'testTag', `windowStage background.`);
            break;
          case window.WindowStageEventType.RESUMED: // 前台可交互状态
            hilog.info(DOMAIN, 'testTag', `windowStage resumed.`);
            break;
          case window.WindowStageEventType.PAUSED: // 前台不可交互状态
            hilog.info(DOMAIN, 'testTag', `windowStage paused.`);
            break;
          default:
            break;
        }
      });
    } catch (exception) {
      hilog.error(DOMAIN, 'testTag',
        `Failed to enable the listener for window stage event changes. Cause: ${JSON.stringify(exception)}`);
    }

    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    // 设置UI加载内容
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }
  • onForeground() : 在UIAbility切换至前台时且UIAbility的UI可见之前,系统触发onForeground回调。开发者可以在该回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。

    例如,应用已获得地理位置权限。在UI显示之前,开发者可以在onForeground()回调中开启定位功能,从而获取到当前的位置信息。

  • onBackground() :在UIAbility的UI完全不可见之后,系统触发onBackground回调,将UIAbility实例切换至后台状态。开发者可以在该回调中释放UI不可见时的无用资源,例如停止定位功能,以节省系统的资源消耗。不能做耗时操作

  • onWindowStageWillDestroy(): 在UIAbility实例销毁之前,系统触发onWindowStageWillDestroy()回调。该回调在WindowStage销毁前执行,此时WindowStage可以使用。开发者可以在该回调中释放通过WindowStage获取的资源、注销WindowStage事件订阅等。

javascript 复制代码
// 释放通过windowStage对象获取的资源
  onWindowStageWillDestroy(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageWillDestroy');
    try {
      // 在onWindowStageWillDestroy()中注销WindowStage事件订阅(获焦/失焦、切到前台/切到后台、前台可交互/前台不可交互)
      windowStage.off('windowStageEvent');
      hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageWillDestroy,off windowStageEvent');
    } catch (error) {
      hilog.error(DOMAIN, 'testTag',
        `Failed to disable the listener for windowStageEvent. error is ${JSON.stringify(error)}`);
    }
  }
  • onWindowStageDestroy():在UIAbility实例销毁之前,系统触发onWindowStageDestroy()回调,开发者可以在该回调中释放UI资源。该回调在WindowStage销毁后执行,此时WindowStage不可以使用。
javascript 复制代码
onWindowStageCreate(windowStage: window.WindowStage): void {
    // 加载UI资源
  }

  onWindowStageDestroy() {
    // 释放UI资源
  }
  • onDestroy(): 在UIAbility实例销毁之前,系统触发onDestroy回调。该回调是UIAbility接收到的最后一个生命周期回调,开发者可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
  • onNewWant():当应用的UIAbility实例已创建,再次调用方法启动该UIAbility实例时,系统触发该UIAbility的onNewWant()回调。开发者可以在该回调中更新要加载的资源和数据等,用于后续的UI展示。

三、应用配置文件深度解析

1、app.json5全局配置

javascript 复制代码
{
  "app": {
    "bundleName": "com.zmf.tools",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}
  • bundleName : 同Android的包名
  • vendor:标识对应用开发厂商的描述,取值为长度不超过255字节的字符串。该标签可用于展示开发厂商信息,如在应用的关于页面,取该标签展示开发厂商信息。
  • versionCode:版本号,正整数
  • versionName:向用户展示的应用版本号
  • icon:应用的图标
  • label:应用的名称

2、module.json5模块配置

javascript 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "ohos.want.action.home"
            ]
          }
        ]
      }
    ],
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false,
        "metadata": [
          {
            "name": "ohos.extension.backup",
            "resource": "$profile:backup_config"
          }
        ],
      }
    ]
  }
}
  • module字段:模块基本信息
  • abilities字段:标识当前Module中UIAbility的配置信息,只对当前UIAbility生效。
  • requestPermissions字段:标识当前应用运行时需向系统申请的权限集合。

四、权限系统详解

1、权限分类

权限类型 授权方式 操作路径
开放权限(系统授权) system_grant 声明权限 > 访问接口
开放权限(用户授权) user_grant 声明权限 > 向用户申请授权 > 访问接口
受限开放权限 system_grant 申请使用受限权限 > 声明权限 > 访问接口
受限开放权限 user_grant 申请使用受限权限 > 声明权限 > 向用户申请授权 > 访问接口

2、常用权限示例

应用必须在module.json5配置文件的requestPermissions标签中声明权限。

bash 复制代码
"requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO"
      },
      {
        "name": "ohos.permission.GET_WIFI_INFO"
      },
      {
        "name": "ohos.permission.PUBLISH_AGENT_REMINDER"
      },
      {
        "name": "ohos.permission.VIBRATE",
      },
      {
        "name": 'ohos.permission.MICROPHONE',
        "reason": '$string:ohos_permission_MICROPHONE',
        "usedScene": {
          "when": "always"
        }
      }
    ],

当应用需要访问用户的隐私信息或使用系统能力时,如获取位置信息、访问日历、使用相机拍摄照片或录音等,应向用户申请授权。这些权限属于user_grant权限。

当应用申请user_grant权限时,需完成以下步骤:

  • 在配置文件中,声明应用需要请求的权限。

  • 将应用中需要申请权限的目标对象与对应权限关联,确保用户明确哪些操作需授予应用指定权限。

  • 运行应用时,用户触发访问目标对象时应调用接口,精准触发动态授权弹框。接口内部会检查当前用户是否已授权所需权限,若未授权,将拉起动态授权弹框请求用户授权。

  • 检查用户的授权结果,确认用户已授权后,再进行下一步操作。

3、动态权限申请流程

javascript 复制代码
  permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];

  // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
  reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
    atManager.requestPermissionsFromUser(context, permissions).then(async (data) => {
      let grantStatus: Array<number> = data.authResults;
      let length: number = grantStatus.length;
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] === 0) {
          // 用户授权,可以继续访问目标操作
          await this.startRecordingProcess()
        } else {
          return;
        }
      }
      // 授权成功
    }).catch((err: BusinessError) => {
      console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
    })
  }

五、HAP、HAR、HSP概念辨析

1、HAP(HarmonyOS Ability Package):可独立部署的运行包

1)基本概念

HAP(Harmony Ability Package)是应用安装和运行的基本单元

HAP包是由代码、资源、第三方库、配置文件等打包生成的模块包,其主要分为两种类型:entry和feature。

  • entry:应用的主模块,作为应用的入口,提供了应用的基础功能
  • feature:应用的动态特性模块,作为应用能力的扩展,可以根据用户的需求和设备类型进行选择性安装。

2)使用场景

  • 单HAP场景:如果只包含UIAbility组件,无需使用ExtensionAbility组件,优先采用单HAP(即一个entry包)来实现应用开发。推荐采用"一个UIAbility+多个页面"的方式。

  • 多HAP场景:如果应用的功能比较复杂,需要使用ExtensionAbility组件,可以采用多HAP(即一个entry包+多个feature包)来实现应用开发,每个HAP中包含一个UIAbility组件或者一个ExtensionAbility组件。在这种场景下,多个HAP引用相同的库文件,可能导致重复打包的问题。

3)创建

  • 创建工程,构建第一个ArkTS应用。

  • 在工程目录上单击右键,选择New > Module。

  • 在弹出的对话框中选择Empty Ability模板,单击Next。

  • 在Module配置界面,配置Module name,选择Module Type和Device Type,然后单击Next。

  • 在Ability配置界面,配置Ability name,然后单击Finish完成创建。



2、HAR(HarmonyOS Archive):静态共享包,编译时复制

1)基本概念

  • HAR(Harmony Archive)是静态共享包,可以包含代码、C++库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。

2)使用场景

  • 支持应用内共享,也可以作为二方库(SDK)、三方库(SDK)发布后供其他应用使用。
  • 作为二方库(SDK),发布到OHPM私仓,供公司内部其他应用使用。
  • 作为三方库(SDK),发布到OHPM中心仓,供其他应用使用。

3)创建

  • 鼠标移到工程目录顶部,单击右键,选择New > Module,在工程中添加模块。
  • 在Choose Your Ability Template界面中,选择Static Library,并单击Next。


    代码结构如下:

3、HSP(HarmonyOS Shared Package):动态共享包,运行时共享

1)基本概念

  • DevEco Studio支持开发动态共享包HSP(Harmony Shared Package)
  • 在应用/元服务开发过程中部分功能按需动态下载,或开发元服务场景时需要分包加载,可使用HSP实现相应功能。
  • 当有多个安装包需要资源共享时,也可利用HSP减少公共资源和代码重复打包。

2)创建

  • 方法1:鼠标移到工程目录顶部,单击鼠标右键,选择New > Module,开始创建新的Module。
  • 方法2:选中工程目录中任意文件,然后在菜单栏选择File > New > Module,开始创建新的Module。
  • 模板类型选择Shared Library,点击Next。


    整个项目结构如下:
相关推荐
养猪喝咖啡3 小时前
ArkTS 文本输入组件(TextInput)详解
harmonyos
养猪喝咖啡3 小时前
HarmonyOS ArkTS 页面导航(Navigation)全面介绍
harmonyos
养猪喝咖啡3 小时前
HarmonyOS ArkTS 从 Router 到 Navigation 的迁移指南
harmonyos
养猪喝咖啡3 小时前
HarmonyOS ArkTS Stack 实战:做一个“悬浮按钮 + 遮罩弹层 + 底部菜单”的完整小项目
harmonyos
Archilect3 小时前
从几何到路径:ArkUI 下的双层容器、缩放偏移与抛掷曲线设计
harmonyos
养猪喝咖啡3 小时前
HarmonyOS ArkTS 创建网格 Grid/GridItem:写得顺、适配稳、滚动不卡的那套方法
harmonyos
子榆.4 小时前
Flutter 与开源鸿蒙(OpenHarmony)性能调优实战:从启动速度到帧率优化的全链路指南
flutter·开源·harmonyos
子榆.4 小时前
Flutter 与开源鸿蒙(OpenHarmony)安全加固实战:防逆向、防调试、数据加密全攻略
flutter·开源·harmonyos
低调电报5 小时前
我的第一个开源项目:鸿蒙分布式“口袋健身”教练
分布式·开源·harmonyos