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
{"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
{"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 打开生成的字节码文件,部分内容如下所示:
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();}
}