HAP / HAR / HSP 到底啥区别?顺带把「导入」那点疑惑讲清楚

HAP / HAR / HSP 到底啥区别?顺带把「导入」那点疑惑讲清楚

我把 demo 拆成了三个模块:entrycommonlibrary。建模块时 DevEco 让我在 HAP / HAR / HSP 里选,当时就一脸懵------这仨名字跟绕口令似的。拆完跑起来疑问更多了:导入的包怎么「用完就没了」?动态共享包说「不占大小」,那它到底加不加载?ESObject 又是个啥......

这篇用一座图书馆的比方,把三者一次讲明白,再把我那几个疑问逐个解决。

一、先一句话定位三兄弟

把「开发一个鸿蒙应用」想象成「经营一座图书馆」:

  • HAP = 能上架借阅的书。 真正能被安装、能跑起来的「成品」就是它。你的 App 本体就是一个(或几个)HAP。
  • HAR = 复印的资料。 要用就复印一份、夹进你的书里。复印完,它就成了书的一部分。
  • HSP = 一本公共参考书。 整个图书馆就一本,摆在中央,谁要查谁去翻,大家共用。

记住这三个画面,下面全是展开。

二、HAR(静态共享包)= 复印夹进书里

HAR 是编译期 就被「复印」进使用方的:编译时,它的代码和资源被打进依赖它的那个 HAP 里。

👉 你的疑问:「导入包是直接导入之后就没有」------对的。 HAR 在编译完成那一刻就「融」进了使用它的 HAP,运行时没有一个独立的 HAR 存在了,它已经是宿主的一部分。就像复印件夹进书里,书印好后你找不到那张「独立的复印件」了。

这带来两个代价:

  1. 重复占体积。 一份 100KB 的工具 HAR,如果被 5 个 HAP 都引用,就被复印 5 份 → 多占 ~500KB。
  2. 单例会各算各的。 每个副本有自己的一份静态变量,所以「全局单例」在多个副本里其实是好几个实例,不是同一个 。 这正是上一步我们踩过的坑:HSP 里的模块拿不到 entry 里那个 LoadLog 单例------因为 HAR 被不同模块各复印一份,单例就裂开了。

👉 你的疑问:「静态共享包只是把不同功能封进不同模块,可以独立开发不同技术栈吗?」 前半句对:HAR 就是用来封装、复用、解耦 的,不同团队/仓库各自维护各自的 HAR、按版本引用,能独立开发。但「不同技术栈」是个误会 ------鸿蒙这些包都是同一套 ArkTS / ArkUI ,不存在「这个模块用 React、那个用 Vue」那种技术栈隔离。模块化解决的是「谁负责哪块、怎么复用 」,不是「换语言换框架」。(HAR 里可以带 native 的 .so,但应用层逻辑还是 ArkTS。)

三、HSP(动态共享包)= 一本公共参考书

HSP 不复印。整个应用里它只有一份 ,运行时加载,多个 HAP 共享同一个实例

👉 你的疑问:「动态共享包不占大小,是指不加载吗?」不是! 「不占大小」说的是:它不会被重复复印 进每个 HAP(去掉重复 = 省体积),不是「零体积」,更不是「不加载」

公共参考书的画面正好:全馆就一本 (所以不重复占地方),但你要用它,还是得走过去翻开 (要加载)。HSP 跟着应用一起安装,运行时按需加载一次,之后大家共用这一份。

附带好处:因为是唯一实例,单例能在各 HAP 间共享 ;而且 HSP 能放 UI / 页面(HAR 也能放 UI,但「又要共享 UI、又想省体积」时 HSP 更合适)。

四、HAP = 那本能真正上架的书

HAP 是唯一能被安装、能运行 的包。一个应用至少有一个 entry HAP,复杂应用可以再拆出若干 feature HAP。HAR 和 HSP 都不能单独安装/运行,它们是给 HAP 用的「料」。

