HarmonyKit | 鸿蒙开发:hvigor 缓存机制与问题排查手册

HarmonyKit | 鸿蒙开发:hvigor 缓存机制与问题排查手册
引言:缓存是性能的源泉,也是问题的温床
HarmonyKit 开发过程中,约有 20% 的"诡异 bug"最终定位到了 hvigor 缓存问题。这类 bug 的共同特征是:代码逻辑完全正确、语法没有问题、昨天还能正常构建、今天突然就报错了。当你遇到这种"变量型" bug(issue only reproducible sometimes),第一个怀疑对象就应该是缓存。
这篇文章基于 HarmonyKit 项目中实际遇到并解决的 7 个缓存相关案例,系统梳理 hvigor 的缓存体系、缓存失效的触发条件、排查步骤和清理策略。
项目仓库:https://atomgit.com/VON-/harmony-kit
hvigor 缓存体系全景
hvigor 的缓存分布在三个位置,各自缓存不同类型的数据:
第一层:.hvigor/cache/(构建引擎缓存)

.hvigor/cache/
├── file-cache.json # 源文件哈希缓存
├── last-build-info.json # 上次构建的快照信息
├── project-config.json # 项目配置的解析结果缓存
├── task-cache.json # 构建任务的执行结果缓存
└── meta.json # 缓存版本和兼容性元数据
这层缓存的职责是"避免重复工作"。如果文件 A 在上次构建后没有变化,hvigor 从 file-cache.json 中检测到哈希值一致,直接跳过对该文件的编译——即使构建模式从 debug 切换到 release(只要文件本身没变)。
last-build-info.json 记录了上次构建的环境快照:SDK 版本、模块列表、构建模式、时间戳。每次新构建开始时,hvigor 对比当前环境与快照,判断是否需要进行某些全局性的重新处理(如重新解析依赖图、重新处理所有资源)。
第二层:.hvigor/dependencyMap/(依赖图缓存)

.hvigor/dependencyMap/
├── oh-package.json5 # 项目级依赖的解析结果
├── dependencyMap.json5 # 模块间依赖关系图
└── entry/
└── oh-package.json5 # entry 模块的依赖解析结果
依赖图缓存的职责是"快速确定受影响范围"。当文件 A 被标记为"脏"(因为内容变化),hvigor 查询依赖图找出所有直接或间接 import 文件 A 的其他文件,将这些文件也标记为"脏"。
依赖图缓存的重要性:HarmonyKit 的 ToolItem.ets 被 Index.ets 和 ToolCard.ets 引用。没有依赖图缓存,hvigor 需要通过 AST 分析每个文件的 import 关系来判断影响范围——这在 20+ 个文件的项目中可能很快,但在 200+ 个文件的项目中就变得昂贵了。
第三层:.hvigor/outputs/records/(构建记录缓存)

.hvigor/outputs/records/
└── performance-recorder.json # 构建性能追踪记录
这个文件记录了每次构建的各阶段耗时,用于性能分析和 regressions 检测。它不直接影响构建正确性,但对性能优化很有价值。
第四层:.hvigor/report/(构建报告)

