1、Hap与App包的区别
OpenHarmony 可以进行两种形式(Hap和App)的打包,HAP是用于本地调试的,APP包是用于上架发布的。 根据不同的设备类型,一个APP包可以包含多个HAP包。
下面从两个角度进行分析
1.1 编译构建角度
- 编译构建是将HarmonyOS应用的源代码、资源、第三方库等打包生成HAP或者APP的过程。其中,HAP可以直接运行在真机设备或者模拟器中;APP则是用于应用上架到华为应用市场。
- 一个HarmonyOS工程下可以存在多个Module,在编译构建时,可以选择对单个Module进行编译构建;也可以对整个工程进行编译构建,同时生成多个HAP。
1.2 签名角度
-
App
HarmonyOS应用发布形态为APP Pack(Application Package,简称APP),它是由一个或多个HAP(HarmonyOS Ability Package)包以及描述APP Pack属性的pack.info文件组成。
-
Hap
1、 一个HAP在工程目录中对应一个Module,它是由代码、资源、第三方库及应用配置文件组成,可以分为Entry和Feature两种类型。Entry:应用的主模块。一个APP中,对于同一设备类型必须有且只有一个entry类型的HAP,可独立安装运行。Feature:应用的动态特性模块。一个APP可以包含一个或多个feature类型的HAP,也可以不含。
2、HAP是Ability的部署包,HarmonyOS应用代码围绕Ability组件展开,它是由一个或多个Ability组成。
3、一个App可以有很多HAP。HAP可以直接在模拟器或者真机设备上运行,用于HarmonyOS应用开发阶段的调试和查看运行效果。
4、原子化服务由1个或多个HAP包组成,1个HAP包对应1个FA或1个PA。每个FA或PA均可独立运行,完成1个特定功能;1个或多个功能(对应FA或PA)完成1个特定的便捷服务。
2、探究Hap的结构
OpenHarmony 的 hap 包本质上是一个压缩包,可以更改后缀名解压压缩包看下 hap 包里的文件结构,解压出来的文件结构如下所示:
2.1 解压Hap包
2.2 Hap的目录结构
2.3 目录说明
Hap 包解压后,里边包含了一个 ets、resources两个 目录和三个配置文件。
- ets:编译后的源码文件;
- modules.abc:源码编译之后的方舟字节码
- sourceMaps.map:配置项,个人理解为是abc文件的一个索引文件;
- resources:资源文件;
- module.json/pack.info: Hap 包的配置文件;
- resources.index: 资源目录的索引文件;
abc 文件表示方舟字节码(ark bytecode,简称 abc),所以项目运行的时候 ark 虚拟机需要加载运行这些 abc 文件。
2.3.1 module.json
typescript
{
"app": {
"apiReleaseType": "Release",
"bundleName": "com.example.healthydiet",
"compileSdkType": "OpenHarmony",
"compileSdkVersion": "3.2.13.5",
"debug": true,
"distributedNotificationEnabled": true,
"icon": "$media:app_icon",
"iconId": 16777217,
"label": "$string:app_name",
"labelId": 16777216,
"minAPIVersion": 9,
"targetAPIVersion": 9,
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0"
},
"module": {
"abilities": [
{
"description": "$string:EntryAbility_desc",
"descriptionId": 16777218,
"icon": "$media:icon",
"iconId": 16777362,
"label": "$string:EntryAbility_label",
"labelId": 16777219,
"name": "EntryAbility",
"skills": [
{
"actions": [
"action.system.home"
],
"entities": [
"entity.system.home"
]
}
],
"srcEntrance": "./ets/entryability/EntryAbility.ts",
"startWindowBackground": "$color:start_window_background",
"startWindowBackgroundId": 16777286,
"startWindowIcon": "$media:icon",
"startWindowIconId": 16777362,
"visible": true
}
],
"compileMode": "esmodule",
"deliveryWithInstall": true,
"dependencies": [],
"description": "$string:module_desc",
"descriptionId": 16777262,
"deviceTypes": [
"default"
],
"installationFree": false,
"mainElement": "EntryAbility",
"metadata": [
{
"name": "ArkTSPartialUpdate",
"value": "true"
}
],
"name": "entry",
"pages": "$profile:main_pages",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
],
"type": "entry",
"virtualMachine": "ark9.0.0.0"
}
}
module.json 是项目里的默认配置文件,只是在打包的时候添加了一些额外的默认配置,比如 distro 下添加了值为 ark 的 virtualMachine 字段,表示当前 hap 包表运行在 ark 虚拟机里等。
2.3.2 pack.info
typescript
{
"summary": {
"app": {
"bundleName": "com.example.healthydiet",
"version": {
"code": 1000000,
"name": "1.0.0"
}
},
"modules": [
{
"mainAbility": "EntryAbility",
"deviceType": [
"default"
],
"abilities": [
{
"name": "EntryAbility",
"label": "$string:EntryAbility_label",
"visible": true
}
],
"distro": {
"moduleType": "entry",
"installationFree": false,
"deliveryWithInstall": true,
"moduleName": "entry"
},
"apiVersion": {
"compatible": 9,
"releaseType": "Release",
"target": 9
}
}
]
},
"packages": [
{
"deviceType": [
"default"
],
"moduleType": "entry",
"deliveryWithInstall": true,
"name": "entry-default"
}
]
}
pack.info用来描述和定义软件包(Package)的信息和属性。
2.3.3 区别
pack.info 是关于整个软件包的信息,而 module.json 是关于单个模块的信息
- pack.info 是用于描述和定义整个软件包的信息,它位于软件包的根目录下,用于标识和描述软件包本身。 module.json
- 是用于描述一个模块的信息,它位于模块的根目录下,用于定义模块的属性和功能。
3、思考鸿蒙应用的启动流程
- 桌面点击应用App图标;
- 操作系统(OpenHarmony)检测到应用启动请求,并根据应用的包名和入口信息找到对应的 HAP 包路径;
- 操作系统加载 HAP 包的信息,并解析其中的配置文件,如 pack.info、module.json等;
- 操作系统根据配置文件中的入口信息,找到应用的入口类和入口方法;
- 操作系统创建应用的运行环境,并执行应用的入口类和入口方法;
- 应用的入口方法执行初始化操作,如创建应用窗口、注册事件监听器等,加载资源文件;
- 应用初始化完成后,执行渲染页面,执行生命周期,进行网络请求等;
- 用户关闭应用,应用关闭;
4、备注
可以通过 010 Editor 打开生成的字节码文件,部分内容如下所示:
typescript
class PreviewCustomCounter extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, __localStorage, elmtId);
this.__weight = new ObservedPropertySimplePU(50, this, "weight");
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
if (params.weight !== undefined) {
this.weight = params.weight;
}
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__weight.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__weight.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
get weight() {
return this.__weight.get();
}
set weight(newValue) {
this.__weight.set(newValue);
}
initialRender() {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Row.create();
if (!isInitialRender) {
Row.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
{
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
if (isInitialRender) {
ViewPU.create(new CustomCounter(this, {
value: this.weight + 'g',
onDec: () => {
this.weight -= 50;
},
onInc: () => {
this.weight += 50;
}
}, undefined, elmtId));
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {
value: this.weight + 'g'
});
}
ViewStackProcessor.StopGetAccessRecording();
});
}
Row.pop();
}
rerender() {
this.updateDirtyElements();
}
}