第2篇:鸿蒙 HAR 共享模块设计——多组件复用实践

一、什么是 HAR(Harmony Archive)

HAR(Harmony Archive)是鸿蒙系统中的静态共享包,类似于 Android 的 AAR 或 iOS 的 Framework。一个 HAR 可以包含代码、资源、配置文件,供其他模块引用。在 DriverLicenseExam 项目中,HAR 被广泛用于模块化与组件化开发。

1.1 HAR 的核心优势

  • 代码复用:公共逻辑提取到 HAR,多个模块共享
  • 独立编译:每个 HAR 可独立编译,加速构建
  • 版本管理:通过 oh-package.json5 控制版本
  • 资源隔离:资源文件跟随 HAR 打包,不与其他模块冲突

二、项目中的 HAR 模块划分

2.1 Commons 公共层(3 个 HAR)

commons/
  ├── commonLib/          ← 公共工具库(har)
  │   ├── src/main/ets/constants/     ← 常量、枚举
  │   ├── src/main/ets/model/         ← 公共数据模型
  │   ├── src/main/ets/utils/         ← 工具类
  │   └── src/main/ets/push/          ← 推送工具
  ├── datasource/         ← 数据源服务(har)
  │   ├── src/main/ets/ExamService.ets  ← 考试服务
  │   └── src/main/ets/Model.ets        ← 数据模型
  └── network/            ← 网络模块(har)
      ├── src/main/ets/apis/          ← API 接口
      ├── src/main/ets/mocks/         ← Mock 数据
      ├── src/main/ets/models/        ← 网络请求封装
      └── src/main/ets/types/         ← 类型定义

2.2 Components 组件层(10+ 个 HAR)

每个组件都是一个独立的 HAR 包,拥有自己的构建配置和依赖管理:

// components/exam/oh-package.json5
{
  "name": "exam",
  "version": "1.0.0",
  "dependencies": {
    "@ohos_agcit/driver_license_exam_commonlib": "file:../../commons/commonLib",
    "@ohos_agcit/driver_license_exam_datasource": "file:../../commons/datasource"
  }
}

三、HAR 的创建与配置

3.1 构建配置文件

每个 HAR 都有独立的 build-profile.json5 配置文件:

{
  "apiType": "stageMode",
  "buildOption": {
    "arkOptions": {
      "compile ArkTS": true
    }
  }
}

3.2 模块导出(Index.ets)

每个 HAR 通过 Index.ets 文件对外暴露接口:

// commons/commonLib/Index.ets
export { CommonConstants } from './src/main/ets/constants/CommonContants';
export { CommonEnums } from './src/main/ets/constants/CommonEnums';
export { Logger } from './src/main/ets/utils/Logger';
export { PermissionUtil } from './src/main/ets/utils/PermissionUtil';
export { AccountUtil } from './src/main/ets/utils/AccountUtil';
export { PreferencesUtil } from './src/main/ets/utils/PreferencesUtil';
export { RouterModule } from './src/main/ets/utils/RouterModule';
export { CommonModel } from './src/main/ets/model/CommonModel';
export { PushUtils } from './src/main/ets/push/PushUtils';

3.3 外部引用

在 entry 模块的 oh-package.json5 中声明依赖:

// products/entry/oh-package.json5
{
  "dependencies": {
    "exam": "file:../../components/exam",
    "aggregated_ads": "file:../../components/aggregated_ads",
    "aggregated_share": "file:../../components/aggregated_share",
    "app_setting": "file:../../components/app_setting",
    "feedback": "file:../../components/feedback",
    "module_city_select": "file:../../components/module_city_select",
    "@ohos_agcit/driver_license_exam_commonlib": "file:../../commons/commonLib",
    "@ohos_agcit/driver_license_exam_datasource": "file:../../commons/datasource"
  }
}

四、核心 HAR 模块深度解析

4.1 commonLib——公共工具库

commonLib 是最基础的 HAR 模块,提供所有模块都需要的工具类:

// Logger 日志工具类
export class Logger {
  private static _domain: number = 0xff00;
  private static _prefix: string = 'EmptyTemplate';
  private static _format: string = '%{public}s, %{public}s';

  public static debug(...args: string[]): void {
    hilog.debug(Logger._domain, Logger._prefix, Logger._format, args);
  }

  public static info(...args: string[]): void {
    hilog.info(Logger._domain, Logger._prefix, Logger._format, args);
  }

  public static error(...args: string[]): void {
    hilog.error(Logger._domain, Logger._prefix, Logger._format, args);
  }
}
// PreferencesUtil 首选项工具类
export class PreferencesUtil {
  private static preferencesUtil: PreferencesUtil;
  private static readonly MY_STORE: string = 'myStore';

