摘要

HAR 是静态打包,全部代码安装时一次性装入主包,APP 体积大、冷启动慢;HSP(Harmony Shared Package)动态共享库支持运行时按需加载,将非核心业务拆分独立动态包,首页仅加载基础模块,其他功能进入对应页面时再动态拉取 / 加载,减小安装包体积、缩短启动耗时。API23 重构 HSP 动态加载上下文、页面路由跳转、资源隔离、内存释放逻辑,修复低版本 HSP 页面跳转空白、动态组件渲染失败、包重复加载内存泄漏、资源跨包访问崩溃等问题。本文完整讲解 HSP 创建、依赖配置、动态加载页面 / 工具 / 组件、分包路由、资源隔离、卸载回收,配套笔记业务 HSP 实战工程,对比 HAR 与 HSP 适用场景,给出分包优化规范,适配课程设计大型多模块项目、商用轻量化 APP 开发。

关键词

OpenHarmony;ArkTS;API23;HSP;动态共享包;按需加载;分包;动态路由;包体积优化

一、引言

1.1 HSP 开发背景

静态 HAR 全部代码编译打包进 entry 主模块,存在明显短板:

  1. 功能越多安装包越大,低端设备安装缓慢;
  2. 首次启动需要加载全部代码,冷启动卡顿;
  3. 低频功能(笔记、相册、设置)长期占用内存,后台耗电;
  4. 多业务耦合,删除功能需要重新全量编译。

HSP 动态共享库解决以上痛点:

  1. 代码、资源独立分包,运行时按需加载,不用不占用内存;
  2. 主包仅保留首页、登录、基础工具,大幅缩减安装包体积;
  3. 低频业务拆分为独立 HSP,进入页面时再加载包;
  4. 支持动态卸载,退出业务页面释放整个分包内存。

OpenHarmony API23 HSP 核心升级点:

  1. 新增 loadPackage/waitPackageLoad 统一加载 API,简化异步等待逻辑;
  2. 隔离 HSP 与主包资源域,避免图片、常量重名冲突;
  3. 修复动态加载页面返回栈错乱、路由丢失;
  4. 完善包卸载资源回收:组件、数据库、定时器自动释放;
  5. 支持 HSP 内部再次依赖基础 HAR,复用公共工具;
  6. 严格限制跨包直接导入,统一使用动态导入接口,杜绝编译爆红。

1.2 HSP 与 HAR 核心区分

表格

类型 加载时机 包体积 跨包导入方式 适用场景
HAR 静态库 编译打包,安装即加载 合并入主包,包大 import 静态导入 全局基础工具、高频公共组件
HSP 动态库 运行时按需动态加载 独立分包,主包极小 动态 loadPackage + 反射调用 低频独立业务:笔记、相机相册、设置

二、API23 HSP 核心配置与基础 API

2.1 HSP 模块 module.json5 基础配置

json

{
  "module": {
    "name": "hsp_note",
    "type": "hsp",
    "description": "笔记动态分包",
    "deviceTypes": ["phone"],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": [
      "src/main/ets/pages/NoteList"
    ],
    "dependencies": [
      {
        "name": "har_base",
        "version": "1.0.0",
        "scope": "shared"
      }
    ]
  }
}

关键点:

  1. type 固定为hsp
  2. HSP 内部可注册 pages 页面,独立路由;
  3. 仅能依赖静态 HAR,HSP 之间禁止互相依赖
  4. deliveryWithInstall=true,打包时自动拆分独立分包。

2.2 entry 主模块依赖 HSP(仅声明,不静态合并)

json

"dependencies": [
  {
    "name": "hsp_note",
    "version": "1.0.0",
    "scope": "shared"
  }
]

2.3 动态加载核心 API

ets

import hspManager from '@ohos.hspManager'

// 1. 异步加载HSP包
await hspManager.loadPackage("hsp_note")

// 2. 等待包加载完成,获取包上下文
const hspContext = await hspManager.waitPackageLoad("hsp_note")

// 3. 卸载分包,释放全部资源
hspManager.unloadPackage("hsp_note")

// 4. 跳转HSP内部页面(包名://页面路径)
router.pushUrl({url: "hsp_note/pages/NoteList"})

