前言

在大型鸿蒙项目、毕设项目中,如果所有页面全部打包进 entry 主模块,会出现安装包体积大、冷启动缓慢、闲置页面持续占用内存等问题。HSP(Harmony Shared Package)动态分包是官方提供的按需加载方案,重型编辑器、相册、视频页面可独立拆分为分包,仅用户点击时加载,退出页面立即卸载释放内存。 本文基于前文四层脚手架,完整演示 HSP 创建、依赖配置、页面跳转传参、分包卸载、常见报错解决方案,所有代码可直接复制运行。

一、HSP 与 HAR 核心区别

表格

类型 HAR 静态包 HSP 动态分包
加载时机 编译期合并入主包,应用启动全部加载 运行时按需加载,未访问不占用内存
页面支持 不能存放 pages 路由页面 支持独立 pages 页面,可单独跳转
依赖规则 可被所有模块静态引入,不能依赖 HSP 仅能依赖 HAR,不能依赖其他 HSP
复用范围 全工程跨模块复用 仅当前应用内部使用,无法跨项目共享
适用场景 通用工具、业务实体、基础组件 低频、体积大的独立业务页面

二、新建 HSP 模块与 module.json5 标准配置

1. 创建 hsp_note_editor 模块

DevEco Studio 右键项目 → New → Module → Harmony Shared Package,模块名hsp_note_editor

2. module.json5 完整配置

json

{
  "module": {
    "name": "hsp_note_editor",
    "type": "hsp",
    "description": "笔记编辑器动态分包",
    "deviceTypes": ["phone"],
    "deliveryWithInstall": true,
    "pages": [
      "src/main/ets/pages/EditorPage"
    ]
  },
  "dependencies": [
    {
      "name": "har_base",
      "version": "1.0.0",
      "scope": "shared"
    },
    {
      "name": "har_note",
      "version": "1.0.0",
      "scope": "shared"
    }
  ]
}

关键配置说明:

  1. deliveryWithInstall: true:打包时分包独立拆分,安装主 HAP 时同步安装分包;
  2. pages 数组注册分包内页面,否则路由跳转提示页面不存在;
  3. 仅依赖底层公共 HAR 与对应业务 HAR,禁止引入其他 HSP。

3. entry 模块依赖配置

entry 的 module.json5 dependencies 中添加该分包,否则编译找不到模块:

json

{
  "name": "hsp_note_editor",
  "version": "1.0.0",
  "scope": "shared"
}

三、HSP 跳转路由工具完整实现(承接上文 RouterUtil)

har_base/utils/router_util.ets补充 HSP 跳转方法,内置登录拦截、异常捕获:

ets

// HSP页面跳转
pushHsp(hspName: string, pagePath: string, params?: Object) {
  // 登录拦截,未登录禁止进入分包页面
  if (!this.checkLogin()) return;
  try {
    router.pushNamedRoute({
      bundleName: this.ctx?.bundleName,
      moduleName: hspName,
      pagePath: pagePath,
      params
    })
  } catch (e) {
    LogUtil.error("RouterUtil", "HSP页面跳转失败", e);
    DialogUtil.alert({ content: "编辑器加载失败,请重试" });
  }
}

// 卸载HSP分包,释放内存
async unloadHsp(hspName: string) {
  try {
    await router.unloadNamedRoute(hspName);
    LogUtil.info("RouterUtil", `分包${hspName}卸载完成`);
  } catch (e) {
    LogUtil.warn("RouterUtil", "分包卸载异常", e);
  }
}

四、HSP 编辑器页面完整代码(hsp_note_editor/pages/EditorPage.ets)

ets

import ThemeUtil from '@ohos:har_base/utils/theme'
import DialogUtil from '@ohos:har_base/utils/dialog_util'
import RouterUtil from '@ohos:har_base/utils/router_util'
import RdbUtil from '@ohos:har_base/utils/rdb_util'
import LogUtil from '@ohos:har_base/utils/log_util'

@Entry
@Component
struct EditorPage {
  @State title: string = ""
  @State content: string = ""
  // 接收列表页传递的笔记id,新增为0
  @State noteId: number = 0

  aboutToAppear() {
    // 获取路由传递参数
    const params = router.getParams() as { id?: number }
    if (params.id && params.id > 0) {
      this.noteId = params.id
      this.loadEditNote()
    }
  }

  // 编辑模式:回填原有笔记数据
  async loadEditNote() {
    const list = await RdbUtil.queryNoteList(0, 100);
    const target = list.find(item => item.id === this.noteId);
    if (target) {
      this.title = target.title
      this.content = target.content
    }
  }

  // 保存笔记
  async saveNote() {
    if (!this.title.trim()) {
      DialogUtil.alert({ content: "标题不能为空" })
      return
    }
    try {
      if (this.noteId > 0) {
        // 更新已有笔记
        await RdbUtil.updateNote(this.noteId, this.title, this.content)
      } else {
        // 新增笔记
        await RdbUtil.insertNote(this.title, this.content)
      }
      DialogUtil.alert({
        content: "保存成功",
        onConfirm: async () => {
          // 返回列表页,卸载当前分包释放内存
          RouterUtil.back()
          await RouterUtil.unloadHsp("hsp_note_editor")
        }
      })
    } catch (err) {
      LogUtil.error("EditorPage", "保存笔记失败", err)
      DialogUtil.alert({ content: "保存失败,请重试" })
    }
  }

