HarmonyOS 6学习:模块化开发中HAR、HSP、HAP的选择与实践指南
HAP是基础:每个应用必须有entry HAP,feature HAP实现模块化HSP优于HAR:应用内共享优先使用HSP,避免资源重复按需加载:非核心功能使用feature HAP,按需安装合理拆分:按业务域拆分模块,保持高内聚低耦合
熟悉我们之前项目的读者一定还记得,在做AI旅行助手时,随着功能不断扩展,项目逐渐变得庞大复杂。最初只有一个entry模块负责所有功能,但随着需求增加,我们逐渐加入了地图模块、社交分享模块、PDF处理模块等。很快,代码量从最初的几千行增长到几万行,编译时间越来越长,团队协作也出现了频繁的代码冲突。
这时候,模块化开发就变得尤为重要。但在HarmonyOS开发中,我们面临三个选择:HAR、HSP、HAP。它们看起来相似,但实际应用场景和效果却有天壤之别。今天我们就来彻底搞懂这三者的区别,以及在实际项目中如何做出正确选择。
一、 从实际问题出发:为什么要模块化?
在开始技术细节之前,先看一个真实场景。我们的AI旅行助手最初架构是这样的:
AI旅行助手(单HAP)
├── 核心AI功能
├── 地图模块
├── 社交分享模块
├── PDF处理模块
├── 用户管理模块
└── 数据缓存模块
随着功能增加,出现了几个明显问题:
-
编译速度慢:任何小改动都要重新编译整个应用
-
代码耦合度高:模块间直接引用,改一处可能影响多处
-
包体积过大:即使用户只用到部分功能,也要下载完整应用
-
团队协作冲突:多人同时修改同一模块,合并代码困难
这时候,模块化重构就提上了日程。但问题来了:该用HAR、HSP还是HAP?
二、 核心概念详解:HAR、HSP、HAP到底是什么?
2.1 HAP:应用安装和运行的基本单元
HAP(Harmony Ability Package) 是应用安装和运行的基本单元,可以理解为Android中的APK模块。每个HAP包含代码、资源、第三方库、配置文件等。
// 典型的HAP结构
entry/
├── src/
│ ├── main/
│ │ ├── ets/ // ArkTS代码
│ │ ├── resources/ // 资源文件
│ │ └── module.json5 // 模块配置文件
│ └── resources/
├── oh-package.json5 // 依赖配置
└── build-profile.json5 // 构建配置
HAP分为两种类型:
-
entry:入口模块,必须存在,包含应用的主入口
-
feature:功能模块,可选,实现特定功能
// module.json5 - entry类型配置
{
"module": {
"name": "entry",
"type": "entry", // entry类型
"description": "$string:module_entry_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages"
}
}
// module.json5 - feature类型配置
{
"module": {
"name": "map_feature",
"type": "feature", // feature类型
"description": "$string:module_map_desc",
"deviceTypes": [
"phone"
],
"deliveryWithInstall": false, // 可按需安装
"installationFree": true,
"pages": "$profile:map_pages"
}
}
2.2 HAR:静态共享包
HAR(Harmony Archive) 是静态共享包,主要用于代码和资源的复用。可以把它理解为HarmonyOS版的npm包或Maven依赖。
// 创建和使用HAR的完整流程
@Entry
@Component
struct MainPage {
// 引入HAR中的组件
@Builder
BuildComponentFromHar() {
// 这里引入HAR中导出的组件
Column() {
// 假设CommonButton是从common_ui.har导入的组件
CommonButton({ text: '从HAR导入的按钮' })
.onClick(() => {
// 调用HAR中的工具函数
const utils = new CommonUtils();
utils.showToast('来自HAR的功能');
})
}
}
build() {
Column() {
this.BuildComponentFromHar()
}
}
}
HAR的核心特点:
-
编译时复制:HAR中的代码在编译时会被复制到每个引用它的模块中
-
可跨应用共享:可以发布到OHPM(OpenHarmony包管理器)中心仓
-
版本独立:每个模块引用的是独立的HAR副本
2.3 HSP:动态共享包
HSP(Harmony Shared Package) 是动态共享包,这是HarmonyOS特有的模块化方案,解决了HAR的资源重复问题。
// HSP的典型使用场景
@Entry
@Component
struct TravelApp {
// 动态加载HSP模块
async loadMapModule() {
try {
// 检查HSP是否已安装
const isInstalled = await this.checkHspInstalled('com.example.mapmodule');
if (!isInstalled) {
// 动态下载并安装HSP
await this.downloadAndInstallHsp('https://example.com/map.hsp');
}
// 加载HSP中的Ability
const context = getContext(this) as common.UIAbilityContext;
let want: Want = {
deviceId: '', // 空字符串表示本设备
bundleName: 'com.example.travel',
abilityName: 'MapAbility',
moduleName: 'map_hsp' // 指定HSP模块名
};
context.startAbility(want);
} catch (error) {
console.error('加载地图模块失败:', error);
}
}
build() {
Column() {
Button('查看地图')
.onClick(() => this.loadMapModule())
}
}
}
HSP的核心特点:
-
运行时共享:多个模块共享同一份HSP代码
-
节省空间:不会重复打包,有效控制应用体积
-
同进程运行:与宿主应用共享进程和生命周期
三、 详细对比:如何选择?
3.1 核心差异对比
|
特性 |
HAP |
HAR |
HSP |
|---|---|---|---|
|
定义 |
应用安装运行单元 |
静态共享包 |
动态共享包 |
|
跨应用共享 |
不可共享 |
✅ 支持 |
❌ 仅限应用内 |
|
资源复用方式 |
不涉及 |
编译时复制 |
运行时共享 |
|
多模块引用影响 |
不涉及 |
多份拷贝,体积增大 |
单份共享,节省空间 |
|
发布方式 |
打包为应用 |
OHPM中心仓/私仓 |
跟随宿主应用发布 |
|
生命周期 |
独立/可配置 |
编译时确定 |
与宿主应用相同 |
3.2 实际选择指南
基于我们的AI旅行助手项目,这里提供具体的选择建议:
场景1:通用UI组件库
// 情况:多个模块都需要使用相同的按钮、卡片、弹窗等UI组件
// ❌ 错误做法:每个模块都复制一份相同的组件代码
// ✅ 正确选择:使用HSP
// 理由:HSP确保多个模块共享同一份UI组件代码
// 优势:
// 1. 一处修改,多处生效
// 2. 不会增加包体积
// 3. 样式和交互保持一致
场景2:工具类和工具函数
// 情况:网络请求、图片处理、数据转换等工具函数
// 选择依据:
// - 如果只在当前应用内使用:HSP ✅
// - 如果要给其他应用使用:HAR ✅
// 网络请求工具 - 使用HSP
class NetworkUtils {
// 这个工具只在旅行助手应用内使用
static async requestTravelData(url: string) {
// 实现...
}
}
// 图片处理工具 - 使用HAR
class ImageProcessor {
// 这个工具可能被多个不同应用使用
static compressImage(imageData: Uint8Array): Uint8Array {
// 实现...
}
}
场景3:业务功能模块
// 情况:地图模块、社交分享模块、PDF处理模块
// 选择:将这些模块拆分为独立的feature HAP
// 优势:
// 1. 可独立开发、测试
// 2. 可按需安装,减少初始包体积
// 3. 独立团队并行开发
四、 实战案例:AI旅行助手的模块化重构
4.1 重构前:单一HAP架构
// 重构前 - 所有代码在一个HAP中
AI旅行助手(entry HAP,50MB)
├── src/main/ets/
│ ├── MainAbility.ets // 主入口
│ ├── pages/
│ │ ├── HomePage.ets // 首页
│ │ ├── AIChatPage.ets // AI对话
│ │ ├── MapPage.ets // 地图功能 - 3000行代码
│ │ ├── SocialPage.ets // 社交分享 - 2000行代码
│ │ └── PdfPage.ets // PDF处理 - 2500行代码
│ ├── common/
│ │ ├── components/ // 公共组件 - 重复定义
│ │ ├── utils/ // 工具函数 - 各处拷贝
│ │ └── constants/ // 常量定义
│ └── ...
存在的问题:
-
地图、社交、PDF模块代码混杂,耦合严重
-
公共组件在多个页面重复定义
-
编译一次需要3分钟
-
安装包体积50MB,用户下载慢
4.2 重构后:模块化架构
// 重构后 - 模块化架构
AI旅行助手(主包 15MB + 动态模块)
├── entry HAP(5MB)- 主入口
├── map_feature HAP(8MB)- 地图模块
├── social_feature HAP(3MB)- 社交分享
├── pdf_feature HAP(4MB)- PDF处理
├── common_ui HSP(2MB)- 公共UI组件
├── network_utils HSP(1MB)- 网络工具
└── data_manager HSP(3MB)- 数据管理
具体实现步骤:
步骤1:创建公共UI组件HSP
// common_ui模块的oh-package.json5
{
"name": "@travel/common-ui",
"version": "1.0.0",
"description": "AI旅行助手公共UI组件库",
"types": "./index.d.ts",
"main": "./src/main/ets/index.ets",
"dependencies": {
// HSP特有的依赖配置
}
}
// 定义可共享的组件
@Component
export struct TravelButton {
@Prop text: string = '';
@Prop type: 'primary' | 'secondary' | 'outline' = 'primary';
@Prop loading: boolean = false;
@Emit onClick: () => void = () => {};
build() {
Button(this.text)
.width('100%')
.height(44)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor(this.getBackgroundColor())
.fontColor(this.getFontColor())
.border({
width: this.type === 'outline' ? 1 : 0,
color: '#1890FF'
})
.borderRadius(8)
.enabled(!this.loading)
.onClick(() => {
if (!this.loading) {
this.onClick();
}
})
}
private getBackgroundColor(): ResourceColor {
switch (this.type) {
case 'primary': return '#1890FF';
case 'secondary': return '#F5F5F5';
case 'outline': return Color.White;
default: return '#1890FF';
}
}
}
步骤2:创建工具类HSP
// network_utils HSP - 网络请求工具
export class TravelApiClient {
private static instance: TravelApiClient;
private baseURL: string = 'https://api.travel-assistant.com';
private token: string = '';
static getInstance(): TravelApiClient {
if (!TravelApiClient.instance) {
TravelApiClient.instance = new TravelApiClient();
}
return TravelApiClient.instance;
}
async request<T>(endpoint: string, options?: RequestOptions): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
...options?.headers
};
try {
const response = await fetch(url, {
method: options?.method || 'GET',
headers,
body: options?.body ? JSON.stringify(options.body) : undefined
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json() as T;
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// 设置认证token
setToken(token: string): void {
this.token = token;
}
}
步骤3:创建功能模块feature HAP
// map_feature模块的module.json5
{
"module": {
"name": "map_feature",
"type": "feature", // 注意这里是feature类型
"description": "$string:module_map_desc",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": false, // 不随主包安装
"installationFree": true, // 支持免安装
"pages": "$profile:map_pages",
"abilities": [
{
"name": "MapAbility",
"srcEntry": "./ets/MapAbility.ets",
"description": "$string:map_ability_desc",
"icon": "$media:icon",
"label": "$string:map_ability_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"visible": true,
"skills": [
{
"actions": [
"action.system.detail"
],
"entities": [
"entity.system.detail"
]
}
]
}
]
}
}
步骤4:配置模块依赖
// entry模块的oh-package.json5
{
"dependencies": {
// 依赖common_ui HSP
"@travel/common-ui": "file:../common_ui",
// 依赖network_utils HSP
"@travel/network-utils": "file:../network_utils"
}
}
// map_feature模块的oh-package.json5
{
"dependencies": {
// 同样依赖这两个HSP
"@travel/common-ui": "file:../common_ui",
"@travel/network-utils": "file:../network_utils",
// 可能还有地图相关的特定依赖
"@ohos/maps": "^1.0.0"
}
}
五、 常见问题与解决方案
5.1 HAR转HSP的常见问题
问题:HAR转HSP后编译报错
// 错误示例:HAR中的单例模式在HSP中失效
// HAR中的工具类
export class ConfigManager {
private static instance: ConfigManager;
private config: any = {};
private constructor() {}
static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
setConfig(key: string, value: any): void {
this.config[key] = value;
}
getConfig(key: string): any {
return this.config[key];
}
}
// 在HSP中使用时的问题
// 模块A中设置值
ConfigManager.getInstance().setConfig('theme', 'dark');
// 模块B中获取值 - 这里获取到的是默认值,不是dark!
// 因为每个模块加载的是不同的HAR副本
const theme = ConfigManager.getInstance().getConfig('theme'); // undefined
解决方案:
// 方案1:使用HSP替代HAR
// 将ConfigManager改为HSP,确保单例真正共享
// 方案2:使用持久化存储替代内存共享
import { preferences } from '@kit.PreferencesKit';
export class SharedConfigManager {
private static prefStore: preferences.Preferences | null = null;
static async initialize(): Promise<void> {
this.prefStore = await preferences.getPreferences(
getContext(),
'shared_config'
);
}
static async setConfig(key: string, value: string): Promise<void> {
if (!this.prefStore) await this.initialize();
await this.prefStore.put(this.prefStore, key, value);
await this.prefStore.flush(this.prefStore);
}
static async getConfig(key: string): Promise<string> {
if (!this.prefStore) await this.initialize();
return await this.prefStore.get(this.prefStore, key, '');
}
}
5.2 多HSP引用同一个HAR的问题
问题:多HSP引用同一个HAR,在A HSP中初始化的值,在B HSP中获取不到。
// 错误:HAR被多个HSP引用
project/
├── common_har/ // HAR包
├── feature_a_hsp/ // 引用common_har
└── feature_b_hsp/ // 也引用common_har
// 结果:feature_a_hsp和feature_b_hsp各自有一份common_har的副本
// feature_a_hsp中设置的值,feature_b_hsp访问不到
解决方案:
-
将HAR升级为HSP
-
使用持久化存储(Preferences、RDB等)在模块间共享数据
-
通过事件总线(EventBus)在模块间通信
5.3 动态加载feature HAP
// 动态加载和安装feature模块
@Entry
@Component
struct MainPage {
// 检查模块是否已安装
async checkModuleInstalled(moduleName: string): Promise<boolean> {
const bundleManager = bundleManager.getBundleManagerForSelf();
const bundleFlags = 0;
try {
const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags);
const moduleExists = bundleInfo.hapModuleInfos?.some(
module => module.name === moduleName
);
return !!moduleExists;
} catch (error) {
console.error('检查模块失败:', error);
return false;
}
}
// 动态安装feature模块
async installFeatureModule(moduleName: string): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const want: Want = {
bundleName: 'com.example.travel',
abilityName: 'MainAbility',
parameters: {
'action': 'installModule',
'moduleName': moduleName
}
};
await context.startAbilityForResult(want);
// 安装成功后,可以启动该模块
await this.launchFeatureModule(moduleName);
} catch (error) {
console.error('安装模块失败:', error);
prompt.showToast({
message: '模块安装失败,请重试',
duration: 3000
});
}
}
// 启动feature模块
async launchFeatureModule(moduleName: string): Promise<void> {
const context = getContext(this) as common.UIAbilityContext;
let want: Want = {
deviceId: '', // 本设备
bundleName: 'com.example.travel',
moduleName: moduleName,
abilityName: `${moduleName}Ability`
};
await context.startAbility(want);
}
}
六、 最佳实践总结
6.1 选择建议总结
根据我们的AI旅行助手项目经验,总结出以下选择建议:
使用HAR的场景:
-
跨应用共享的UI组件库
-
通用的工具类库(如日期处理、字符串工具等)
-
第三方封装的SDK
-
需要发布到OHPM中心仓供其他开发者使用的库
使用HSP的场景:
-
应用内多个模块共享的UI组件
-
应用内共享的业务工具类
-
需要单例管理的服务类
-
资源文件(图片、字体等)共享
使用feature HAP的场景:
-
独立的功能模块(如地图、社交、支付等)
-
可按需安装的功能
-
不同团队负责的独立模块
-
需要独立测试和发布的模块
6.2 性能优化建议
// 模块懒加载优化
@Component
struct LazyModuleLoader {
@State isModuleLoaded: boolean = false;
@State moduleComponent: any = null;
// 按需加载模块
async loadModuleIfNeeded() {
if (this.isModuleLoaded) {
return;
}
// 显示加载状态
this.isModuleLoaded = false;
try {
// 动态导入模块
const module = await import('../feature_module/FeatureComponent');
this.moduleComponent = module.FeatureComponent;
this.isModuleLoaded = true;
} catch (error) {
console.error('加载模块失败:', error);
}
}
build() {
Column() {
if (this.isModuleLoaded && this.moduleComponent) {
// 动态渲染加载的组件
this.moduleComponent()
} else {
// 显示加载中状态
LoadingIndicator()
.size(40)
.color('#1890FF')
Button('加载模块')
.onClick(() => this.loadModuleIfNeeded())
}
}
}
}
6.3 包大小优化对比
优化前(单HAP):
-
总大小:50MB
-
首次下载:50MB
-
包含:所有功能
优化后(模块化):
-
entry HAP:5MB(核心功能)
-
按需feature HAPs:20MB(可选安装)
-
公共HSPs:6MB(共享,不重复)
-
首次下载:11MB(节省78%流量)
七、 常见陷阱与调试技巧
7.1 依赖循环问题
// 错误:循环依赖
// module_a依赖module_b,module_b又依赖module_a
{
"module_a": {
"dependencies": {
"module_b": "file:../module_b"
}
},
"module_b": {
"dependencies": {
"module_a": "file:../module_a"
}
}
}
解决方案:
-
提取公共代码到新的HSP
-
使用接口抽象代替具体实现依赖
-
通过事件通信代替直接调用
7.2 资源ID冲突问题
// HSP中的资源引用
// 正确做法:使用资源别名避免冲突
@Component
struct SharedComponent {
@Builder
BuildImage() {
// ❌ 错误:直接使用资源ID,可能与其他模块冲突
// Image($r('app.media.logo'))
// ✅ 正确:通过HSP模块名限定
Image($r('app.media.module_a:logo'))
}
}
7.3 版本管理建议
// 版本锁定策略
{
"dependencies": {
// 主版本号.次版本号.修订号
"@travel/common-ui": "1.2.3", // 精确版本
"@travel/network-utils": "^1.2.0", // 兼容版本(自动更新次版本和修订号)
"@travel/data-manager": "~1.2.3" // 只更新修订号
}
}
八、 总结
通过模块化重构,我们的AI旅行助手项目获得了显著改进:
重构效果对比:
-
编译时间:从3分钟缩短到30秒(增量编译)
-
安装包大小:从50MB减少到11MB(首次下载)
-
团队开发效率:提升60%(并行开发,减少冲突)
-
代码维护性:大幅提升(模块解耦,职责清晰)
关键收获:
-
HAP是基础:每个应用必须有entry HAP,feature HAP实现模块化
-
HSP优于HAR:应用内共享优先使用HSP,避免资源重复
-
按需加载:非核心功能使用feature HAP,按需安装
-
合理拆分:按业务域拆分模块,保持高内聚低耦合
最佳实践:
-
小即是美:每个模块专注单一职责
-
明确依赖:避免循环依赖,合理使用HSP
-
版本控制:严格管理模块版本
-
持续重构:随着业务发展,不断优化模块划分
HarmonyOS的模块化体系(HAR、HSP、HAP)为大型应用开发提供了强大支持。正确使用这些工具,不仅能提升开发效率,还能显著改善用户体验。希望本文的实践经验能帮助你在HarmonyOS应用开发中,做出更明智的架构选择。
更多推荐

所有评论(0)