  public static getInstance(): PreferencesUtil {
    if (!PreferencesUtil.preferencesUtil) {
      PreferencesUtil.preferencesUtil = new PreferencesUtil();
    }
    return PreferencesUtil.preferencesUtil;
  }

  getPreferences(context: Context): preferences.Preferences {
    preferences.removePreferencesFromCacheSync(context, MY_STORE);
    return preferences.getPreferencesSync(context, { name: MY_STORE });
  }

  preferencesPut(preferences: preferences.Preferences, key: string, value: preferences.ValueType): void {
    preferences.putSync(key, value);
    this.preferencesFlush(preferences);
  }
}

4.2 datasource——数据源服务

datasource 模块封装了考试相关的数据逻辑,对外暴露 ExamService 和各类数据模型:

// ExamService 数据服务单例
@ObservedV2
export class ExamService {
  private static _instance: ExamService;
  private examDetails: ExamDetail[] = [];
  @Trace mockExamCount: number = 0;
  @Trace mockExamScore: number[] = [];

  public static instance(context: Context) {
    ExamService.context = context;
    if (!ExamService._instance) {
      ExamService._instance = new ExamService();
    }
    return ExamService._instance;
  }

  // 获取全部已做数量
  public getDidCount(): number {
    return this.examDetails.filter(item => item.isCorrect !== undefined).length;
  }

  // 计算正确率
  public calAccuracyRate(): number {
    const rightCount = this.getCorrectCount();
    const totalCount = this.getTotalCount();
    if (rightCount === 0) return 0;
    return Math.ceil(rightCount / totalCount * 100);
  }
}

4.3 network——网络模块

network 模块封装了基于 Axios 的网络请求:

// AxiosHttpModel 网络请求封装
export class AxiosHttpModel {
  private _instance: AxiosInstance;
  private _openMock: boolean;

  constructor(config: HttpRequestConfig, openMock: boolean = false) {
    this._config = config;
    this._instance = axios.create(config);
    this._openMock = openMock;
    this._setupInterceptor();
  }

  public async request<T = ESObject>(config: HttpRequestConfig): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      this._instance
        .request<ESObject, T>(config)
        .then(res => resolve(res))
        .catch(err => {
          Logger.error(`https request failed:${config.url}`, err.message);
          reject(err);
        });
    });
  }

  private _setupInterceptor(): void {
    this._instance.interceptors.request.use(
      this._config.interceptorHooks.requestInterceptor,
      this._config.interceptorHooks.requestInterceptorCatch,
    );
    this._instance.interceptors.response.use(
      this._config.interceptorHooks.responseInterceptor,
      this._openMock ? this._replaceMock : this._config.interceptorHooks.responseInterceptorCatch,
    );
  }
}

五、组件 HAR 的独立开发与测试

5.1 独立运行的组件

每个组件 HAR 可以独立开发和测试,通过 BuildProfile 配置不同的运行环境:

// components/exam/BuildProfile.ets
// 编译时区分组件独立运行和作为 HAR 依赖的场景

5.2 组件测试

每个 HAR 都包含独立的测试目录:

components/exam/
  ├── src/ohosTest/    ← 集成测试
  └── src/test/        ← 单元测试
    ├── List.test.ets
    └── LocalUnit.test.ets

六、HAR 模块的最佳实践

6.1 依赖管理原则

  1. 单向依赖:commons → components → products,避免循环依赖
  2. 接口抽象:通过 Index.ets 严格控制导出内容
  3. 版本对齐:多个模块依赖同一 HAR 时使用相同版本

6.2 模块划分原则

  • commonLib:纯工具类,无业务逻辑
  • datasource:数据层,只关心数据不关心 UI
  • network:网络层,只关心通信不关心业务
  • components:业务组件,包含 UI 和业务逻辑

七、总结

通过 HAR 模块化设计,DriverLicenseExam 项目实现了:

  1. 高内聚低耦合:每个 HAR 职责单一,模块间依赖清晰
  2. 并行开发:团队可以同时开发不同 HAR 模块
  3. 独立测试:每个 HAR 可独立编译和运行测试
  4. 按需集成:产品层可以灵活选择需要的组件

这种架构设计是鸿蒙中大型应用的推荐实践,值得学习和借鉴。


关键源码文件:

  • commons/commonLib/Index.ets — 公共库导出
  • commons/commonLib/src/main/ets/utils/PreferencesUtil.ets — 首选项工具
  • commons/datasource/src/main/ets/ExamService.ets — 数据服务
  • commons/network/src/main/ets/models/AxiosHttpModel.ets — 网络封装
  • products/entry/oh-package.json5 — 依赖配置
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