五、并排对比

HAP HAR(静态共享) HSP(动态共享)
比喻 能借阅的书 复印夹进书里 公共参考书
能否独立安装/运行 ✅ 能 ❌ 不能 ❌ 不能
何时「进」使用方 ------ 编译期打进去 运行时加载
被多处用会重复占体积吗 ------ (各复印一份) 不会(全应用一份)
单例 ------ 各副本各一个 全应用共享一个
能放 UI/页面
你的 demo entry common(网络底层) library(动态加载的功能)

六、顺带把「导入」和 ESObject 说清楚

👉 你的疑问:「ESObject 是模块的数据类型???」不是。

ESObject 是 ArkTS 给「编译期说不清类型的值 」准备的一个类型逃生舱 。动态 import('library') 拿回来的模块,编译器静态不知道它长什么样 (它是运行时才加载的),所以只能先用 ESObject 接住,再动态地访问它的方法。

不是「模块专属类型」 ------凡是动态来的、互操作来的、类型不确定的值,都可能是 ESObject。一句话:ESObject ≈「我暂时不知道你具体是啥,但先让我能用」。 代价是这块没有静态类型检查,所以能不用就不用。

👉 你的疑问:「为什么导入动态共享包,加载一次就不用再加载了?」

因为模块缓存 :一个模块被求值一次 后就进缓存,之后无论再 import 多少次,拿到的都是同一个缓存实例 ------这就是你看到「加载时刻 HH:mm:ss 一直不变」的原因。HSP 更进一步,全应用只有一份实例。所以「加载一次就够」是 ES 模块(和 HSP 单实例)的本性,不是哪里特别配置出来的。

demo 里我们还顺手加了一层「显式缓存」:首次 import('library') 把结果存进字段,之后直接复用、连 import() 都不再调------这是更地道的写法。但底层就算你每次都调 import(),模块也不会重新加载,道理就在这。

七、那到底怎么选?

  • 只有一个模块会用HAR(简单,编译进去就完事)。
  • 多个 HAP/模块共用,或想去重省体积,或要共享单例/UIHSP
  • 一个经验:能不拆就别乱拆。拆模块要付依赖管理、版本对齐、编译变慢的成本,别为了「看起来专业」而过度模块化。

对上你的 demo:网络底层只被 entry 用、且要静态直连 → 放 common(HAR) 正合适;library 想演示「按需动态加载的独立分包」→ 用 HSP 正好。

一句话总结

HAP 是能跑的成品书;HAR 是复印件------编译期夹进谁就成了谁的一部分,被多处用会重复占体积、单例还各算各的;HSP 是全馆唯一的公共参考书------运行时加载一次、大家共享,「不占大小」是说不重复复印、不是不加载。 至于 ESObject,它不是模块类型,而是「编译期说不清类型」的逃生舱;「加载一次就够」则是模块缓存的本性。


参考(建议对着官方再过一遍)

相关推荐
基德爆肝c语言1 小时前
MySQL表的操作
前端·数据库·mysql
秃头网友小李1 小时前
前端难点:Element Plus 样式覆盖 —— :deep()、CSS 变量与滚动状态类名
前端·vue.js
the_answer1 小时前
XSS 与 CSRF 深度解析
前端
打呵欠的猫1 小时前
AI 生成的代码你敢直接上线吗?我总结出 3 条铁律
前端·ai编程
极速蜗牛1 小时前
我在 Taro 小程序项目里实践的 API First + AI 编程方式
前端·人工智能·后端
锋行天下2 小时前
数据库安全并发控制详解:乐观锁 vs 悲观锁 vs 原子操作
前端·数据库·后端
饼饼饼3 小时前
React19 新手指南:JSX 没那么难,用好这几条规则就够了
前端·javascript·react.js
想吃火锅10053 小时前
【前端手撕】new
前端
小小小小宇3 小时前
AI大背景下端到端界面测试
前端