HarmonyOS7 动态加载:HSP 共享包减小 App 体积的秘密

前言
前几天运营找我,说 App 包体积超了应用市场的限制。一看,好家伙,3 个业务模块各引了一份网络库,光重复代码就占了 12MB。
同一个东西装了三遍,这不是浪费是什么?
HSP(Harmony Shared Package)就是来解决这个问题的。跟 HAR 不同,HSP 在运行时只有一份副本,多个模块共享。今天聊聊 HSP 怎么用,什么时候该用。
HarmonyOS7 提供了两种共享包:HAR 是静态的,编译时拷贝到每个使用方;HSP 是动态的,运行时只加载一份。如果你的 App 有多个模块引用同一套代码,HSP 能帮你砍掉重复体积。
我之前一个项目把网络库从 HAR 换成 HSP,包体积直接减少了 8MB。效果就是这么立竿见影。
HSP 解决了什么问题
先看一个真实场景:
App/
├── entry/ → 引用 common_net HAR(4MB)
├── feature_a/ → 引用 common_net HAR(4MB)
└── feature_b/ → 引用 common_net HAR(4MB)
总计:12MB 重复代码
HAR 是编译时拷贝,3 个模块各拷一份,同一个库在 App 里存在了 3 次。
换成 HSP 后:
App/
├── entry/ → 引用 common_net HSP
├── feature_a/ → 引用 common_net HSP
├── feature_b/ → 引用 common_net HSP
└── common_net/ → HSP 模块,运行时只加载一份(4MB)
总计:4MB,省了 8MB

HSP 的核心价值:运行时共享,一份代码多个模块用。
创建 HSP 模块
创建步骤跟 HAR 类似,但选的类型不同。
步骤:
- 右键项目根目录 → New → Module
- 选择 Shared Shared Library(HSP)
- 填写模块名,比如
common_net - 点击 Finish
确认 build-profile.json5:
// common_net/build-profile.json5
{
"apiType": "StageMode",
"buildOption": {
"arkOptions": {
"staticBuild": false // false 表示动态构建(HSP)
}
}
}

注意 staticBuild: false,这是 HSP 和 HAR 的关键区别。HAR 是 true,HSP 是 false。
HSP 模块的 oh-package.json5:
{
"name": "common_net",
"version": "1.0.0",
"description": "网络请求共享库",
"main": "Index.ets"
}
导出与引用
HSP 的导出机制跟 HAR 一样,通过 Index.ets 控制。
导出 API:
// common_net/src/main/ets/common_net/Index.ets
export { HttpClient } from './HttpClient'
export { ApiResponse } from './model/ApiResponse'
export type { RequestConfig } from './model/RequestConfig'
来看看 HttpClient 的实现:
// common_net/src/main/ets/common_net/HttpClient.ets
import { http } from '@kit.NetworkKit'
import { RequestConfig } from './model/RequestConfig'
import { ApiResponse } from './model/ApiResponse'
export class HttpClient {
private static instance: HttpClient | null = null
// 单例模式,确保运行时只有一个实例
static getInstance(): HttpClient {
if (!HttpClient.instance) {
HttpClient.instance = new HttpClient()
}
return HttpClient.instance
}
async request<T>(config: RequestConfig): Promise<ApiResponse<T>> {
const httpRequest = http.createHttp()
try {
const response = await httpRequest.request(config.url, {
method: config.method,
header: config.headers,
extraData: config.data
})
return JSON.parse(response.result as string) as ApiResponse<T>
} finally {
httpRequest.destroy()
}
}
async get<T>(url: string): Promise<ApiResponse<T>> {
return this.request<T>({ url, method: http.RequestMethod.GET })
}
async post<T>(url: string, data: object): Promise<ApiResponse<T>> {
return this.request<T>({ url, method: http.RequestMethod.POST, data })
}
}
HttpClient 设计成单例是有讲究的——HSP 是运行时共享的,如果每个引用方都 new 一个实例,就失去了共享的意义。单例保证了整个 App 只有一个 HTTP 客户端,连接池、拦截器等资源也能统一管理。
在其他模块中引用:
// entry/oh-package.json5
{
"dependencies": {
"common_net": "file:../common_net"
}
}
// entry/src/main/ets/pages/Index.ets
import { HttpClient } from 'common_net'
@Entry
@Component
struct IndexPage {
async aboutToAppear() {
const client = HttpClient.getInstance()
const data = await client.get('/api/home')
// 处理数据...
}
}
跟 HAR 的引用方式一模一样,但底层的加载机制完全不同——HAR 是编译时拷贝代码进来,HSP 是运行时加载共享的 .so 文件。
运行时加载
HSP 支持运行时按需加载,这是 HAR 做不到的。
动态导入 HSP:
import { abilityLoader } from '@kit.AbilityKit'
async function loadFeatureModule() {
try {
// 动态加载 HSP 模块
const module = await abilityLoader.loadModule('common_net/Index')
// 获取导出的类
const { HttpClient } = module
// 正常使用
const client = HttpClient.getInstance()
const data = await client.get('/api/data')
console.info('数据加载成功')
} catch (err) {
console.error(`模块加载失败: ${err.message}`)
}
}
这段代码的关键在于 loadModule: 它在运行时才去加载 HSP 模块,不是编译时就塞进 App 里。适合这些场景:
- 功能模块按需加载,首屏只加载核心功能
- 插件化架构,运行时决定加载哪些模块
- 减少首屏启动时间,非核心模块延迟加载
敲黑板:动态加载有额外的运行时开销,不要所有模块都用动态加载。核心模块静态引入,非核心模块动态加载,这才是正确姿势。
HSP 与 HAR 选择
到底用 HAR 还是 HSP?别纠结,看这张决策表:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 项目内部模块化 | HAR | 简单直接,编译时优化更好 |
| 多模块引用同一库 | HSP | 避免重复拷贝,减小包体积 |
| 跨 App 共享代码 | HSP | 多个 App 运行时共享一份 |
| 工具函数、小体积库 | HAR | 体积不大,没必要用 HSP |
| 网络/图片等大型基础库 | HSP | 体积大,共享收益明显 |
| 需要运行时按需加载 | HSP | HAR 不支持动态加载 |
| 纯 UI 组件库 | HAR | 组件库通常不大,HAR 更方便 |
我的经验: 体积超过 2MB 的共享库用 HSP,小的用 HAR。别为了用 HSP 而用 HSP,HSP 的运行时加载机制会带来一点性能开销。
注意事项
用 HSP 有几个坑得提前知道:
1. HSP 模块必须在 App 内
HSP 不能独立发布和安装,它必须跟 App 一起打包。如果你需要一个独立分发的模块,那不是 HSP 能解决的问题。
2. HSP 不支持多进程共享
HSP 在同一进程内共享,多进程场景下每个进程各加载一份,共享效果就没了。
3. 调试比 HAR 复杂
HSP 是运行时加载的,断点调试和堆栈追踪比 HAR 稍微麻烦一些。DevEco Studio 对 HSP 的调试支持在持续优化,但体验上还是 HAR 更丝滑。
4. 版本一致性
同一 App 内所有模块引用的 HSP 版本必须一致。如果 entry 引了 v1.0,feature_a 引了 v1.1,编译时会报错。
写在最后
HSP 的核心价值就一个词:共享。多个模块用同一套代码,运行时只加载一份,包体积和内存都省了。
但 HSP 不是万能药。小体积的模块用 HAR 更简单,别为了省几百 KB 折腾 HSP。选型的核心标准:体积大、引用多、跨模块共享的库用 HSP,其余用 HAR。
更多推荐



所有评论(0)