前言:之前的文章中,我们算是入门了,成功构建了一个非常简单的鸿蒙应用。现在我们来了解一下鸿蒙应用程序包的一些概念性的知识,这会贯穿于我们整个开发流程。另外说明一下:这个文章几乎都是官方文档中的内容,跟着写一遍加深记忆。
1. Stage应用程序包结构
HarmonyOS提供了应用程序包开发、安装、查询、更新、卸载的管理机制,方便开发者开发和管理HarmonyOS应用。
基于Stage开发模型开发的应用,经过编译打包之后,如下图所示:
1.1 Module的介绍
我们之前简单介绍过Stage模型的包结构,并且也尝试创建了一个新的Module,这次再来深入的了解一下:
- 一个应用包含一个或者多个Module;
- Module是HarmonyOS应用/服务的基本功能单元,包含了源代码、资源文件、三方库应用/服务配置文件;
- 每个Module都可以独立编译和运行;
- Module分为Ablity和Library两种类型:
- Ablity类型的Module对应于编译后的HAP(Harmony Ability Package)
- Library类型的Module对应于HAR(Harmony Archive)或者HSP(Harmony Shared Package)
在前期我们接触的更多的是Ablity类型的Module。
一个Module可以包含一个或多个UIAbility组件,如下图所示:
1.2 UIAbility组件是什么?
UIAblity组件是一种包含UI界面的应用组件,用于和用户交互。在一个UIAbility组件中可以通过多个页面来实现一个功能模块。每一个UIAbility组件实例,都对应于一个最近任务列表中的任务,就是是手机上的最近任务列表。
理解如下:UIAbility主要是提供界面展示和用户交互,可以将单个功能模块中的页面构建成一个UIAbility,在一个Module中也是可以有多个UIAbility的。
1.3 .hap文件
DevEco Studio会把工程中的Module,编译成对应的.hap后缀的文件,即HAP。HAP是HarmonyOS应用安装的基本单位,包含了编译后的代码、资源、三方库及配置文件。
HAP可以分为Entry和Feature两种类型:
- Entry类型:应用的主模块,在module.json5配置文件中的type标签配置为"entry"类型。同一个应用中,只支持一个Entry类型的HAP,通常用于实现应用的入口界面、入口图标、主特性功能等。
下面是之前应用中的Entry的module.json5文件:
- Feature类型:动态特性模块,配置文件中的type为feature,在一个应用程序中可以有0个或多个。通常用于实现应用的特性功能,可以配置成按需下载安装,也可以配置成随Entry类型的HAP一起下载安装
下面是之前新创建的Module的配置文件:
配置文件中的deliveryWithInstall就是控制是否随Entry一起下载安装。
1.4 Bundle
每个鸿蒙应用都可以包含多个.hap文件,一个应用中的.hap文件合在一起就称为一个Bundle,而bundleName就是应用的唯一标识(app.json5文件中进行配置)。
在应用上架到应用市场时,需要把所有的.hap文件(即Bundle)打包为一个.app后缀的文件用于上架,这个.app文件成为App Pack(Application Package),其中同时包含了描述App Pack属性的pack.info文件。
另外需要注意一点:在云端分发和终端设备安装时,都是以HAP为单位进行分发和安装的。相当于本地开发的生成的.app文件中会包括所有的HAP,但是可能会针对于不同的设备使用部分HAP,最后下载到用户手机的只是部分HAP。.app文件的作用只是聚合这些HAP,用于上架应用市场。
1.5 HAP包结构
打包后的HAP中有以下文件:
- ets:用于存放应用代码编译后的字节码文件
- libs:用于存放库文件。库文件是鸿蒙应用依赖的第三方代码(.so)
- resources:资源文件(字符串、图片)
- module.json:配置文件。内容由工程配置中的module.json5和app.json5组成
- pack.info:是Bundle中用于描述每个HAP属性的文件,例如app中的bundleName和versionCode信息、module中的name、type和abilities等信息,由IDE工具生成Bundle包时自动生成。
目前暂时没有找到办法直接打开鸿蒙.app文件夹进行验证,只能通过官网上的描述去了解了。
2.多HAP机制
2.1 设计目标
上面提到一个应用会有多个HAP,那为何要这样设计呢?
1.方便模块化管理应用:
将应用划分多个模块,每个模块的实现放到独立的HAP中。Entry中统一集成和管理Feature包,Feature包去进行单独的开发和测试;
2.可以灵活的组合和部署HAP:
比如应用程序有一个Entry包和两个Feature包,其中Feature1只能部署到设备A,Feature2只能部署到设备B,那开发者就可以方便的组合Entry和Feature部署到不同的设备上;
3.方便资源共享,减少程序包大小
多个HAP需要用到的资源以及so文件可以放到单独的HAP中,其他HAP可以到该HAP中访问资源和so文件;
2.2 构建视图
从开发态到编译态,Module中的文件会发生以下变更:
- ets目录:ArkTS源码编译生成.abc文件
- resources目录:AppScope目录下的资源文件会合入到Module下面资源目录中,如果两个目录下存在重名文件,编译打包后只会保留AppScope目录下的资源文件
- module配置文件:AppScope目录下的app.json5文件字段会合入到Module下面的module.json5文件中,编译后生成HAP或HSP最终高的module.json文件
一个开发态的Module编译后生成一个部署态的HAP,Module和HAP一一对应。所有的HAP最终都会编译到一个App Pack中(以.app为后缀的文件),用于发布到应用市场。
2.3 多HAP的开发调试和发布流程
开发阶段:
按照业务逻辑创建多个Module,在各自的Module中完成自身业务的开发。
调试阶段: 通过Studio编译打包,生成单个or多个HAP,就可以基于HAP进行调试。在调试前,需要先安装或更新HAP:
- 使用DevEco Studio进行调试:应用程序包调试方法
- 使用hdc工具(通过HarmonyOS SDK获取,在SDK的toolchains目录下)进行调试
在调试前,需要先安装或更新HAP,此处有两种方式:
- 直接使用hdc安装、更新HAP,HAP的路径为开发平台上的文件路径:
arduino
// 安装、更新,多HAP可以指定多个文件路径
hdc install C:\entry.hap C:\feature.hap
// 执行结果
install bundle successfully.
// 卸载
hdc uninstall com.example.myapplication
// 执行结果
uninstall bundle successfully.
2.先执行hdc shell,再使用bm工具安装、更新HAP:
HAP的文件路径为真机上的文件路径,命令参考如下:
kotlin
// 先执行hdc shell才能使用bm工具
hdc shell
// 安装、更新,多HAP可以指定多个文件路径
bm install -p /data/app/entry.hap /data/app/feature.hap
// 执行结果
install bundle successfully.
// 卸载
bm uninstall -n com.example.myapplication
// 执行结果
uninstall bundle successfully.
完成HAP安装或更新后,即可参考相关调试命令进行调试。
发布阶段 当开发的程序包满足发布要求时,可以在工具中打包编译生成App包。将该App包上架到应用市场云端,应用市场会对上架的App包校验签名,校验签名通过后会将App包中的HAP拆分出来,同时对拆分出的HAP重新添加签名,然后对HAP进行分发。
所以用户设备上下载的是HAP,并不是.app文件。
部署阶段
用户在设备上的应用市场客户端能够看到各种各样的应用,这些应用均由云端分发而来,有些是多HAP应用,有些是单HAP应用。用户选择某个应用后,应用市场将下载应用所包含的全部deliveryWithInstall设置为"true"的HAP。
安装
下载完成后,应用市场客户端再调用系统中包管理服务的安装接口安装下载的HAP,包管理服务以应用为单位将其中所有HAP部署到指定目录下,以完成应用的安装。
2.4 多HAP使用规则
- App Pack包不能直接安装到设备上,只是打包上传到应用市场;
- App Pack包中所有的HAP的配置文件中的bundleName和versionCode标签必须一致;
- App Pack包中同一设备类型的所有HAP中,只能有一个entry类型的HAP;
- App Pack包中的每个HAP必须配置moduleName标签,同一设备类型的所有HAP对应的moduleName标签必须唯一;
- 同一应用的所有HAP签名证书要一致,应用市场分发时会将所有HAP从App Pack中拆分出来,同时对其中的所有HAP进行重签名,这样保证了所有HAP签名证书的一致性。在调试阶段,开发者通过命令行或IDE将HAP安装到设备上时要保证所有HAP签名证书一致,否则会出现安装失败的问题;
3. 应用程序包更新流程
HarmonyOS包管理服务提供了应用程序包更新能力,更新方式如下。
- 应用市场内更新:新版本应用通过应用市场上架后,应用市场通知终端用户该应用有新版本,终端用户可以根据通知到应用市场(客户端)进行应用升级;
- 应用内检测升级:终端用户启动应用时,应用市场检测到该应用有新版本会通知终端用户,可以到应用市场进行应用的下载更新;
4.共享包
鸿蒙OS提供了两种共享包:HAR(Harmony Archive)静态共享包和HSP(Harmony Shared Package)动态共享包。
他们都是为了实现代码和资源的共享,不同之处在于:
- HAR中的代码和资源跟随使用方编译,如果有多个使用方,它们的编译产物中会存在多份相同拷贝;
- HSP中的代码和资源可以独立编译,运行时在一个进程中代码也只会存在一份;
HSP的一些约束:
- HSP和使用方都需要是Stage模型
- HSP和使用方都需要使用esmodule编译模式
- HSP不支持在配置文件中声明abilities、extensionAbilities标签
- HSP按场景可以分为应用内HSP和应用间HSP,后者还不支持
这里目前只了解一下共享包,等之后需要用的时候在补充相关使用记录,有兴趣的可以直接看官方文档:developer.huawei.com/consumer/cn...
5.应用热修复
支持动态发布补丁修复线上版本的问题: developer.huawei.com/consumer/cn...
用的时候再仔细研究。
6.应用配置文件
在基于Stage模型开发的应用项目代码下,都存在一个app.json5及一个或多个module.json5这两种配置文件。
app.json5主要包含以下内容:
- 应用的全局配置信息,包含应用的包名、开发厂商、版本号等基本信息。
- 特定设备类型的配置信息。
module.json5主要包含以下内容:
- Module的基本配置信息,例如Module名称、类型、描述、支持的设备类型等基本信息。
- 应用组件信息,包含UIAbility组件和ExtensionAbility组件的描述信息。
- 应用运行过程中所需的权限信息。
6.1 app.json5配置文件
bash
{
"app": {
"bundleName": "com.application.myapplication",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name",
"description": "$string:description_application",
"minAPIVersion": 9,
"targetAPIVersion": 9,
"apiReleaseType": "Release",
"debug": false,
"car": {
"minAPIVersion": 8,
}
},
}
属性名称 | 描述 |
---|---|
bundleName | 包名,用于标识应用的唯一性。eg. com.example.demo |
bundleType | 类型。app or atomicService(元服务) |
debug | 应用是否可以调试 |
icon | 应用图标 |
label | 应用名称 |
description | 应用的描述信息 |
vendor | 标识对应用开发厂商的描述 |
versionCode | 用于比较两个版本,和Android一样 |
versionName | 四段式:A.B.C.D 主版本号、次版本号、特性版本号、修订版本号 |
minCompatible VersionCode | 标识应用能够兼容的最低历史版本号,用于跨设备兼容性判断(当前不支持) |
minAPIVersion | 标识应用运行需要的SDK的API最小版本,由build-profile中compatibleSdkVersion生成 |
targetAPIVersion | 标识应用运行需要的API目标版本,由build-profile中compileSdkVersion生成 |
apiReleaseType | 标识应用需要的API目标版本的类型,不需要填写,IDE生成 |
multiProjects | 当前工程是否支持多工程联合开发 |
tablet | 可不填,对tablet设备做特殊支持 |
tv | 可不填,对tv设备做特殊支持 |
wearable | 可不填,对wearable设备做特殊支持 |
car | 可不填,对car设备做特殊支持 |
phone | 可不填,对phone设备做特殊支持 |
6.2 module.json5配置文件
bash
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"tv",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"virtualMachine": "ark",
"metadata": [
{
"name": "string",
"value": "string",
"resource": "$profile:distributionFilter_config"
}
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ts",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"ohos.want.action.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.abilitydemo.permission.PROVIDER",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when": "inuse"
}
}
]
}
}
其中module对应的是模块的配置信息,一个模块对应一个打包后的hap包。具体属性如下:
属性名称 | 介绍 |
---|---|
name | 当前Module的名称 |
type | 当前Module的类型。entry or feature |
scrEntry | 当前Module的对应的代码路径,默认值为空 |
description | 当前Module的描述,默认值为空 |
process | 当前Module的进程名,默认值为bundleName |
mainElement | 当前Module的入口UIAbility名称或ExtensionAbility名称,默认值为空 |
deviceTypes | 当前Module支持的设备 |
deliveryWithInstall | 表示该Module对应的HAP是否跟随应用一块安装 |
installationFree | 表示是否支持免安装特性 |
virtualMachine | 当前Module运行的目标虚拟机类型,IDE自动插入 |
pages | 当前Module的profile资源,列举每个页面信息 |
metadata | 当前Module的自定义元信息,默认为空 |
abilities | 当前Module的UIAbility的配置信息,默认为空 |
extensionAbilities | 当前Module的ExtensionAbility的配置信息,默认为空 |
requestPremissions | 当前应用运行时向系统申请的权限集合,默认为空 |
testRunner | 当前Module用于支持对测试框架的配置,默认为空 |
6.1.1 deviceTypes
支持的设备类型:
- 平板 tablet
- 智慧屏 tv
- 智能手表 wearable
- 车机 car
- 手机 phone
json
{
"module": {
"name": "myHapName",
"type": "feature",
"deviceTypes" : [
"tablet"
]
}
}
6.1.2 pages标签
用于指定描述页面信息的配置文件:
kotlin
{
"module": {
// ...
"pages": "$profile:main_pages", // 通过profile下的资源文件配置,main_pages名称可修改
}
}
pages配置文件标签:
- src:描述所有路由信息
- window:定义与窗口显示相关的配置,可为空
- designWidth:页面设计基准宽度,以此为基准,根据实际设备宽度来缩放元素,默认时720px
- autoDesignWidth:页面设计基准宽度是否自动计算,由设备宽度与屏幕密度计算得出,默认为false
6.1.3 metadata标签
标识HAP的自定义元信息,标签为数组,包含name value resource:
- name:名称
- value:数据项的值
- resource:定义用户自定义数据格式,标签值为该数据的资源的索引
bash
{
"module": {
"metadata": [{
"name": "module_metadata",
"value": "a test demo for module metadata",
"resource": "$profile:shortcuts_config",
}],
"abilities": [{
"metadata": [{
"name": "ability_metadata",
"value": "a test demo for ability",
"resource": "$profile:config_file"
},
{
"name": "ability_metadata_2",
"value": "a string test",
"resource": "$profile:config_file"
}],
}],
"extensionAbilities": [{
"metadata": [{
"name": "extensionAbility_metadata",
"value": "a test for extensionAbility",
"resource": "$profile:config_file"
},
{
"name": "extensionAbility_metadata_2",
"value": "a string test",
"resource": "$profile:config_file"
}],
}]
}
}
6.1.4 abilities标签
描述UIAbility组件的配置信息,标签值为数组类型,该标签下的配置只对当前UIAbility生效:
属性名 | 描述 |
---|---|
name | 当前UIAbility组件的名称,整个应用内要唯一 |
srcEntry | 入口UIAbility的代码路径 |
launchType | 启动模式:multiton 每次启动都新建,singleton 单例,specified 运行时由开发者决定 |
description | 描述 |
icon | 图标 |
label | 显示的名称,支持多语言,唯一 |
permissions | 权限信息 |
metadata | 当前UIAbility组件的元信息 |
exported | 是否可以被其他应用调用 |
continuable | 是否可以前一 |
skills | 能否接受Want的特征集 |
backgroundModes | 后台长任务类型 |
startWindowIcon | 当前UIAbility组件启动页面图标资源文件的索引 |
removeMissionAfterTerminate | 标识销毁后是否从任务列表中移除 |
orientation | 组件启动时的方向 |
supportWindowMode | 窗口模式:fullscreen split floating |
priority | 优先级,0 -> 10 |
maxWindowRatio | 支持的最大的窗口宽高比 |
minWindowRatio | 支持的最小的窗口宽高比 |
maxWindowWidth | 支持的最大的窗口高度 vp |
minWindowWidth | 支持的最小的窗口宽度 vp |
maxWindowHeight | 支持的最大的窗口高度 vp |
minWindowHeight | 支持的最小的窗口高度 vp |
excludeFromMissions | 是否在最近任务列表中展示,仅支持系统应用 |
bash
{
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ts",
"launchType":"singleton",
"description": "$string:description_main_ability",
"icon": "$media:icon",
"label": "Login",
"permissions": [],
"metadata": [],
"exported": true,
"continuable": true,
"skills": [{
"actions": ["ohos.want.action.home"],
"entities": ["entity.system.home"],
"uris": []
}],
"backgroundModes": [
"dataTransfer",
"audioPlayback",
"audioRecording",
"location",
"bluetoothInteraction",
"multiDeviceConnection",
"wifiInteraction",
"voip",
"taskKeeping"
],
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:red",
"removeMissionAfterTerminate": true,
"orientation": " ",
"supportWindowMode": ["fullscreen", "split", "floating"],
"maxWindowRatio": 3.5,
"minWindowRatio": 0.5,
"maxWindowWidth": 2560,
"minWindowWidth": 1400,
"maxWindowHeight": 300,
"minWindowHeight": 200,
"excludeFromMissions": false
}]
}