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

HAP / HAR / HSP 到底啥区别?顺带把「导入」那点疑惑讲清楚 HAP / HAR / HSP 到底啥区别顺带把「导入」那点疑惑讲清楚我把 demo 拆成了三个模块entry、common、library。建模块时 DevEco 让我在HAP / HAR / HSP里选当时就一脸懵——这仨名字跟绕口令似的。拆完跑起来疑问更多了导入的包怎么「用完就没了」动态共享包说「不占大小」那它到底加不加载ESObject又是个啥……这篇用一座图书馆的比方把三者一次讲明白再把我那几个疑问逐个解决。一、先一句话定位三兄弟把「开发一个鸿蒙应用」想象成「经营一座图书馆」HAP 能上架借阅的书。真正能被安装、能跑起来的「成品」就是它。你的 App 本体就是一个或几个HAP。HAR 复印的资料。要用就复印一份、夹进你的书里。复印完它就成了书的一部分。HSP 一本公共参考书。整个图书馆就一本摆在中央谁要查谁去翻大家共用。记住这三个画面下面全是展开。二、HAR静态共享包 复印夹进书里HAR 是编译期就被「复印」进使用方的编译时它的代码和资源被打进依赖它的那个 HAP 里。你的疑问「导入包是直接导入之后就没有」——对的。HAR 在编译完成那一刻就「融」进了使用它的 HAP运行时没有一个独立的 HAR 存在了它已经是宿主的一部分。就像复印件夹进书里书印好后你找不到那张「独立的复印件」了。这带来两个代价重复占体积。一份 100KB 的工具 HAR如果被 5 个 HAP 都引用就被复印 5 份 → 多占 ~500KB。单例会各算各的。每个副本有自己的一份静态变量所以「全局单例」在多个副本里其实是好几个实例不是同一个。这正是上一步我们踩过的坑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 是唯一能被安装、能运行的包。一个应用至少有一个entryHAP复杂应用可以再拆出若干featureHAP。HAR 和 HSP 都不能单独安装/运行它们是给 HAP 用的「料」。五、并排对比HAPHAR静态共享HSP动态共享比喻能借阅的书复印夹进书里公共参考书能否独立安装/运行✅ 能❌ 不能❌ 不能何时「进」使用方——编译期打进去运行时加载被多处用会重复占体积吗——会各复印一份不会全应用一份单例——各副本各一个全应用共享一个能放 UI/页面✅✅✅你的 demoentrycommon网络底层library动态加载的功能六、顺带把「导入」和 ESObject 说清楚你的疑问「ESObject 是模块的数据类型」不是。ESObject是 ArkTS 给「编译期说不清类型的值」准备的一个类型逃生舱。动态import(library)拿回来的模块编译器静态不知道它长什么样它是运行时才加载的所以只能先用ESObject接住再动态地访问它的方法。它不是「模块专属类型」——凡是动态来的、互操作来的、类型不确定的值都可能是ESObject。一句话ESObject≈「我暂时不知道你具体是啥但先让我能用」。代价是这块没有静态类型检查所以能不用就不用。你的疑问「为什么导入动态共享包加载一次就不用再加载了」因为模块缓存一个模块被求值一次后就进缓存之后无论再import多少次拿到的都是同一个缓存实例——这就是你看到「加载时刻 HH:mm:ss 一直不变」的原因。HSP 更进一步全应用只有一份实例。所以「加载一次就够」是 ES 模块和 HSP 单实例的本性不是哪里特别配置出来的。demo 里我们还顺手加了一层「显式缓存」首次import(library)把结果存进字段之后直接复用、连import()都不再调——这是更地道的写法。但底层就算你每次都调import()模块也不会重新加载道理就在这。七、那到底怎么选只有一个模块会用→HAR简单编译进去就完事。多个 HAP/模块共用或想去重省体积或要共享单例/UI→HSP。一个经验能不拆就别乱拆。拆模块要付依赖管理、版本对齐、编译变慢的成本别为了「看起来专业」而过度模块化。对上你的 demo网络底层只被entry用、且要静态直连 → 放common(HAR)正合适library想演示「按需动态加载的独立分包」→ 用HSP正好。一句话总结HAP 是能跑的成品书HAR 是复印件——编译期夹进谁就成了谁的一部分被多处用会重复占体积、单例还各算各的HSP 是全馆唯一的公共参考书——运行时加载一次、大家共享「不占大小」是说不重复复印、不是不加载。至于ESObject它不是模块类型而是「编译期说不清类型」的逃生舱「加载一次就够」则是模块缓存的本性。参考建议对着官方再过一遍应用程序包概述 / Stage 模型应用程序包结构HarmonyOS 应用包类型详解 —— HAP、HAR、HSP如何理解 HAP、HAR、HSP 三者之间的关系动态加载dynamic import- ArkTS模块化