2.4 API23 强制约束

  1. 禁止直接 import 导入 HSP 内部 ets 代码,必须动态加载后反射调用;
  2. HSP 页面路由格式固定:hsp包名/页面完整pages路径
  3. HSP 不能定义 UIAbility,生命周期统一由 entry 主 Ability 管理;
  4. 跨 HSP 资源不能直接 $r 引用,需通过全局 HAR 共享资源;
  5. 页面退出必须 unloadPackage 释放分包,防止内存持续上涨;
  6. 同一分包不可重复并发 loadPackage,增加加载状态锁防重复请求。

三、最简基础示例:动态加载并跳转 HSP 页面

entry 首页跳转笔记分包

ets

import hspManager from '@ohos.hspManager'
import router from '@ohos.router'
import { showToast } from '@ohos:har_base/utils/toast_util'

@Entry
@Component
struct Index {
  @State loading: boolean = false

  async jumpNoteHsp() {
    if (this.loading) return
    this.loading = true
    try {
      // 加载笔记动态分包
      await hspManager.loadPackage("hsp_note")
      showToast("笔记模块加载完成")
      // 跳转HSP内部页面
      router.pushUrl({ url: "hsp_note/src/main/ets/pages/NoteList" })
    } catch (err) {
      showToast("动态模块加载失败")
      console.error("hsp加载异常", err)
    } finally {
      this.loading = false
    }
  }