.hvigor/report/
├── report-202607051507059470.json
├── report-202607051502171150.json
└── ...
每次构建生成一个报告文件,包含构建过程的详细信息。主要用于 CI 环境的构建结果分析和历史对比。本地开发时一般不需要关注。
缓存失效的五种触发场景
场景一:修改 hvigor-config.json5
任何对 hvigor/hvigor-config.json5 的修改都应该触发缓存重置。但实际行为取决于修改的内容:
- 修改
execution.parallel、execution.typeCheck:不影响文件缓存和依赖图,但影响编译过程的执行方式。hvigor 通常能正确处理增量。 - 修改
modelVersion:标记大版本变更,hvigor 应自动执行全量重建。 - 修改
dependencies:影响插件加载,需要重启 daemon 才能生效。
最佳实践:修改 hvigor-config.json5 后,手动执行一次 hvigorw clean && hvigorw assembleHap。不要依赖 hvigor 的自动缓存失效判断。
场景二:切换 SDK 版本
从 API 18 切换到 API 22 时,所有缓存的类型信息和编译配置都过时了。hvigor 理论上应该检测到 targetSdkVersion 的变化并触发全量重建,但在实际中不总是可靠。
HarmonyKit 的迁经验:切换到 API 22 时,第一次增量编译通过了,但运行时出现了 API 行为变化导致的崩溃。执行全量清理重建后问题消失。
rm -rf .hvigor entry/build
hvigorw --no-daemon assembleHap
场景三:重命名或移动源文件
如果你重命名了 RegexTester.ets 为 RegexTool.ets,hvigor 的缓存中仍然记录着旧文件 RegexTester.ets 的哈希值和编译状态。新的 RegexTool.ets 被当作"新文件"编译,但旧的缓存记录可能会导致:
- 编译器报错"找不到
pages/tools/RegexTester"——因为main_pages.json仍然引用旧路径 - 依赖图缓存过时——引用
RegexTester的 import 语句没有被更新
最佳实践:文件重命名后,在 IDE 中使用"Refactor > Rename"功能(支持的话),或者手动执行一次 hvigorw clean。
场景四:Git 分支切换后构建异常
当你在 feature 分支上修改了多个文件,切换到 develop 分支时文件内容变了,但 .hvigor/cache/ 中的哈希值还是 feature 分支的。hvigor 通过文件修改时间戳辅助判断——切换分支会更新文件时间戳,通常能正确触发重编译。
但偶发情况下,Git 操作可能保留文件时间戳(取决于 git checkout 的行为和文件系统实现),导致 hvigor 误判文件未变化。
最佳实践:
git checkout develop
hvigorw clean
hvigorw assembleHap
场景五:异常中断的构建
如果在构建过程中按下 Ctrl+C 强制中断,或者在 DevEco Studio 中点了 Stop,部分中间文件可能处于不一致状态。典型的例子:
entry/build/intermediates/中的中间产物只写了一半.hvigor/cache/task-cache.json记录了某个任务"执行中"但实际已中断- daemon 进程仍然运行但内部状态不一致
这种情况的症状是"上次能构建,Ctrl+C 后就不行了"。
修复方案:
# 第一级修复
hvigorw clean
hvigorw assembleHap
# 如果仍然失败,第二级修复
rm -rf .hvigor entry/build
hvigorw --stop # 停止 daemon
hvigorw --no-daemon assembleHap
最致命的缓存问题:.hvigor 递归生成
这是 HarmonyKit 开发中遇到的最诡异也最具破坏性的缓存问题。
现象
在 entry/src/main/ets/pages/tools/ 目录下,出现了一个 .hvigor 子目录,其中又包含 cache/、dependencyMap/ 等结构。更糟糕的情况:.hvigor 目录递归嵌套——.hvigor 里面还有个 .hvigor,导致目录树无限扩展,最终触发文件系统的递归限制,编译报错 Too many open files。
根因
hvigor daemon 在扫描源码目录构建依赖图时,需要确定"哪些目录是模块源码目录"和"哪些目录是缓存目录"。正常情况下,daemon 从项目根目录的 build-profile.json5 中读取 modules[].srcPath 来确定源码目录。
但当 daemon 进程异常重启,或者两个 DevEco Studio 实例同时运行时,daemon 的工作目录可能被错误设置。如果 daemon 的当前工作目录指向了 entry/src/main/ets/ 而不是项目根目录,它将源码目录识别为 ./(即当前目录),将缓存输出到 ./.hvigor/(即 entry/src/main/ets/.hvigor/)。
这之后,下一次构建时 daemon 扫描源码目录发现 .hvigor 子目录,将其中的文件也纳入编译范围——包括缓存文件(JSON)和可能被递归包含的更多文件。
排查与修复
# 1. 定位所有不在项目根目录的 .hvigor 目录
find entry/ -name ".hvigor" -type d
# 2. 删除所有错误的 .hvigor 目录
find entry/ -name ".hvigor" -type d -exec rm -rf {} +
# 3. 检查是否有递归嵌套导致的超大深层目录
find entry/ -maxdepth 50 -type d | wc -l
# 如果行数异常大(超过几百),说明可能仍有残留
# 4. 彻底清理并重建
rm -rf .hvigor entry/build
hvigorw --stop
killall node # 极端情况,杀掉所有 Node 进程
hvigorw --no-daemon clean
hvigorw assembleHap
预防
- 及时清理过期 daemon:开发完成后用
hvigorw --stop停止 daemon,不要让它一直在后台运行 - 避免多实例:不要同时打开两个 DevEco Studio 窗口指向同一个项目
- 定期全量重建:每周执行一次
rm -rf .hvigor entry/build && hvigorw assembleHap可以避免缓存问题的积累
缓存问题排查的通用流程
遇到"代码明明对但构建失败"的问题,按以下步骤排查:
Step 1: 确认不是代码问题
# 检查语法是否完全正确
# 在 DevEco Studio 中查看 Problems 面板
# 确认不是 ArkTS 严格模式导致的类型错误
Step 2: 清理应用级缓存(尝试保留 daemon)
hvigorw clean
hvigorw assembleHap
Step 3: 清理所有缓存(包括 daemon)
rm -rf .hvigor entry/build
hvigorw --stop
hvigorw --no-daemon assembleHap
Step 4: 终极清理(完全从零开始)
rm -rf .hvigor entry/build oh_modules/.hvigor .idea
hvigorw --stop
killall node
ohpm install
hvigorw --no-daemon assembleHap
Step 5: 检查环境问题
# 验证 SDK 安装是否正确
hdc list targets
# 验证 Node.js 版本
node --version
# 验证 ohpm 版本
ohpm --version
# 检查磁盘空间
df -h .
大多数缓存问题在 Step 2 或 Step 3 就能解决。如果问题持续到 Step 4 仍然存在,那很可能不是缓存问题,需要回到代码或环境检查。
build 目录:什么时候该删
entry/build/ 目录包含的是构建产物,而非缓存。删除它是安全且常见的操作:
rm -rf entry/build
应该删除 build/ 的场景:
- 切换 buildMode(debug → release 或反过来)
- 修改了
module.json5中的配置(如 abilities、permissions) - 修改了资源文件(
resources/目录下的内容) - 修改了签名配置
- 构建后的 HAP 安装到设备上行为异常
不需要删除 build/ 的场景:
- 仅修改了
.ets源文件(增量编译会正确处理) - 仅修改了
main_pages.json中的页面列表
删除 build/ vs 删除 .hvigor/ 的区别:
- 删除
build/:清理构建产物,但保留编译缓存(下次构建回退到增量编译的"缓存有效但产物缺失"状态,重新生成所有中间和最终产物,但不需要重新编译未变化的源文件) - 删除
.hvigor/:清理编译缓存(下次构建是全量编译,所有文件重新编译) - 删除两者:完全的"从零开始"构建
对于日常开发,"先试 hvigorw clean,不行再删 .hvigor/"是最有效率的排查路径。
缓存与 CI/CD 的交互
在 CI/CD 环境中,缓存的策略不同:
应该跨构建保留的缓存
- oh_modules/:如果
oh-package.json5没有变化,复用已安装的依赖可以节省安装时间 - ~/.ohpm/cache/:ohpm 的全局缓存目录(位于用户目录下)
应该每次构建清理的缓存
- .hvigor/cache/:CI 环境中的文件路径可能每次都不同(如临时目录)
- entry/build/:CI 的每次构建都应该是干净的
CI 配置示例:
- name: Build
run: |
# 清理构建产物(保留依赖缓存)
rm -rf entry/build .hvigor/cache
# 如果 oh-package.json5 变化了才重装依赖
ohpm install
# 全量干净构建
hvigorw --no-daemon assembleHap -p buildMode=release
CI 中使用 --no-daemon 是因为每次 CI 运行结束后环境被销毁,daemon 的常驻没有意义,反而可能因为残留进程导致下一次 CI 运行出现问题。
缓存规模与项目规模的关系
HarmonyKit 是一个小型项目(约 25 个 .ets 文件,~2000 行代码),缓存体积不到 5MB。但大型鸿蒙项目的缓存体积可能达到数百 MB。
缓存规模和项目规模的对应关系(经验值):
| 项目规模 | .ets 文件数 | .hvigor/ 体积 | 构建时间(全量) | 构建时间(增量) |
|---|---|---|---|---|
| 小型(如 HarmonyKit) | < 50 | < 10 MB | ~15s | ~4s |
| 中型 | 50-200 | 10-50 MB | ~30s | ~8s |
| 大型 | 200-1000 | 50-200 MB | ~90s | ~20s |
| 超大型 | > 1000 | > 200 MB | 2-5 min | ~40s |
对于中型及以上项目,缓存的重要性显著提升——全量构建可能需要 90 秒甚至更久,增量编译是日常开发体验的生命线。因此,正确的缓存管理在大型项目中不是"可选优化",而是"开发可行性"的前提。
结语
缓存问题是所有构建系统都面临的经典难题——你既要缓存来提高性能,又因为缓存引入了一致性问题。hvigor 作为相对年轻的构建系统,在缓存策略上仍在迭代优化。
对开发者来说,最重要的不是记住每个缓存文件的作用,而是建立一套排查习惯:遇到构建问题先问"是不是缓存的问题?",按清理层级从"最轻"到"最重"逐步排查。大多数时候,hvigorw clean 就是你需要的全部修复。
更多推荐

所有评论(0)