HAR 用多了为什么包体越来越大?一次 HAR 转 HSP 的工程实践【鸿蒙心迹】

👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀
前言
多模块工程走到一定规模,HAR 的问题就开始浮出水面——不是 HAR 本身设计有缺陷,而是它的工作方式决定了,一旦被多个模块引用,代码就会被分别打进每一个引用方里。这篇文章讲的是把 HAR 转换成 HSP 的过程:为什么转、能不能转、怎么一步步改。
一、多模块引用 HAR 时到底发生了什么?
HAR(Harmony Archive)是静态共享包。根据官方文档的描述:
HAR 中的代码资源在编译时会被打包进引用方的模块包(HAP/HSP)中,引用方多时,会造成多个 HAP/HSP 中存在相同拷贝的代码和资源,包体增大。
假设工程里有三个 HAP(比如 entry、feature_a、feature_b),都依赖了同一个 HAR(比如 common_utils)。编译时,common_utils 的代码会被拷贝三份,分别打进三个 HAP 里。
磁盘占用是一个方面,运行时内存里同样存在多份一样的逻辑,并不会因为"它们来自同一个 HAR"就共享内存。
HSP(Harmony Shared Package)的设计思路正好相反:
HSP 中的代码资源不会被打包进引用方的模块包(HAP/HSP)中,而是独立存在于应用安装目录下,减少代码/资源冗余,节省磁盘空间。多个 HAP/HSP 引用同一个 HSP 时,在运行阶段共享同一份代码和资源,减少内存占用。
这是两种本质上不同的共享模式,不是说 HSP 一定比 HAR 好,而是应用场景不同。
二、HAR 与 HSP 的本质差异
在决定是否要转换之前,先把两者的边界搞清楚。
| 对比维度 | HAR(静态共享包) | HSP(动态共享包) |
|---|---|---|
| 代码打包方式 | 编译时拷贝进引用方 | 独立存在,运行时共享 |
| 支持多 HAP 引用 | 支持,但每个引用方各存一份 | 支持,所有引用方共享同一份 |
| 支持 pages 页面路由 | 不支持 | 支持(API 12 起) |
| 支持 UIAbility | 不支持 | 不支持 |
| 支持应用间共享 | 支持(发布 OHPM 三方库) | 不支持,仅限应用内 |
| $r 引用 AppScope 资源 | 不支持 | API 12 起支持 |
| module.json5 type 字段 | "har" | "shared" |
这里有一个前提条件值得单独说:HSP 仅支持应用内共享,不支持应用间共享。如果你的 HAR 是发布到 OHPM 给其他应用用的三方库,就不适合转成 HSP。HSP 只适用于同一应用的多模块共享场景。
三、官方要求的转换前提
根据官方资料,HAR 转 HSP 需要满足以下条件才能正常工作:
- 必须是应用内共享场景,不能有跨应用引用需求;
- 模块必须使用 Stage 模型,HAR/HSP 都只存在于 Stage 模型工程;
- 引用方必须在同一应用工程内,HSP 不支持被其他应用的 HAP/HSP 引用;
- pages 路由(如有)需要适配,HSP 从 API 12 起才支持 pages,且路由跳转路径格式与 HAP 不同;
- Worker 的 URL 格式不同,HAR 和 HAP/HSP 在创建 Worker 时使用的路径格式有区别,需要特别处理。
四、修改 module.json5
这是最核心的一步。HAR 的 module.json5 中 type 字段是 "har",转成 HSP 需要改为 "shared"。
HAR 原始配置(转换前):
// harModule/src/main/module.json5
{
"module": {
"name": "commonUtils",
"type": "har",
"deviceTypes": [
"phone",
"tablet"
]
}
}
HSP 目标配置(转换后):
// commonUtils/src/main/module.json5
{
"module": {
"name": "commonUtils",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet"
]
}
}
name 字段保持不变,引用方的 oh-package.json5 中的依赖路径也不需要改。关键就是 type 从 "har" 改成 "shared"。
五、增加 pages 配置
如果原有的 HAR 没有页面(纯工具/组件库),这一步可以跳过。
如果需要在 HSP 中使用 pages(API 12 起支持),需要两处配置:
第一步,在 module.json5 中增加 pages 字段:
{
"module": {
"name": "commonUtils",
"type": "shared",
"pages": "$profile:main_pages",
"deviceTypes": [
"phone",
"tablet"
]
}
}
第二步,在 resources/base/profile/main_pages.json 中声明页面:
{
"src": [
"pages/Index"
]
}
HSP 里的页面不能通过 windowStage.loadContent 直接加载,必须通过路由跳转。跳转时路径格式也与普通 HAP 内的页面不同,需要带上 bundle 名和模块名:
import { router } from '@kit.ArkUI';
// 跳转 HSP 中的页面,格式:@bundle:bundleName/moduleName/ets/pages/PageName
router.pushUrl({
url: '@bundle:com.example.myapp/commonUtils/ets/pages/Index'
});
这个路径格式根据官方文档,@bundle: 后面依次是应用的 bundleName、模块名、以及页面的相对路径(从 ets 开始)。
六、调整依赖
引用方的 oh-package.json5 通常不需要修改,只要本地路径引用没有变化:
// entry/oh-package.json5
{
"dependencies": {
"commonUtils": "file:../commonUtils"
}
}
需要关注的是工程级 build-profile.json5。确保 HSP 模块已经被正确列入工程的 modules 列表:
// 工程根目录 build-profile.json5(节选)
{
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [{ "name": "default", "applyToProducts": ["default"] }]
},
{
"name": "commonUtils",
"srcPath": "./commonUtils",
"targets": [{ "name": "default", "applyToProducts": ["default"] }]
}
]
}
HAR 转 HSP 后模块本身还在,srcPath 不需要变,这里通常不用动。
七、检查组件和 Worker 的差异
自定义组件(ArkUI 组件)
HAR 和 HSP 都支持导出自定义组件,通过 Index.ets 对外暴露,引用方的 import 路径也保持一致。这部分转换后一般不需要改动。
Worker
这是容易出问题的地方。根据官方文档,HAR 和 HAP/HSP 创建 Worker 时,URL 格式是不同的:
-
HAP/HSP 中创建 Worker,使用模块名作为路径前缀,例如:
// 在 HSP(模块名 commonUtils)中创建 Worker import { worker } from '@kit.ArkTS'; const myWorker = new worker.ThreadWorker('commonUtils/ets/workers/MyWorker.ts'); -
HAR 中创建 Worker,使用相对路径,例如:
// 在 HAR 中创建 Worker import { worker } from '@kit.ArkTS'; const myWorker = new worker.ThreadWorker('../utils/src/main/ets/workers/MyWorker.ts');
如果原来的 HAR 里有 Worker,转成 HSP 之后,Worker 的 URL 必须从相对路径改成以模块名开头的路径格式。
另外,Worker 文件还需要在模块级 build-profile.json5 的 buildOption/sourceOption/workers 字段中声明:
{
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/workers/MyWorker.ts"
]
}
}
}
这个配置在 HAR 里也需要,通常已经有了,转成 HSP 后保持不变即可。
八、转换后的工程结构
经过上述调整,工程结构的变化主要体现在 module.json5 的内容上,目录结构本身并不需要大改:
myApp/
├── entry/
│ ├── src/main/
│ └── oh-package.json5 // 依赖路径不变
├── commonUtils/ // 原 HAR,现在是 HSP
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── components/ // 自定义组件,不变
│ │ │ └── workers/ // Worker 文件,URL 格式需调整
│ │ ├── resources/
│ │ │ └── base/
│ │ │ └── profile/
│ │ │ └── main_pages.json // 如有 pages,新增
│ │ └── module.json5 // type 改为 "shared",按需加 pages
│ ├── Index.ets // 对外导出接口,不变
│ └── oh-package.json5 // 不变
└── build-profile.json5 // 确认 modules 列表包含 commonUtils
开发经验总结
几个容易理解偏的地方,值得单独关注:
1. HSP 不是 HAR 的升级版,是两种不同场景。 HAR 支持应用间共享(三方库),HSP 只支持应用内共享。如果当前 HAR 是打算发布给其他应用用的,不应该转成 HSP。
2. pages 支持从 API 12 起生效。 如果目标设备的 API Level 低于 12,HSP 里的 pages 配置是无效的。在转换前需要确认工程的 compatibleSdkVersion。
3. Worker URL 格式是最容易漏掉的。 HAR 用相对路径,HSP/HAP 用模块名前缀,两者不可混用。如果转换后 Worker 报找不到文件,优先检查这里。
4. (r 引用 AppScope 资源的限制。 原 HAR 如果有 \)r('app.string.xxx') 这类引用 AppScope 资源的代码,转成 HSP 后在 API 12 以下版本同样会失败,API 12 起才支持。
5. HSP 不能被其他应用依赖。 这是在架构设计时需要确认的边界,一旦有跨应用共享需求,只能保持 HAR。
转换本身并不复杂,改动集中在 module.json5 的 type 字段、pages 配置以及 Worker 的 URL。真正需要在改之前想清楚的,是这个共享模块的定位:它只服务于当前应用的多个模块,还是将来有可能对外发布?这个问题定了,选 HAR 还是 HSP 就很自然了。
如果你的工程里有多个 HAR 被多个 HAP 同时引用,可以对比一下转换前后的编译产物大小,观察实际包体变化。包体减少的幅度取决于被引用的 HAR 代码量以及引用方的数量,引用方越多、HAR 越大,收益越明显。
📝 写在最后
如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!
我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!
感谢你的阅读,我们下篇文章再见~👋
✍️ 作者:菜鸟不学编程
🧵 本文原创,转载请注明出处。
更多推荐



所有评论(0)