
鸿蒙技术分享:🔥🔥🔥克隆大作战!一份代码如何秒变多个产品?鸿蒙开发多目标产物配置
克隆大作战!一份代码如何做出多个产品包?鸿蒙开发多目标产物配置
日常开发中,因为某些需求(黑的、白的、灰的),我们经常需要将一份项目代码编译成不同的产品安装包。
鸿蒙开发IDE-DevEco是支持多目标产物配置的,因此就进行了一番研究。
需求
首先说一下我们的常用诉求:
- 应用信息差异化:名称、图标、包名、版本号、发布者;
- 应用内容差异化:纯逻辑代码、页面、资源(配置项、图片)、服务卡片、依赖项;
- 工程信息差异化:签名、模块化;
如何实现
DevEco的的多产物配置
DevEco的多目标产物配置能力分为HAP/HAR和APP。
Product多产物
应用级的信息都是在Product产物中配置。
配置文件为:./build-profile.json5
,app-products
{
"name": "Ultimate",
// 签名配置名称
"signingConfig": "Ultimate",
// 包名
"bundleName": "com.example.ultimate.app",
// 应用图标
"icon": "$media:app_icon_ultimate",
// 应用名称
"label": "$string:app_name_ultimate",
// 版本code
"versionCode": 10000,
// 版本号
"versionName": "1.0.0",
// 发布者
"vendor": "Ultimate"
}
资源文件:
.
├── AppScope
│ ├── app.json5
│ ├── resources
│ │ └── base
│ │ ├── element
│ │ │ └── string.json
│ │ └── media
│ │ └── app_icon.png
│ │ └── app_icon_ultimate.png
string.json内容:
{
"string": [
{
"name": "app_name",
"value": "应用"
},
{
"name": "app_name_ultimate",
"value": "应用Ultimate"
}
]
}
Product产物的配置项中包含了名称、图标、包名、版本号、发布者等信息。
你如果按照官方文档-能力说明里的配置进行修改,就会得出上面的配置方法,AppScope-resources-media里有两个图片;string.json里也有两个应用名称。
这种方法运行没有什么问题,但是在最终的打包成果中,无论你构建哪个Product对应的HAP包,resources目录里都会包含两个图标文件。
如果你要配置十几个不同的Product,那这里就有十几个图标文件。这效果显然不是我们想要的。
实际上能力说明文档里对于APP多产物配置的说明不是全部,在DevEco中,./build-profile.json5
,app-products,选中products
跳转到“声明和用法”,我们就能看到它支持的所有配置项。
{
"products": {
"description": "This field is used to describe different product types defined by the openHarmony application. By default, a default product exists and different signature materials can be specified.",
"type": "array",
"items": {
"type": "object",
"oneOf": [
{
"propertyNames": {
"enum": [
"name",
"signingConfig",
"bundleName",
"buildOption",
"runtimeOS",
"compileSdkVersion",
"compatibleSdkVersion",
"compatibleSdkVersionStage",
"targetSdkVersion",
"bundleType",
"label",
"icon",
"versionCode",
"versionName",
"resource",
"output",
"vendor"
]
}
}
]
}
}
}
里面包含了resource
配置,所以我们可以通过多个resource目录进行差异化配置。
{
"name": "Ultimate",
// ultimate版本包名
"bundleName": "com.example.ultimate.app",
// ultimate版本指定资源目录
"resource": {
"directories": [
"./AppScope/resources_ultimate"
]
}
}
资源文件:
.
├── AppScope
│ ├── app.json5
│ ├── resources
│ │ └── base
│ │ ├── element
│ │ │ └── string.json
│ │ └── media
│ │ └── app_icon.png
│ ├── ultimateRes
│ │ └── base
│ │ ├── element
│ │ │ └── string.json
│ │ └── media
│ │ └── app_icon.png
这样每个Product单独打包时,HAP都只会包含自己的图标。
Target多产物
配置文件为:./build-profile.json5
,modules:
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default",
"release"
]
},
{
"name": "ultimate",
"applyToProducts": [
"ultimate"
]
}
]
}
]
配置文件为:./entry/build-profile.json5
,targets:
{
"name": "ultimate",
"source": {
"pages": [
"pages/Index",
"pages/Detail",
],
"sourceRoots": [
"./src/ultimate"
]
},
"resource": {
"directories": [
"./src/main/resources_ultimate",
"./src/main/resources",
]
}
}
页面差异化
ultimate版本包含的独有页面可以通过source-pages
进行配置添加。
也可以通过resource-directories
在resources_ultimate-base-profile
目录下添加main_pages.json
文件进行差异化配置。
公共页面的差异化配置
若某些公共页面上的显示内容有差异化,比如文本或者图片和普通版本不同,可以通过resource-directories
在resources_ultimate
目录下添加同名的media资源或者element数据进行覆盖。
需要注意的是:
请注意,如果target引用的多个资源文件目录下,存在同名的资源,则在构建打包过程中,将按照配置的资源文件目录顺序进行选择。
也就是说在directories
列表中,越靠前优先级越高。
公共页面的差异化逻辑
差异化逻辑所在的ets文件目录通过source-sourceRoots
进行配置添加。
比如同样的一个功能,标准版本是通过网络请求获取数据,ultimate版本是读取本地rawfile,就可以将这部分逻辑封装到同名类中进行差异化配置。
具体操作参照官方文档。
不过有一点需注意的是sourceRoots
和resource
的配置不同,sourceRoots
中的文件是以增量的方式拷贝到主目录下的。
也就是说虽然"sourceRoots": ["./src/ultimate"]
只配了一个目录,但最终打包的代码是ultimate中的代码+main目录的代码。
服务卡片差异化
和页面差异化类似,服务卡片的显示也依赖于配置文件resources-base-profile-form_config.json
。
因此差异化主要是通过修改此配置进行的。
在差异化目录的配置文件中配置了几个卡片,就会显示几个卡片。
模块差异化和依赖差异化
oh-package.json5
中的依赖项并没有多产物的差异化配置能力。
如果你的差异化能力依赖了某个第三方组件,你又不想每个应用都将此组件代码包含进去,那么就需要将此依赖项封装成单独的har包,然后在modules
中进行配置。
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default",
"release"
]
},
{
"name": "ultimate",
"applyToProducts": [
"ultimate"
]
}
]
},
{
"name": "har",
"srcPath": "./har",
"targets": [
{
"name": "default",
"applyToProducts": [
"ultimate"
]
}
]
}
]
不足之处
页面只是差异化了配置,没有差异化实际代码
在页面中的配置,只是修改了当前HAP支持跳转的页面列表,而不支持跳转的页面源代码还是存在的。
那么是否可以将页面代码和配置同时进行差异化呢?
比如将页面代码放到source-sourceRoots
中?
主要的问题还是在于鸿蒙的路由管理方案!
基于页面pages配置的方案不可行,因为页面代码放到sourceRoots
指定的目录后,就无法通过pages/xxxx
来定位了。
那么是否可以不用pages配置呢?有,那就是命名路由,但是entry不支持命名路由。
HAR/HSP支持命名路由,但还有一个问题,命名路由需要先import对应的页面类。而差异化页面的入口一般都是公共页面,没办法显式的import。
至于Navigation页面,如果是路由表方案,同样存在页面路径的问题。其他的方案比较复杂,暂时还没验证。
整体看,无法满足需求。
服务卡片的差异化配置,也没有差异化服务卡片的代码
和上面的页面差异化类似,服务卡片的配置文件中也包含了文件路径src
:
{
"forms": [
{
"name": "Card",
"displayName": "$string:Card_display_name",
"description": "$string:Card_desc",
"src": "./ets/card/pages/Card.ets",
"uiSyntax": "arkts",
}
]
}
虽然我可以将服务卡片的代码挪到sourceRoots
中,但是src
的值是限定死的main
目录的子目录,无法指定到sourceRoots
目录。
因此,虽然在多产物配置中可以隐藏不需要的卡片,但卡片的代码还是会打进包里,更麻烦的是元服务单包有2M的限制,工程大了就很麻烦。
多产物配置涉及列表的地方不是合并操作
核心问题还是在于resource-directories
配置,它在处理同名文件时,只能存在一个,对于内容无法合并。
在常见的产品场景中经常有功能组合的情况,比如产品A包含卡片1,卡片2;产品B包含卡片2,卡片3;产品C包含卡片3;
那现在就要在产品A、B、C对应的form_config.json
文件中完整的配置其需要的卡片信息,有大量的重复配置,后期修改也麻烦。
类似:
// 产品A的form_config.json
{
"forms": [
{
"name": "Card1",
// ...
},
{
"name": "Card2",
// ...
}
]
}
// 产品B的form_config.json
{
"forms": [
{
"name": "Card2",
// ...
},
{
"name": "Card3",
// ...
}
]
}
// 产品C的form_config.json
{
"forms": [
{
"name": "Card3",
// ...
}
]
}
更加高效的方案应该是类似:
// 产品A的Target配置
{
"name": "targetA",
"resource": {
"directories": [
"./AppScope/resources_card1",
"./AppScope/resources_card2",
]
}
}
// 产品B的Target配置
{
"name": "targetA",
"resource": {
"directories": [
"./AppScope/resources_card2",
"./AppScope/resources_card3",
]
}
}
// 产品C的Target配置
{
"name": "targetA",
"resource": {
"directories": [
"./AppScope/resources_card3",
]
}
}
每个服务卡片拥有自己的form_config.json
配置,然后target根据配置列表对同名配置文件的内容进行合并。
页面也有类似的问题,如果我的应用有100个页面,分别包装成5个产品,那最终可能就要有接近500条页面配置...
总结
DevEco目前的多目标产物配置的功能能够满足基础的需求,但是对于差异化细节的考量不足,尤其是对于元服务,包体积影响比较大。
当前的多目标产物配置方案比较散碎,而且基于文件的配置化方案和路由、服务卡片等能力耦合较强,总给人束手束脚的感觉。
更多推荐
所有评论(0)