  // 页面销毁强制卸载分包,防止内存残留
  async aboutToDisappear() {
    DialogUtil.closeAllDialog()
    await RouterUtil.unloadHsp("hsp_note_editor")
  }

  build() {
    const color = ThemeUtil.getColor()
    const size = ThemeUtil.getSize()
    Column({ space: size.gapLg })
      .width("100%")
      .height("100%")
      .padding(size.gapMd)
      .backgroundColor(color.pageBg)
    {
      Text(this.noteId > 0 ? "编辑笔记" : "新建笔记")
        .fontSize(size.fontTitle)
        .fontColor(color.textPrimary)

      TextInput({ text: this.title, placeholder: "请输入笔记标题" })
        .width("100%")
        .height(size.btnNormal)
        .fontSize(size.fontMain)
        .backgroundColor(color.cardBg)
        .borderRadius(size.radiusSm)
        .onChange((val: string) => this.title = val)

      TextArea({ text: this.content, placeholder: "请输入笔记内容" })
        .width("100%")
        .layoutWeight(1)
        .fontSize(size.fontAux)
        .backgroundColor(color.cardBg)
        .borderRadius(size.radiusSm)
        .onChange((val: string) => this.content = val)

      Button("保存笔记")
        .width("100%")
        .height(size.btnNormal)
        .backgroundColor(color.primary)
        .borderRadius(size.radiusSm)
        .onClick(() => this.saveNote())
    }
  }
}

五、entry 首页跳转 HSP 调用示例

ets

import RouterUtil from '@ohos:har_base/utils/router_util'
import ThemeUtil from '@ohos:har_base/utils/theme'

@Entry
@Component
struct IndexPage {
  build() {
    const color = ThemeUtil.getColor()
    const size = ThemeUtil.getSize()
    Column({ space: size.gapLg })
      .width("100%")
      .height("100%")
      .padding(size.gapMd)
      .backgroundColor(color.pageBg)
    {
      Text("笔记管理首页")
        .fontSize(size.fontTitle)
        .fontColor(color.textPrimary)

      Button("新建笔记")
        .width("100%")
        .height(size.btnNormal)
        .backgroundColor(color.primary)
        .borderRadius(size.radiusSm)
        .onClick(() => {
          // 跳转HSP,传参id=0代表新建
          RouterUtil.pushHsp("hsp_note_editor", "src/main/ets/pages/EditorPage", { id: 0 })
        })

      Button("查看笔记列表")
        .width("100%")
        .height(size.btnNormal)
        .backgroundColor(color.success)
        .borderRadius(size.radiusSm)
        .onClick(() => RouterUtil.push("pages/note/NoteListPage"))
    }
  }
}

六、HSP 核心规范与内存优化要点

  1. 分包卸载强制规范 页面aboutToDisappear生命周期必须调用unloadNamedRoute卸载分包,否则分包代码、图片资源常驻内存,多次进出内存持续上涨。
  2. 资源隔离规范 HSP 内部图片、矢量图标添加分包专属前缀editor_,避免和 entry、HAR 资源重名打包覆盖。
  3. 禁止跨 HSP 引用 hsp_a 不能导入 hsp_b 的任何 ets 代码,跨分包交互只能通过路由传参,不能静态 import。
  4. 数据存储规范 分包不能独立封装数据库逻辑,所有 RDB 操作统一调用业务 HAR 提供的工具,分包仅做页面渲染。
  5. 全局状态规范 分包可正常使用 GlobalStore 全局状态、ThemeUtil 主题工具,底层 HAR 全局能力完全共享。

七、HSP 高频报错与解决方案

报错 1:pushNamedRoute 提示页面不存在

原因 1:HSP 模块 module.json5 中 pages 数组未注册页面路径; 原因 2:跳转 pagePath 路径拼写错误,大小写不匹配; 解决:核对 pages 配置,复制完整页面文件路径填入路由参数。

报错 2:编译提示模块找不到

原因:entry 模块 dependencies 未添加 HSP 依赖; 解决:entry 的 module.json5 添加对应 HSP 依赖,同步清理项目缓存重新编译。

报错 3:多次打开分包内存持续升高

原因:页面退出未调用 unloadHsp 卸载分包、图片 PixelMap 未释放、全局监听未解绑; 解决:aboutToDisappear 统一执行卸载分包、关闭弹窗、释放图像资源。

报错 4:HSP 无法跳转其他 HSP 页面

原因:官方限制 HSP 之间不能互相路由跳转; 解决:返回 entry 主页面,由 entry 统一调度跳转不同分包。

报错 5:分包内深色模式切换无响应

原因:分包内部缓存主题色值,未实时调用 ThemeUtil.getColor (); 解决:build 方法内直接动态获取主题,不使用 @State 缓存颜色变量。

八、文末总结

HSP 动态分包是鸿蒙项目轻量化、性能优化的核心手段,将低频重型页面独立拆分,大幅缩减主 HAP 体积、降低冷启动耗时。结合前文四层架构,业务页面拆分至 HSP、基础能力下沉 HAR,实现代码复用与按需加载双重收益。 本文完整覆盖 HSP 创建、依赖配置、路由跳转、参数传递、分包卸载全流程,配套可直接运行的编辑器页面代码,解决分包开发 90% 常见报错,适合毕设、商用项目直接落地。 下一篇将讲解 OpenHarmony 图片压缩、相册权限完整工具 ImagePickerUtil 实战。

Logo

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

更多推荐