HarmonyOS
NEXT API文档
文档中心-HarmonyOS NEXT开发文档-华为开发者联盟
HarmonyOS Design
HarmonyOS 主题图标库 | icon素材免费下载 | 华为开发者联盟
环境
工具下载
npm 配置
警告
强烈建议直接使用 ohpm 进行第三方库安装及配置。
npm config set @ohos:registry=https://repo.harmonyos.com/npm/
也可手动修改 C:\Users\<用户名>\.npmrc
+ @ohos:registry=https://repo.harmonyos.com/npm/
ohpm 配置
配置文件在 C:\Users\<用户名>\.ohpm\.ohpmrc
# 如果已经安装了 `DevEco Studio` 那这个配置是本身就在的
+ registry=https://ohpm.openharmony.cn/ohpm/
ohpmrc-ohpm-Command Line Tools | 华为开发者联盟
resolve_conflict
ohpm客户端在1.5.0版本开始支持依赖版本冲突自动解决功能。只需要在.ohpmrc文件中,将resolve_conflict配置为true或缺省,即可开启该功能。依赖冲突的处理策略为:当您的项目同时依赖了某个三方库的不同版本时,ohpm将选择其中的最高版本进行安装。
命令行hap签名
搭建流水线-Command Line Tools - 华为HarmonyOS开发者
插件
AI智能辅助编程工具-DevEco Studio | 华为开发者联盟
应用权限管控概述
权限列表
声明权限
{
"module": {
// ···
// 1.ohos.permission.APPROXIMATELY_LOCATION与ohos.permission.LOCATION为user_grant权限,reason和usedScene为必填字段。
// 2.ohos.permission.USE_BLUETOOTH为system_grant权限,reason和usedScene为选填字段。
"requestPermissions": [
{
// 权限名称
"name": "ohos.permission.APPROXIMATELY_LOCATION",
// 申请原因
"reason": "$string:approximately_location_permission_reason",
"usedScene": {
// 使用权限的Ability
"abilities": [
"FormAbility"
],
// 调用时机:使用时弹框
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:location_permission_reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.USE_BLUETOOTH"
}
]
}
}
向用户申请授权
校验当前是否已经授权
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED; // 获取应用程序的accessTokenID。 let tokenId: number = 0; try { let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo; tokenId = appInfo.accessTokenId; } catch (error) { const err: BusinessError = error as BusinessError; console.error(`Failed to get bundle info for self, code: ${err.code}, message: ${err.message}`); } // 校验应用是否被授予权限。 try { grantStatus = await atManager.checkAccessToken(tokenId, permission); } catch (error) { const err: BusinessError = error as BusinessError; console.error(`Failed to check access token, code: ${err.code}, message: ${err.message}`); } return grantStatus; } async function checkPermissions(): Promise<void> { let grantStatus1: boolean = await checkPermissionGrant('ohos.permission.LOCATION') === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取精确定位权限状态。 let grantStatus2: boolean = await checkPermissionGrant('ohos.permission.APPROXIMATELY_LOCATION') === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取模糊定位权限状态。 // 精确定位权限只能跟模糊定位权限一起申请,或者已经有模糊定位权限才能申请精确定位权限。 if (grantStatus2 && !grantStatus1) { // 申请精确定位权限。 } else if (!grantStatus1 && !grantStatus2) { // 申请模糊定位权限与精确定位权限或单独申请模糊定位权限。 } else { // 已经授权,可以继续访问目标操作。 } }动态向用户申请权限
重要
应用在
UIAbility#onWindowStageCreate()中申请授权时,需要等待异步接口loadContent()/setUIContent()执行结束后或在loadContent()/setUIContent()回调中调用requestPermissionsFromUser(),否则在Content加载完成前,requestPermissionsFromUser会调用失败。应用在
UIExtensionAbility申请授权时,需要在onWindowStageCreate函数执行结束后或在onWindowStageCreate函数回调中调用requestPermissionsFromUser(),否则在ability加载完成前,requestPermissionsFromUser会调用失败。
在UIAbility中向用户申请授权
// 使用UIExtensionAbility:将import { UIAbility } from '@kit.AbilityKit' 替换为import { UIExtensionAbility } from '@kit.AbilityKit'; import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; const permissions: Array<Permissions> = ['ohos.permission.LOCATION','ohos.permission.APPROXIMATELY_LOCATION']; // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext。 function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗。 atManager.requestPermissionsFromUser(context, permissions).then((data) => { let grantStatus: Array<number> = data.authResults; let length: number = grantStatus.length; for (let i = 0; i < length; i++) { if (grantStatus[i] === 0) { // 用户授权,可以继续访问目标操作。 } else { // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。 return; } } // 授权成功。 }).catch((err: BusinessError) => { console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); }) } // 使用UIExtensionAbility:将 UIAbility 替换为UIExtensionAbility export default class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { // ... windowStage.loadContent('pages/Index', (err, data) => { reqPermissionsFromUser(permissions, this.context); // ... }); } // ... }在UI中向用户申请授权
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; const permissions: Array<Permissions> = ['ohos.permission.LOCATION','ohos.permission.APPROXIMATELY_LOCATION']; // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void { let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗。 atManager.requestPermissionsFromUser(context, permissions).then((data) => { let grantStatus: Array<number> = data.authResults; let length: number = grantStatus.length; for (let i = 0; i < length; i++) { if (grantStatus[i] === 0) { // 用户授权,可以继续访问目标操作。 } else { // 当用户拒绝授权时,系统应提示用户必须授予相应权限才能使用当前页面的功能,并指导用户前往系统设置开启所需权限。 return; } } // 授权成功 }).catch((err: BusinessError) => { console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); }) } @Entry @Component struct Index { aboutToAppear() { // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext const context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; reqPermissionsFromUser(permissions, context); } build() { // ... } }
重要
- 每次执行需要目标权限的操作时,应用都必须检查自己是否已经具有该权限。
- 如果用户拒绝授权,将无法再次弹窗。需要引导用户进行手动授权,也可以使用requestPermissionOnSetting再次向用户申请授权,此时不再弹窗,而是一个拉起半模态窗口引导用户授权。
- 用户手动授权:
- 路径一:设置 -> 隐私与安全 -> 权限类型(如位置信息) -> 具体应用
- 路径二:设置 -> 应用和元服务 -> 某个应用
- 用户手动授权:
再次向用户申请授权
import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
atManager.requestPermissionOnSetting(context, ['ohos.permission.APPROXIMATELY_LOCATION']).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
console.info(`requestPermissionOnSetting success, result: ${data}`);
}).catch((err: BusinessError) => {
console.error(`requestPermissionOnSetting fail, code: ${err.code}, message: ${err.message}`);
});
链接
Native侧与ArkTS侧的相互调用:CommonAppDevelopment/feature/etswrapper · HarmonyOS-Cases/Cases
弹框封装:CommonAppDevelopment/feature/encapsulationdialog · HarmonyOS-Cases/Cases
使用colorPicker实现背景跟随主题颜色转换:CommonAppDevelopment/feature/effectkit · HarmonyOS-Cases/Cases
Grid和List内拖拽交换子组件位置:CommonAppDevelopment/feature/dragandexchange · HarmonyOS-Cases/Cases
数字滚动动效:CommonAppDevelopment/feature/digitalscrollanimation · HarmonyOS-Cases/Cases
图片选择和下载保存案例:CommonAppDevelopment/feature/photopickandsave · HarmonyOS-Cases/Cases
json5
oh-package.json5
从OHPM 5.0.0版本开始,支持区分工程级与模块级oh-package.json5配置。其中:
- 工程级oh-package.json5文件:位于工程根目录下,主要用来描述全局配置,如:依赖覆盖(overrides)、依赖关系重写(overrideDependencyMap)和参数化配置(parameterFile)等。
- 模块级oh-package.json5文件:位于工程各个模块的根目录下,用来描述包名、版本、入口文件(类型声明文件)和依赖项等信息。
开发者可将标准的DevEco Studio工程下的各个模块打成HAR包后,发布到OpenHarmony三方库中心仓;所有发布到仓库的包必须包含模块级oh-package.json5文件,以描述当前包基本信息。
提示
可以鼠标左键单击某个属性查看解释
app.json5
module.json5
build-profile.json5
开发
无障碍(李跳跳
警告
HarmonyOS NEXT现在不再开放无障碍API
重要
虚拟机不支持无障碍Kit
Accessibility Kit简介-Accessibility Kit(无障碍开发服务)-应用框架 | 华为开发者联盟
ArkTS API-Accessibility Kit(无障碍开发服务)-应用框架 | 华为开发者联盟
@ohos.accessibility.GesturePoint (手势触摸点)-ArkTS API-Accessibility Kit(无障碍开发服务)-应用框架 | 华为开发者联盟
- 检测当前窗口内的App包名
- 检测指定App窗口内的是否存在某个文字
HSP & HAR
应用程序包概述-应用程序包基础知识-开发基础知识-基础入门 | 华为开发者联盟

Stage 模型
基本概念

每个Entry类型或者Feature类型的HAP在运行期都有一个AbilityStage实例,当HAP中的代码首次被加载到进程中的时候,系统会先创建AbilityStage实例。
UIAbility组件和ExtensionAbility组件
Stage模型提供UIAbility和ExtensionAbility两种类型的组件,这两种组件都有具体的类承载,支持面向对象的开发方式。
- UIAbility组件是一种包含UI的应用组件,主要用于和用户交互。例如,图库类应用可以在UIAbility组件中展示图片瀑布流,在用户选择某个图片后,在新的页面中展示图片的详细内容。同时用户可以通过返回键返回到瀑布流页面。UIAbility组件的生命周期只包含创建、销毁、前台、后台等状态,与显示相关的状态通过WindowStage的事件暴露给开发者。
- ExtensionAbility组件是一种面向特定场景的应用组件。开发者并不直接从ExtensionAbility组件派生,而是需要使用ExtensionAbility组件的派生类。目前ExtensionAbility组件有用于卡片场景的FormExtensionAbility,用于输入法场景的InputMethodExtensionAbility,用于延时任务场景的WorkSchedulerExtensionAbility等多种派生类,这些派生类都是基于特定场景提供的。例如,用户在桌面创建应用的卡片,需要应用开发者从FormExtensionAbility派生,实现其中的回调函数,并在配置文件中配置该能力。ExtensionAbility组件的派生类实例由用户触发创建,并由系统管理生命周期。在Stage模型上,三方应用开发者不能开发自定义服务,而需要根据自身的业务场景通过ExtensionAbility组件的派生类来实现。
一个HAP包中可以包含一个或多个UIAbility/ExtensionAbility组件,这些组件在运行时共用同一个AbilityStage实例。当HAP中的代码(无论是UIAbility组件还是ExtensionAbility组件)首次被加载到进程中的时候,系统会先创建对应的AbilityStage实例。
每个UIAbility实例都会与一个WindowStage类实例绑定,该类起到了应用进程内窗口管理器的作用。它包含一个主窗口。也就是说UIAbility实例通过WindowStage持有了一个主窗口,该主窗口为ArkUI提供了绘制区域,可以加载不同的ArkUI页面。
在Stage模型上,Context及其派生类向开发者提供在运行期可以调用的各种资源和能力。UIAbility组件和各种ExtensionAbility组件的派生类都有各自不同的Context类,他们都继承自基类Context,但是各自又根据所属组件,提供不同的能力。
Context
详情:应用上下文Context-Stage模型应用组件-指南
不同类型Context的说明
| Context类型 | 说明 | 获取方式 | 使用场景 |
|---|---|---|---|
| ApplicationContext | 应用的全局上下文,提供应用级别的信息和能力。 | - 从API version 14开始,可以直接使用getApplicationContext获取。- API version 14以前版本,只能使用其他Context实例的getApplicationContext方法获取。 | - 获取当前应用的基本信息。- 获取应用级别的文件路径。- 获取和修改加密分区。- 注册生命周期监听。 |
| AbilityStageContext | 模块级别的上下文,提供模块级别的信息和能力。 | - 如果需要获取当前AbilityStage的Context,可以直接通过AbilityStage实例获取context属性。- 如果需要获取同一应用中其他Module的Context,可以通过createModuleContext方法。 | - 获取当前模块的基本信息。- 获取模块的文件路径。 |
| UIAbilityContext | UIAbility组件对应的上下文,提供UIAbility对外的信息和能力。 | - 通过UIAbility实例直接获取context属性。- 在UIAbility的窗口中加载的UI组件实例,需要使用UIContext的getHostContext方法。 | - 获取当前UIAbility基本信息。- 启动其他应用或元服务、连接/断连系统应用创建的ServiceExtensionAbility等。- 销毁自身的UIAbility。 |
| ExtensionContext | ExtensionAbility组件对应的上下文,每种类型的ExtensionContext提供不同的信息和能力。 | 通过ExtensionAbility实例直接获取Context属性。 | 不同类型的ExtensionAbility对应的Context提供的能力不同。以输入法上下文InputMethodExtensionContext为例,主要提供如下能力:- 获取InputMethodExtensionAbility的基本信息。- 销毁当前输入法。 |
| UIContext | ArkUI的UI实例上下文,提供UI操作相关的能力。与上述其他类型的Context无直接关系。 | - 在UI组件内获取UIContext,直接使用getHostContext方法。- 在存在Window实例的情况下,使用Window提供的getUIContext方法。 | 主要用于UI实例中UI相关操作,例如:- 获取当前UI实例的字体。- 显示不同类型的弹框。- 设置软键盘弹出时UI避让模式。 |
不同类型Context的继承关系如下:

不同类型Context的持有关系如下:

提示
UIContext是指UI实例上下文,用于关联窗口*(window)与UI页面(@Entry)*。与本文档中的应用上下文Context无直接关联,不存在继承或持有关系。
应用启动
module.json5中mainElement指定了当前Module的入口UIAbility名称或者ExtensionAbility名称"module": { "name": "flexmod", "type": "entry", "description": "$string:module_desc", "mainElement": "FlexmodAbility", ... }module.json5中abilities下面的srcEntry指定了当前Ability的入口为FlexmodAbility.ets"abilities": [ { "name": "FlexmodAbility", "srcEntry": "./ets/flexmodability/FlexmodAbility.ets", "description": "$string:ability_desc", "icon": "$media:layered_image", "label": "$string:ability_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": [ "entity.system.home" ], "actions": [ // "action.system.home" "ohos.want.action.home" ] } ] } ],FlexmodAbility.ets应用入口Ability中的
onWindowStageCreate方法,使用windowStage.loadContent()方法设置应用要加载的页面:export default class FlexmodAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { // Main window is created, set main page for this ability. Logger.info('Ability onWindowStageCreate'); windowStage.loadContent('pages/Index', (err) => { if (err.code) { Logger.error('Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } Logger.info('Succeeded in loading the content.'); }); } }
UIAbility的生命周期
提示
一个hap可以有多个UIAbility

- UIAbility启动到前台,对应流程图参见上图。
- 当用户启动一个UIAbility时,系统会依次触发onCreate()、onWindowStageCreate()、onForeground()生命周期回调。
- 当用户跳转到其他应用(当前UIAbility切换到后台)时,系统会触发onBackground()生命周期回调。
- 当用户再次将UIAbility切换到前台时,系统会依次触发onNewWant()、onForeground()生命周期回调。
UIAbility启动到后台,对应流程图参见下图。
- 当用户通过UIAbilityContext.startAbilityByCall()接口启动一个UIAbility到后台时,系统会依次触发onCreate()、onBackground()(不会执行onWindowStageCreate()生命周期回调)生命周期回调。
- 当用户将UIAbility拉到前台,系统会依次触发onNewWant()、onWindowStageCreate()、onForeground()生命周期回调。

手机配置文件(环境变量)发生改变,会触发
UIAbility的onConfigurationUpdate事件onConfigurationUpdate(newConfig: Configuration): void { AppStorage.setOrCreate('currentColorMode', newConfig.colorMode); hilog.info(0x0000, 'EntryAbility', 'the newConfig.colorMode is %{public}s', JSON.stringify(AppStorage.get('currentColorMode')) ?? ''); }
在 Stage 模型下,应用主窗口由 UIAbility 创建并维护生命周期。
Create:应用加载过程中,UIAbility实例创建完成时系统会调用onCreate()回调。可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示。在进入
Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。在
onWindowStageCreate()回调中通过loadContent()方法设置应用要加载的页面,并根据需要调用on('windowStageEvent')方法订阅WindowStage的事件(获焦/失焦、可见/不可见)。onWindowStageCreate(windowStage: window.WindowStage): void { // 设置WindowStage的事件订阅(获焦/失焦、可见/不可见) try { windowStage.on('windowStageEvent', (data) => { let stageEventType: window.WindowStageEventType = data; switch (stageEventType) { case window.WindowStageEventType.SHOWN: // 切到前台 console.info('windowStage foreground.'); break; case window.WindowStageEventType.ACTIVE: // 获焦状态 console.info('windowStage active.'); break; case window.WindowStageEventType.INACTIVE: // 失焦状态 console.info('windowStage inactive.'); break; case window.WindowStageEventType.HIDDEN: // 切到后台 console.info('windowStage background.'); break; default: break; } }); } catch (exception) { console.error('Failed to enable the listener for window stage event changes. Cause:' + JSON.stringify(exception)); } // 设置UI加载 windowStage.loadContent('pages/Index', (err) => { // ... if (err.code) { Logger.error('Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } Logger.info('Succeeded in loading the content.'); }); }
页面和自定义组件生命周期
指南:自定义组件的生命周期
API:自定义组件的生命周期
在开始之前,我们先明确自定义组件和页面的关系:
- 自定义组件:@Component或@ComponentV2装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
页面生命周期,只有 被@Entry装饰的组件(仅router)生命周期,才提供以下生命周期接口:
- onPageShow(仅router):页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide(仅router):页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress(仅router):当用户点击返回按钮时触发。
组件生命周期,即用 装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
- onDidBuild:组件build()函数执行完成之后回调该接口,不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
- onWillApplyTheme12+:onWillApplyTheme函数用于获取当前组件上下文的Theme对象,在创建自定义组件的新实例后,在
aboutToAppear之前、build()之后执行。允许在onWillApplyTheme函数中改变状态变量,更改将在后续执行build()函数中生效。- *注:每次执行
ThemeControl.setDefaultTheme()改变了主题之后,onWillApplyTheme()回调都会再次执行。处于这个特性,写了这么实现动态切换主题色的代码:动态改变主题色
- *注:每次执行
生命周期流程如下图所示,下图展示的是被 @Entry 装饰的组件(页面)生命周期。

根据上面的流程图,我们从自定义组件的初始创建、重新渲染和删除来详细解释。
自定义组件的创建和渲染流程
- 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
- 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
- 如果开发者定义了aboutToAppear,则执行aboutToAppear方法。
- 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
- 如果开发者定义了onDidBuild,则执行onDidBuild方法。
自定义组件重新渲染
当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:
- 框架观察到了变化,将启动重新渲染。
- 根据框架持有的两个map(自定义组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。
自定义组件的删除
如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:
- 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。
- 自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。
不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。
以下示例展示了生命周期的调用时机:
// Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor:string = "#FF007DFF"
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide() {
console.info('Index onPageHide');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() {
console.info('Index onBackPress');
this.btnColor ="#FFEE0606"
return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
}
// 组件生命周期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// 组件生命周期
onDidBuild() {
console.info('MyComponent onDidBuild');
}
// 组件生命周期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild为true,创建Child子组件,执行Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild为false,删除Child子组件,执行Child aboutToDisappear
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
this.showChild = false;
})
// push到page页面,执行onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/page' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// 组件生命周期
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// 组件生命周期
onDidBuild() {
console.info('[lifeCycle] Child onDidBuild');
}
// 组件生命周期
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear')
}
build() {
Text(this.title)
.fontSize(50)
.margin(20)
.onClick(() => {
this.title = 'Hello ArkUI';
})
}
}
// page.ets
@Entry
@Component
struct page {
@State textColor: Color = Color.Black;
@State num: number = 0
onPageShow() {
this.num = 5
}
onPageHide() {
console.log("page onPageHide");
}
onBackPress() { // 不设置返回值按照false处理
this.textColor = Color.Grey
this.num = 0
}
aboutToAppear() {
this.textColor = Color.Blue
}
build() {
Column() {
Text(`num 的值为:${this.num}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.textColor)
.margin(20)
.onClick(() => {
this.num += 5
})
}
.width('100%')
}
}
以上示例中,Index页面包含两个自定义组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。只有@Entry装饰的节点才可以使页面级别的生命周期方法生效,因此在MyComponent中声明当前Index页面的页面生命周期函数(onPageShow / onPageHide / onBackPress)。MyComponent和其子组件Child分别声明了各自的组件级别生命周期函数(aboutToAppear / onDidBuild/aboutToDisappear)。
- 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> MyComponent onDidBuild--> Child aboutToAppear --> Child build --> Child onDidBuild --> Index onPageShow。
- 点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。
- 点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
- 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。
- 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。
- 最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。
- 退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
自定义组件监听页面生命周期
使用无感监听页面路由的能力,能够实现在自定义组件中监听页面的生命周期。
// Index.ets
import { uiObserver, router, UIObserver } from '@kit.ArkUI';
@Entry
@Component
struct Index {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`Index onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`Index onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`this page is ${this.queryRouterPageInfo()?.pageId}`)
.fontSize(25)
Button("push self")
.onClick(() => {
router.pushUrl({
url: 'pages/Index'
})
})
Column() {
SubComponent()
}
}
}
}
@Component
struct SubComponent {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`SubComponent onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`SubComponent onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`SubComponent`)
}
}
}
应用关闭
import { common } from '@kit.AbilityKit';
private UIContext: UIContext = this.getUIContext()
private ApplicationContext: common.ApplicationContext = this.getUIContext().getHostContext() as common.ApplicationContext
private UIAbilityContext: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext
// 停止Ability自身。(不影响已其他启动的UIAbility)
this.UIAbilityContext.terminateSelf()
.then(()=>{
hilog.info(DOMAIN, TAG, `#onBackPress#terminateSelf() terminate Ability Sucessfully!`);
})
.catch((error: BusinessError) => {
hilog.error(DOMAIN, TAG, `ERROR: #onBackPress#terminateSelf(): ${JSON.stringify(error)}`)
})
// 终止应用的所有进程,进程退出时不会正常走完应用生命周期。
// this.ApplicationContext.killAllProcesses()
// .then()
// .catch()
提示
- 调用
context.terminateSelf()方法停止当前UIAbility实例,默认会保留该实例的快照(Snapshot),即在最近任务列表中仍然能查看到该实例对应的任务。如不需要保留该实例的快照,可以在其对应UIAbility的module.json5配置文件中,将abilities标签的removeMissionAfterTerminate字段配置为true。 - 如需要关闭应用所有的UIAbility实例,也就是整个应用。可以调用ApplicationContext的killAllProcesses()方法实现关闭应用所有的进程。
可能需要:连续返回两次退出应用
应用沙箱目录
应用文件目录结构图

通过ApplicationContext获取应用级别的应用文件路径,此路径是应用全局信息推荐的存放路径,这些文件会跟随应用的卸载而删除。
通过AbilityStageContext、UIAbilityContext、ExtensionContext获取HAP级别的应用文件路径。此路径是HAP相关信息推荐的存放路径,这些文件会跟随HAP的卸载而删除,但不会影响应用级别路径的文件,除非该应用的HAP已全部卸载。
应用需要对应用文件目录下的应用文件进行查看、创建、读写、删除、移动、复制、获取属性等访问操作
1.应用文件目录:
- 这是应用在沙箱保护机制下保存文件的主要目录。应用可以在此目录下保存和处理自己的文件,如用户数据、配置文件、图片、媒体文件等 ![引用1]。
- 路径格式为
/data/storage/<APP_NAME>/files,其中<APP_NAME>是应用的包名。
2.应用缓存文件路径: ![][引用1]
- 应用缓存文件路径用于存储应用的缓存数据,如下载的文件、图片缓存、数据库缓存等 ![引用1]。
- 路径格式为
/data/storage/<APP_NAME>/cache。
3.应用首选项数据路径: ![][引用1]
- 应用首选项数据路径用于存储应用的配置文件和首选项数据,如用户设置、偏好配置等。
- 路径格式为
/data/storage/<APP_NAME>/preferences。
4.应用临时文件路径:
- 应用临时文件路径用于存储应用运行期间产生的临时文件,如数据库缓存、图片缓存、临时日志文件等。
- 路径格式为
/data/storage/<APP_NAME>/temp。
5.数据库路径:
- 应用在el2加密条件下存储私有数据库数据的目录为
/data/storage/<APP_NAME>/database![引用1]。
6.分布式文件路径:
- 应用在el2加密条件下存储分布式文件的目录为
/mnt/hmdfs/<USER_ID>/account/merge_view/data/<APP_NAME>![][引用1]。 - 此路径适用于多设备场景下的数据共享和备份。
优化-状态管理
状态管理最佳实践-开发高性能ArkUI-性能优化-性能 | 华为开发者联盟
MVVM
MVVM 将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。
- View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。
- ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,通常一个View对应一个ViewModel,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。
- Model:负责存储和管理应用的数据以及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。
ArkTS
状态管理
ArkUI
导航
动态改变主题色
在 AppTheme.ets 定义好主题之后,在 Index.ets 的 aboutToAppear 方法内使用 ThemeControl.setDefaultTheme 方法就可以更改当前主题,利用调用 ThemeControl.setDefaultTheme 方法 onWillApplyTheme() 回调就会执行的特性,给 @Provide('key') 所修饰变量赋值来使其子组件(使用@Consume('key'))得到新的主题色变更。
注
需确保 ThemeControl.setDefaultTheme() 接口在页面build前执行。若在UIAbility中使用此接口设置应用级默认主题,需确保该接口在onWindowStageCreate阶段里windowStage.loadContent接口调用完成的回调函数中执行。详细代码可参考设置应用内组件自定义主题色。
AppTheme.etsimport { CustomColors, CustomTheme } from '@ohos.arkui.theme' import { HashMap } from '@kit.ArkTS' /* 主题定义 多个主题就创建多个*/ /* 鸿蒙蓝 blueAppTheme */ // 主题颜色 class BlueAppThemeColors implements CustomColors { // 这里以icon主题色为例 iconEmphasize: ResourceColor = $r('sys.color.icon_emphasize') // fontPrimary等颜色 } // 主题配置 class BlueAppTheme implements CustomTheme { public colors: BlueAppThemeColors = new BlueAppThemeColors() } /* 红色 redTheme */ // 主题颜色 class RedAppThemeColors implements CustomColors { iconEmphasize: ResourceColor = Color.Red } // 主题配置 class RedAppTheme implements CustomTheme { public colors: BlueAppThemeColors = new RedAppThemeColors() } /* 导出 */ // 自定义主题名称 TODO 补充不提名称字符串 export type CustomThemeName = 'blueAppTheme' | 'redAppTheme' // 自定义主题HashMap let _CUSTOM_THEME_MAP: HashMap<CustomThemeName, CustomTheme> = new HashMap<CustomThemeName, CustomTheme>() _CUSTOM_THEME_MAP.set('blueAppTheme', new BlueAppTheme()) _CUSTOM_THEME_MAP.set('redAppTheme', new RedAppTheme()) export const CUSTOM_THEME_MAP = _CUSTOM_THEME_MAPIndex.etsimport { Theme, ThemeControl, CustomColors, Colors, CustomTheme, CustomDarkColors } from '@kit.ArkUI' import { CustomThemeName, CUSTOM_THEME_MAP } from '../common/AppTheme' // 官方示例实在这里直接改的主题,测试的时候发现`aboutToAppear()`回调执行早于`onWillApplyTheme()`,索性直接在`aboutToAppear`里写了 // ThemeControl.setDefaultTheme('xxx') @Entry @Component struct Index { // @StorageProp('selectedTheme') // TODO 使用用户首选项之后 删掉`@State`,取消注释上面的`@StorageProp` @State selectedTheme: CustomThemeName = 'redAppTheme' // 系统颜色调用(现在每个主题中只有一个颜色,如果有多个颜色,可以把这个换成主题色对应的`CustomTheme`) @Provide('icon_emphasize') icon_emphasize: ResourceColor = $r('sys.color.icon_emphasize') build(){} /* * 生命周期方法:在build()函数执行前调用。允许在本函数中改变状态变量,更改将在后续执行build()函数中生效。 */ aboutToAppear() { // 在页面build前执行ThemeControl,就可以改变主题颜色 ThemeControl.setDefaultTheme(CUSTOM_THEME_MAP.get(this.selectedTheme)) hilog.info(0x1000, this.componentName, `#aboutToAppear#ThemeControl.setDefaultTheme(${this.selectedTheme})`) } /** * 生命周期方法:获取当前组件上下文的Theme对象,在build()函数之前执行。允许在onWillApplyTheme函数中改变状态变量,更改将在后续执行build()函数中生效。 * @param theme 当前启用的主题 */ onWillApplyTheme(theme: Theme) { hilog.info(0x1000, this.componentName, `#onWillApplyTheme`) this.icon_emphasize = theme.colors.iconEmphasize; } }
UI单位
问:UI布局默认是多少 vp 为基准,以达到不同机器自适应
无论屏幕分辨率或密度如何,组件的视觉效果保持一致。
vp具体计算公式为:vp= px/(DPI/160)
px 是屏幕的真实物理像素值,densityDPI 通常指系统屏幕密度,densityPixels 是屏幕密度与标准DPI的比率,常见取值有 0.75、1.0、1.5、2.0、3.0 等。在HarmonyOS中,标准DPI为 160 。以华为Mate 40 Pro为例, densityDPI 为 560,densityPixels 为 3.5。要查看真机的DPI,可以调用屏幕属性中的 display 接口查询。
import { display } from '@kit.ArkUI';
let displayClass: display.Display | null = null;
try {
displayClass = display.getDefaultDisplaySync();
} catch (exception) {
console.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception));
}
如果原型图没有提供vp单位的布局,开发者可以根据 densityPixels 把 px转为vp,HarmonyOS也封装了现成的接口 px2vp() 和 vp2px() 供开发者直接调用。
参考链接
连续返回两次退出应用
前提:自定义组件需要被 @Entry 修饰
示例代码:
import { hilog } from '@kit.PerformanceAnalysisKit';
import { systemDateTime } from '@kit.BasicServicesKit';
import { PromptAction } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
const DOMAIN = 0xfff0
const TAG = `NavigationExample`
@Entry
@Component
struct NavigationExample {
// Context
private UIContext: UIContext = this.getUIContext()
private ApplicationContext: common.ApplicationContext = this.getUIContext().getHostContext() as common.ApplicationContext
private UIAbilityContext: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext
private promptAction : PromptAction = this.UIContext.getPromptAction()
// Navigation 页面栈
pageInfos: NavPathStack = new NavPathStack();
// 记录触发返回时的时间
exitTime: number = 0
build() {
Navigation(this.pageInfos) {
// ...
}
}
/**
* 在router路由页面(即@Entry装饰的自定义组件)生效,当用户点击返回按钮时触发。
* @returns 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理。
*/
onBackPress() {
hilog.info(DOMAIN, TAG, `#onBackPress() executed`);
let currentTime = systemDateTime.getTime(false)
// 两次返回操作时间间隔,当前为大于2000ms就执行退出Ability操作。按照业务需求修改此间隔
if (currentTime - this.exitTime > 2000) {
this.exitTime = currentTime
this.promptAction.showToast({ message: '再次返回将退出应用', duration: 2000 })
return true
} else {
// 停止Ability自身。(不影响已其他启动的UIAbility)
this.UIAbilityContext.terminateSelf()
.then(()=>{
hilog.info(DOMAIN, TAG, `#onBackPress#terminateSelf() terminate Ability Sucessfully!`);
})
.catch((error: BusinessError) => {
hilog.error(DOMAIN, TAG, `ERROR: #onBackPress#terminateSelf(): ${JSON.stringify(error)}`)
})
// 终止应用的所有进程,进程退出时不会正常走完应用生命周期。
// this.ApplicationContext.killAllProcesses()
// .then()
// .catch()
return false
}
}
}
提示
如果退出UIAbility之后不想在后台保留快照(Snapshot),可以参考应用关闭,并关注最下方的tip。
CPP
参考我自己的项目(多.cpp):https://gitcode.com/hmos-dev/audio-recorder
N-API使用指导:napi-guidelines.md - OpenHarmony/docs示例
native项目(单.cpp):NativeTemplateDemo - OpenHarmony Codelabs示例
native项目(多.cpp):NativeAPI/XComponent示例
native项目(多.cpp):Native/NdkXComponent
逆向 & 反编译
仓库:ohos-decompiler/abc-decompiler
提示
GitCode镜像:abc-decompiler/releases
使用方法
下载发行版:
jadx-dev-all.jar使用鸿蒙SDK拆包工具拆包出
.abc文件# 1. 进入鸿蒙工具链文件夹 cd \DevEco Studio\sdk\default\openharmony\toolchains\lib # 执行解包命令 java -jar app_unpacking_tool.jar --mode hap --hap-path <path> --out-path <path> [--force true]指令 是否必选项 选项 描述 --mode 是 app | hap 拆包类型。 --hap-path 是 NA HAP包路径。 --rpcid 否 true或者false 是否单独将rpcid文件从HAP包中提取到指定目录。如果为true,将仅提取rpcid文件,不对HAP包进行拆包。(app模式时不可用) --out-path 是 NA 拆包目标文件路径。 --force 否 true或者false 默认值为false。如果为true,表示当目标文件存在时,强制删除。 运行逆向工具
# path 是 `.abc` 的路径 java -jar jadx-dev-all.jar <path>或者先运行
jar,然后把.abc拖进工具里java -jar jadx-dev-all.jar
提示
反编译成功可能需要:方舟字节码函数命名规则
