摘要
单 entry 模块存放全部页面、工具、组件会导致工程臃肿、编译缓慢、多人协作代码冲突、功能无法复用。HAR 静态共享库用于拆分独立业务模块、通用基础能力库,实现代码解耦、多工程复用、分层隔离。API Version23 重构 HAR 依赖导入、资源互通、跨模块类型导出、编译缓存机制,修复低版本 HAR 资源找不到、类型导入爆红、跨模块工具循环依赖、图片 rawfile 无法访问、多 HAR 编译冲突等兼容缺陷。旧项目升级 API23 后常出现:导入 HAR 组件提示不存在、模型 interface 跨模块无法识别、rawfile 图片加载空白、同时引入多个 HAR 编译报错。本文基于 DevEco Studio API23+,完整讲解 HAR 库创建、依赖配置、跨模块资源 / 工具 / 组件调用、循环依赖规避、多模块分层架构实战,封装基础通用能力 HAR、业务笔记模块 HAR 两套完整示例,输出分包分层规范、导出约束、编译优化方案,为大型鸿蒙项目模块化拆分提供标准化模板。
关键词
OpenHarmony;ArkTS;API Version23;HAR;静态共享库;模块化拆分;多模块工程;代码复用;分层架构
一、引言
1.1 HAR 分包开发背景
小型课程设计单 entry 模块可正常开发,但中大型项目、多人协作、多终端复用场景存在明显痛点:
- 所有代码堆在 entry,文件上千,打开工程卡顿、编译耗时久;
- 通用工具、自定义组件、数据模型无法给其他项目复用;
- 业务耦合严重,删除某一功能会牵连大量页面代码;
- 多人开发容易出现文件命名冲突、git 合并大量冲突;
- 功能无法独立版本管控,基础能力更新全项目重新编译。
HAR(Harmony Archive)静态共享库用于解决以上问题,特点:
- 静态编译打包,编译时合并至主模块,运行无性能损耗;
- 可拆分通用基础库、独立业务模块库;
- 支持导出 ets 工具、自定义组件、类型、常量、资源;
- 支持多个 HAR 互相依赖,主 entry 统一引入所有 HAR。
OpenHarmony API Version23 HAR 核心升级变更:
- 严格控制跨模块导出权限,未 export 的类 / 函数外部模块不可访问;
- 统一 rawfile、media 资源跨 HAR 访问路径规则,修复图片资源加载失败;
- 优化循环依赖检测,编译阶段直接抛出依赖环报错,便于定位;
- 完善类型导出传递,HAR 导出 interface 后 entry 可正常识别类型;
- 分模块编译缓存,修改单个 HAR 仅重新编译该模块,大幅提升编译速度;
- 区分 HAR 静态库与 HSP 动态共享库,简化小型项目分包成本。
大量旧项目升级后跨模块导入爆红、资源加载空白,根源是未规范导出、资源路径写法错误、存在循环依赖,因此掌握 HAR 分包是企业级大型项目必备架构能力。
1.2 开发环境与分层场景
开发工具:DevEco Studio 5.0+ 适配系统:OpenHarmony API23、HarmonyOS NEXT 模块类型:entry(主应用模块)、多个 har 静态库模块 分包示例:
- har_base:全局工具、权限、网络、文件、通知、动画公共底层能力
- har_note:笔记独立业务(页面、CRUD、列表组件、数据库模型) 测试场景:主 entry 导入 har_base 工具、entry 引入 har_note 页面、HAR 之间互相依赖、跨模块资源访问、类型共享
二、API23+ HAR 核心约束与基础配置
2.1 HAR 与 entry 核心区别
表格
| 模块 | 用途 | 能否独立运行 | 页面注册 | Ability |
|---|---|---|---|---|
| entry | 主应用入口 | 可独立安装运行 | module.json5 注册 pages | 可定义 EntryAbility |
| HAR 静态库 | 代码 / 资源共享分包 | 不可单独运行 | 禁止注册 pages | 不允许定义 Ability |
2.2 module.json5 HAR 基础模板(har_base 示例)
json
{ "module": { "name": "har_base", "type": "har", "description": "全局基础工具库", "deviceTypes": ["phone"], "deliveryWithInstall": false, "installationFree": false } }2.3 主 entry 引入 HAR 依赖(entry module.json5)
json
"dependencies": [ { "name": "har_base", "version": "1.0.0", "scope": "shared" }, { "name": "har_note", "version": "1.0.0", "scope": "shared" } ]2.4 跨模块导入路径规则
- 导入 HAR 内 ets 代码:
import xxx from '@ohos:模块名/相对路径' - HAR 内部互相导入:同标准相对路径
../ - HAR 资源访问:模块名、r ('har 模块名:rawfile/xxx')
2.5 API23 强制约束
- HAR 中所有需要外部访问的类、函数、interface、常量必须加 export,私有代码禁止导出;
- HAR 不能定义 UIAbility、不能在 module.json5 注册 pages 页面;页面只能存放于 entry;
- 禁止双向循环依赖(har_base 依赖 har_note 同时 har_note 依赖 har_base);
- 动态权限、数据库初始化上下文仅 entry 可提供,HAR 工具仅接收外部传入 context;
- HAR 不支持 AppStorage 全局初始化,全局状态统一在 entry EntryAbility 管理;
- 多个 HAR 同名资源会产生覆盖冲突,资源文件名增加模块前缀区分。
三、基础最简 HAR 导入示例
3.1 har_base 内通用工具 har_base/src/main/ets/utils/toast_util.ets
ets
import promptAction from '@ohos.promptAction' // 必须export导出,entry才能导入使用 export function showToast(msg: string) { promptAction.showToast({message: msg}) }3.2 entry 页面跨模块导入 HAR 工具
ets
// 固定格式 @ohos:har模块名/文件相对路径 import { showToast } from '@ohos:har_base/utils/toast_util' @Entry @Component struct Index { build() { Button("调用HAR封装Toast") .onClick(()=> showToast("来自基础HAR库工具")) } }3.3 HAR 共享数据模型 interface
har_base/src/main/ets/model/NoteModel.ets
ets
export interface NoteItem { id: number title: string content: string }entry 页面使用:
ets
import { NoteItem } from '@ohos:har_base/model/NoteModel' @State note: NoteItem = {id:1, title:"测试", content:"HAR共享实体"}四、完整分层多模块实战架构(两套 HAR 分包完整代码)
整体工程目录结构
plaintext
项目根目录 ├─ entry(主应用,页面、EntryAbility、路由跳转) ├─ har_base(基础底层HAR:工具、常量、类型、通用组件) │ └─ src/main/ets │ ├─ utils 网络/文件/权限/通知/动画全套工具 │ ├─ model 全局interface、常量Constant │ └─ components 通用弹窗、加载组件 └─ har_note(笔记业务HAR:数据库操作、笔记专用组件、业务逻辑) └─ src/main/ets ├─ model 笔记业务实体 ├─ db 笔记RDB封装 └─ components 笔记条目ListItem组件4.1 实战一:基础底层 HAR har_base 通用导出汇总
har_base/src/main/ets/model/Constant.ets(全局常量导出)
ets
export const STORAGE_KEY = { IS_LOGIN: "isLogin", DARK_MODE: "darkMode" } export const PAGE_ROUTE = { INDEX: "pages/index/index", NOTE_LIST: "pages/note/NoteList" }har_base/src/main/ets/components/LoadingDialog.ets(通用组件导出)
ets
@Component export struct LoadingDialog { build() { Column() { Text("加载中...") } .width(120) .height(100) .backgroundColor("#00000099") .borderRadius(12) } }har_base/src/main/ets/utils/permission_util.ets(权限工具单例导出)
ets
import accessToken from '@ohos.accessToken' import common from '@ohos.app.ability.common' import { showToast } from './toast_util' class PermissionUtil { private static ins: PermissionUtil private ctx: common.UIAbilityContext | null = null static getInstance() { if(!PermissionUtil.ins) PermissionUtil.ins = new PermissionUtil() return PermissionUtil.ins } setContext(ctx: common.UIAbilityContext) { this.ctx = ctx } async requestCamera(): Promise<boolean> { if(!this.ctx) return false const status = accessToken.checkAccessTokenSync(this.ctx, "ohos.permission.CAMERA") if(status === accessToken.GrantStatus.PERMISSION_GRANTED) return true const res = await accessToken.requestPermissions(this.ctx, ["ohos.permission.CAMERA"]) if(res[0].grantStatus === accessToken.GrantStatus.PERMISSION_GRANTED) return true showToast("未授予相机权限") return false } } export default PermissionUtil.getInstance()4.2 实战二:业务 HAR har_note 笔记数据库与条目组件
har_note/src/main/ets/db/note_db.ets(依赖 har_base 工具)
ets
import relationalStore from '@ohos.data.relationalStore' import common from '@ohos.app.ability.common' import { showToast } from '@ohos:har_base/utils/toast_util' import { NoteItem } from '@ohos:har_base/model/NoteModel' class NoteDbUtil { private static ins: NoteDbUtil private store: relationalStore.RdbStore | null = null private ctx: common.UIAbilityContext | null = null static getInstance() { if(!NoteDbUtil.ins) NoteDbUtil.ins = new NoteDbUtil() return NoteDbUtil.ins } setContext(ctx: common.UIAbilityContext) { this.ctx = ctx } async init() { if(!this.ctx) return const config: relationalStore.StoreConfig = { name: "note_db", securityLevel: relationalStore.SecurityLevel.S1 } this.store = await relationalStore.getRdbStore(this.ctx, config) await this.store.executeSql(`CREATE TABLE IF NOT EXISTS note(id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT,content TEXT)`) } async addNote(title:string, content:string): Promise<number> { if(!this.store) return -1 const bucket = {title, content} return await this.store.insert("note", bucket) } async queryList(page:number, size:number): NoteItem[] { if(!this.store) return [] const pred = new relationalStore.RdbPredicates("note") pred.offset(page*size).limit(size) const rs = await this.store.query("note", ["id","title","content"], pred) let list: NoteItem[] = [] while(rs.goToNextRow()){ list.push({ id: rs.getLong(0), title: rs.getString(1), content: rs.getString(2) }) } rs.close() return list } } export default NoteDbUtil.getInstance()har_note/src/main/ets/components/NoteItemCard.ets(导出笔记条目组件)
ets
import { NoteItem } from '@ohos:har_base/model/NoteModel' @Component export struct NoteItemCard { @Param data: NoteItem @Param onDelete: () => void build() { Row() { Column({space:4}).layoutWeight(1) { Text(this.data.title).fontSize(17) Text(this.data.content).fontColor("#666") } Button("删除").backgroundColor("#f56c6c") .onClick(()=> this.onDelete()) } .width("100%") .padding(16) .backgroundColor(Color.White) .borderRadius(10) } }4.3 实战三:entry 主页面调用两套 HAR 完整示例 pages/note/NoteList.ets
ets
// 导入基础HAR工具、常量、通用组件 import PermissionUtil from '@ohos:har_base/utils/permission_util' import { LoadingDialog } from '@ohos:har_base/components/LoadingDialog' import { PAGE_ROUTE } from '@ohos:har_base/model/Constant' // 导入笔记业务HAR数据库、条目组件 import NoteDbUtil from '@ohos:har_note/db/note_db' import { NoteItemCard } from '@ohos:har_note/components/NoteItemCard' import { NoteItem } from '@ohos:har_base/model/NoteModel' import RouterUtil from '../utils/router_util' @Entry @Component struct NoteListPage { @State list: NoteItem[] = [] ctrl: CustomDialogController | null = null async aboutToAppear() { // 给HAR工具传入页面上下文 const ctx = getContext(this) PermissionUtil.setContext(ctx) NoteDbUtil.setContext(ctx) await NoteDbUtil.init() await this.loadData() } async loadData() { this.list = await NoteDbUtil.queryList(0,10) } async delItem(id: number) { await NoteDbUtil.deleteNote(id) await this.loadData() } build() { Column({space:12}) { Text("笔记管理(多HAR分包工程演示)").fontSize(22).padding(10) List({space:10}).layoutWeight(1) { ForEach(this.list, (item:NoteItem)=>{ ListItem() { NoteItemCard({ data: item, onDelete: ()=> this.delItem(item.id) }) } }) } Button("返回首页").onClick(()=> RouterUtil.replace(PAGE_ROUTE.INDEX)) } .width("100%") .height("100%") .backgroundColor("#F5F5F5") } async aboutToDisappear() { await NoteDbUtil.closeDB() } }五、API23+ HAR 模块化分层与编译优化规范
5.1 分包分层规范(单向依赖,杜绝循环)
- 底层 har_base:无任何其他 HAR 依赖,提供通用基础能力;
- 业务 HAR(har_note、har_user):仅允许依赖 har_base,业务 HAR 之间禁止互相依赖;
- entry 主模块:可依赖所有 HAR,页面、Ability、全局状态全部放在 entry;
- 分层依赖流向:entry → 业务 HAR → 底层 har_base,单向不回流。
5.2 导出编码规范
- 所有对外可访问函数、类、组件、interface 必须添加 export;
- 仅暴露外部需要调用的 API,内部辅助工具私有不导出;
- 单例工具使用
export default,常量 / 类型使用命名export const/interface; - HAR 内组件统一 @Component + export,entry 才能直接引入渲染。
5.3 资源访问规范
- HAR 图片资源:
$r('har模块名:media/图片名'); - HAR rawfile 静态 HTML:
$r('har模块名:rawfile/index.html'); - 不同 HAR 资源文件名增加模块前缀,避免同名覆盖;
- 通用全局图片统一存放 har_base media 目录,业务图片存放对应业务 HAR。
5.4 编译性能优化规范
- 按功能拆分多个小型 HAR,不创建单个超大 HAR;
- 修改仅涉及单一 HAR 时,仅该模块增量编译,缩短等待时间;
- 稳定不变的底层基础能力抽离为独立 HAR,极少重新编译;
- 调试阶段可临时移除未开发完成的 HAR 依赖,加快编译速度。
5.5 协作开发规范
- 不同开发人员负责独立 HAR 模块,代码隔离减少冲突;
- HAR 对外 API 统一维护导出文件,接口变更统一同步;
- 公共基础修改统一提交 har_base,各业务模块同步更新依赖。
六、API23 升级高频 HAR 兼容问题与解决方案
问题 1:entry 导入 HAR 代码提示爆红,找不到模块 解决:entry module.json5 添加对应 dependencies 依赖,导入路径使用@ohos:har名/标准前缀。
问题 2:HAR 内定义 interface,entry 接收时报类型不匹配 解决:实体 interface 统一放在底层 har_base 并 export 导出,业务 HAR、entry 统一从 base 导入。
问题 3:HAR 中读取 media 图片空白无法显示 解决:使用跨模块资源格式$r('har模块名:media/xxx'),不使用本地相对资源写法。
问题 4:编译提示循环依赖报错 cyclic dependency 解决:调整分层,业务 HAR 只依赖底层 base,业务 HAR 之间不互相导入。
问题 5:HAR 中调用 RDB/Preferences 提示上下文为空 解决:HAR 工具仅提供方法,由 entry 页面获取 context 传入 HAR 工具 setContext。
问题 6:多个 HAR 存在同名组件,渲染出现错乱 解决:组件、资源文件名增加 HAR 模块专属前缀,避免命名冲突。
七、总结
HAR 静态共享库是 OpenHarmony 大型项目标准化分包方案,API Version23 完善跨模块导出、资源访问、循环依赖检测、增量编译机制,解决单 entry 工程臃肿、代码无法复用、编译缓慢、多人协作冲突等核心痛点。 标准分层架构:底层基础 HAR 存放全局通用工具与基础组件,各业务独立拆分为业务 HAR,主 entry 仅负责页面与应用生命周期管理,严格单向依赖杜绝循环耦合。全部代码兼容 API23 与 HarmonyOS NEXT,适合课程设计大型综合项目、企业商用鸿蒙工程模块化改造。