  build() {
    Column() {
      Text("主应用首页(仅基础包)").fontSize(24).margin({ top: 80 })
      Button(this.loading ? "加载中..." : "打开笔记模块(动态HSP)")
        .width(300)
        .height(48)
        .margin({ top: 30 })
        .onClick(() => this.jumpNoteHsp())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

HSP 笔记页面 hsp_note/src/main/ets/pages/NoteList.ets

ets

import NoteDbUtil from '@ohos:har_base/db/note_db'
import { NoteItemCard } from '@ohos:har_base/components/NoteItemCard'
import { NoteItem } from '@ohos:har_base/model/NoteModel'
import router from '@ohos.router'
import hspManager from '@ohos.hspManager'

@Entry
@Component
struct NoteList {
  @State list: NoteItem[] = []

  async aboutToAppear() {
    const ctx = getContext(this)
    NoteDbUtil.setContext(ctx)
    await NoteDbUtil.init()
    this.list = await NoteDbUtil.queryList(0, 10)
  }

  // 返回首页并卸载HSP分包释放内存
  backHome() {
    router.back()
    // 卸载动态包
    hspManager.unloadPackage("hsp_note")
  }

  build() {
    Column({ space: 12 }) {
      Row() {
        Button("返回首页并释放分包").onClick(() => this.backHome())
        Text("笔记动态分包页面(HSP)").layoutWeight(1).fontSize(20)
      }
      List({ space: 10 }).layoutWeight(1) {
        ForEach(this.list, (item: NoteItem) => {
          ListItem() {
            NoteItemCard({ data: item })
          }
        })
      }
    }
    .width("100%")
    .height("100%")
    .padding(12)
    .backgroundColor("#f5f5f5")
  }

  async aboutToDisappear() {
    await NoteDbUtil.closeDB()
  }
}

四、实战封装:HSP 动态加载工具类 utils/hsp_load_util.ets

ets

import hspManager from '@ohos.hspManager'
import router from '@ohos.router'
import promptAction from '@ohos.promptAction'

class HspLoadUtil {
  private static instance: HspLoadUtil
  // 记录正在加载的分包,防重复并发加载
  private loadingMap: Map<string, boolean> = new Map()

  static getInstance(): HspLoadUtil {
    if (!HspLoadUtil.instance) HspLoadUtil.instance = new HspLoadUtil()
    return HspLoadUtil.instance
  }

  // 加载分包并跳转内部页面
  async loadAndJump(hspName: string, pagePath: string): Promise<boolean> {
    if (this.loadingMap.get(hspName)) {
      promptAction.showToast({ message: "模块正在加载,请稍等" })
      return false
    }
    this.loadingMap.set(hspName, true)
    try {
      // 加载分包
      await hspManager.loadPackage(hspName)
      // 等待包就绪
      await hspManager.waitPackageLoad(hspName)
      // 跳转页面
      router.pushUrl({ url: `${hspName}/${pagePath}` })
      return true
    } catch (e) {
      promptAction.showToast({ message: "功能模块加载失败" })
      console.error(`HSP[${hspName}]加载失败`, e)
      return false
    } finally {
      this.loadingMap.set(hspName, false)
    }
  }

  // 卸载分包,释放资源
  unload(hspName: string) {
    hspManager.unloadPackage(hspName)
    this.loadingMap.delete(hspName)
  }
}

export default HspLoadUtil.getInstance()

工具调用示例

ets

import HspLoadUtil from '../utils/hsp_load_util'

// 点击按钮加载笔记分包并跳转页面
Button("打开笔记模块")
  .onClick(async ()=>{
    await HspLoadUtil.loadAndJump("hsp_note", "src/main/ets/pages/NoteList")
  })

五、HSP 分包工程分层架构规范

标准三层架构

  1. entry(主包)
    • 首页、登录、全局状态、EntryAbility、HSP 加载工具
    • 仅依赖基础 har_base,不包含任何低频业务代码
  2. har_base(静态公共库)
    • 全局工具、数据模型、通用 UI 组件、常量
    • entry 与所有 HSP 统一依赖,共享无重复代码
  3. hsp_xxx(各业务动态分包)
    • hsp_note 笔记模块
    • hsp_camera 相机相册模块
    • hsp_setting 设置模块 每个 HSP 仅依赖 har_base,互相不依赖,独立按需加载、卸载

分包拆分标准

  1. 必须放入主 entry:登录、首页、全局基础工具、启动逻辑;
  2. 适合拆 HSP:低频独立完整业务,页面不与首页强耦合;
  3. 禁止拆 HSP:全局高频组件、通用工具(统一放入 HAR)。

六、性能与内存优化规范

  1. 加载时机优化:不在 aboutToAppear 自动预加载 HSP,用户点击按钮再加载,减少冷启动耗时;
  2. 内存释放强制规范:退出 HSP 业务页面必须调用 unloadPackage,释放数据库、定时器、组件缓存;
  3. 防重复加载:工具类增加 loadingMap 状态锁,避免快速点击重复触发 loadPackage;
  4. 资源复用规范:图片、字体、通用组件全部放入 har_base,HSP 不重复存放资源,减小分包体积;
  5. 避免嵌套加载:禁止在一个 HSP 内部加载另一个 HSP,统一由 entry 主包管理分包加载逻辑;
  6. 分包体积控制:单个 HSP 控制在 10MB 以内,超大业务继续拆分为多个小 HSP。

七、API23 升级常见问题解决方案

问题 1:跳转 HSP 页面提示页面不存在 解决:路由路径完整填写 hsp包名/完整页面文件路径,核对 module.json5 中 pages 注册路径。

问题 2:多次点击按钮重复加载,应用卡顿 解决:使用 Map 标记加载状态,加载中拦截重复点击。

问题 3:退出页面不卸载 HSP,内存持续上涨 解决:页面返回时调用 hspManager.unloadPackage 释放分包资源。

问题 4:HSP 中使用 RDB/Preferences 上下文失效 解决:HSP 页面通过 getContext (this) 获取上下文,传入 HAR 工具类,不要使用 entry 全局上下文。

问题 5:编译提示 HSP 之间存在依赖循环 解决:HSP 仅允许依赖静态 har_base,所有 HSP 互相不引用。

问题 6:打包后分包未拆分,全部合并进 entry 解决:HSP 模块 deliveryWithInstall 配置为 true,重新构建 App 包。

八、总结

HSP 动态共享包是 OpenHarmony 官方分包体积优化方案,API23 完善动态加载、等待就绪、卸载回收、路由跳转整套能力,完美解决静态 HAR 打包主包体积大、冷启动慢、低频功能常驻占用内存等痛点。 工程采用「entry 主包 + HAR 公共底层 + 多 HSP 业务分包」三层标准架构,高频基础能力静态复用,低频独立业务按需动态加载、用完卸载释放内存,兼顾启动速度、安装包大小、内存占用三大性能指标。 代码完全兼容 API23 / HarmonyOS NEXT,适合大型综合课程设计、商用轻量化鸿蒙 APP 分包改造。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