Kotlin Multiplatform for OpenHarmony 实战:Metro 编译期 DI 的鸿蒙等价物——显式图装配
三方库适配征文 · 第二批 · 上游 ZacSweers/Metro 1.5.0
适配代码:https://atomgit.com/wdracky/kmp-ohos-adapters/tree/main/metro
摘要
本文记录 ZacSweers/Metro 1.5.0 在 OpenHarmony 上的适配实践。Metro 是 KMP 生态的编译期依赖注入框架,依赖 KSP 生成依赖图实现类,而鸿蒙侧没有 KSP。适配的核心洞察是:Metro 的生成物本质是一张显式绑定表(key → provider,单例带缓存),因此可以用手写 MetroGraphBuilder 装配的方式语义复刻,实现运行时零反射的依赖注入。文章给出 DevEco Studio 26 + Kotlin 2.1.x 的版本组合、三层架构映射(Metro 上游 Kotlin 层 / 鸿蒙语义层 / 鸿蒙引擎层)、循环依赖检测与作用域链两个关键设计,并整理踩坑记录与 6 条 FAQ,供同类编译期框架的鸿蒙适配参考。
目录
一、Metro 是什么
Metro 是 ZacSweers 的 KMP 编译期依赖注入框架:KSP 扫描 @DependencyGraph/@Provides/@SingleIn,生成依赖图实现类——零反射、增量编译友好。2025-2026 年 KMP 社区最热门的 Dagger 替代。
二、环境搭建
适配与验证基于以下版本组合,建议严格对齐,避免 KSP / 编译器版本错配导致的生成失败:
| 组件 | 版本 | 说明 |
|---|---|---|
| DevEco Studio | 26(DevEco 26) | 鸿蒙官方 IDE,本文所有 ArkTS 侧代码与真机/模拟器调试均在此版本完成 |
| HarmonyOS SDK | 随 DevEco 26 配套(API 20+) | 使用 IDE 内置 SDK Manager 安装 |
| Kotlin | 2.1.x | KMP 侧编译与 Metro 注解处理 |
| Kotlin Multiplatform (KMP) | 2.1.x | 与 Kotlin 版本保持一致 |
| KMP for OpenHarmony 插件 | 与 KMP 2.1.x 匹配的 OH 适配版本 | 负责 Kotlin → ArkTS 产物桥接 |
| Metro | 1.5.0 | 上游适配目标版本 |
| KSP | 与 Kotlin 2.1.x 对应 | Metro 编译期代码生成依赖 |
开发界面如下:

搭建步骤与官方指引:
- DevEco Studio 26 下载与安装:https://developer.huawei.com/consumer/cn/deveco-studio/
- HarmonyOS SDK 与 API 版本管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-sdk-manager
- Kotlin Multiplatform 官方文档(版本与 Gradle 配置):https://kotlinlang.org/docs/multiplatform.html
- KMP for OpenHarmony 环境配置与插件说明:https://atomgit.com/CPF-KMP-CMP
- Metro 上游仓库与版本发布记录:https://github.com/ZacSweers/Metro
提示:DevEco 26 对 ArkTS 语法校验更严格,若出现
arkts-no-any-unknown类报错,请优先检查绑定表是否使用了any,改用显式泛型或字符串 key 方案。
二、适配路线与核心难题
Metro 的价值在编译期代码生成,而鸿蒙没有 KSP。适配的第一步是想清楚:生成物是什么?
Metro 生成的图实现类,本质是一张显式绑定表:key → provider(构造调用),单例带缓存。鸿蒙的等价物就是手写这张表:
const graph = new MetroGraphBuilder()
.singleton<Logger>('Logger', () => new Logger())
.singleton<Clock>('Clock', () => new Clock())
.provider<UserRepository>('UserRepository',
() => new UserRepository(graph.get<Logger>('Logger'), graph.get<Clock>('Clock')))
.build();
这正是 Metro 生成的代码逐行展开后的样子——显式图装配 = 生成代码的等价物。
整体适配路线如下:
三、三层架构
| Metro(Kotlin) | 鸿蒙语义层 | 鸿蒙引擎层 |
|---|---|---|
Provider<T> | type Provider<T> = () => T | — |
@SingleIn(AppScope) | builder.singleton(key, provider) | — |
@Provides(无作用域) | builder.provider(key, provider) | — |
@DependencyGraph(AppScope) | DependencyGraph | OhosMetroRuntime.configure() |
@DependencyGraph(ActivityScope, parent) | ScopedGraph(parent 链) | createActivityScope() |
graph.create<T>() | graph.get<T>(key) | metroGet<T>() |
| 编译期循环依赖诊断 | 运行时 MetroError(cycle) | — |
三层职责划分如下:
四、三个关键设计
1. 循环依赖检测。上游在编译期诊断;鸿蒙在解析时用解析栈跟踪:
if (this.resolving.indexOf(key) >= 0) {
throw new MetroError('cycle', '循环依赖检测:' + this.resolving.join(' -> ') + ' -> ' + key);
}
A→B→A 立即抛出,错误信息带完整解析路径。
2. 作用域链。ScopedGraph 子图未命中向上查找父图,对应 @SingleIn(ActivityScope) 依赖 AppScope 绑定;绑定惰性 build,避免空图。
3. 注解文档化。@Injectable→显式绑定、@Inject→graph.get()——把 Metro 注解语义固化为 ArkTS 侧的编写规范(MetroInjectionAnnotations.USAGE),团队照表施工即可。
循环依赖检测与作用域链查找的流程如下:
五、踩坑记录
- 泛型 key 的擦除:ArkTS 泛型在运行时不保留,绑定表用字符串 key(
'Logger')——上游用类型做 key,鸿蒙用「类型名」做 key,取用时metroGet<Logger>(graph, 'Logger')显式声明,类型安全不丢。 - provider 里的 this 引用:UserRepository 的 provider 引用 graph 自身,ArkTS 要求先声明后使用——用
graph.get()在 provider 内部惰性解析,构造顺序天然正确。 - ScopedGraph 的惰性装配:最初在构造时就 build 空图,导致后注册的绑定丢失——改为首次解析时 ensureGraph()。
六、FAQ
Q1:DevEco 26 下 KMP 插件加载失败怎么办?
先确认 IDE 版本与插件版本匹配,再检查 Settings → Plugins 中 KMP for OpenHarmony 是否启用;若仍失败,清理 ~/.gradle/caches 与项目 .hvigor 缓存后重新 Sync。
Q2:Kotlin 与 KSP 版本必须完全一致吗?
KSP 版本号形如 2.1.x-1.0.x,前半段必须与 Kotlin 版本严格对应,否则 Metro 的符号处理会直接报错。
Q3:为什么绑定表要用字符串 key,而不是类型 key?
ArkTS 运行时泛型被擦除,无法用类型做 Map 的 key。用「类型名」字符串做 key,再在取用处 metroGet<Logger>(graph, 'Logger') 显式声明类型,兼顾运行时可查与编译期类型安全。
Q4:循环依赖为什么不在编译期报错?
鸿蒙侧没有 KSP,无法在编译期构建完整依赖图,因此改为运行时解析栈检测,错误信息会带上完整解析路径,定位成本与编译期诊断接近。
Q5:ScopedGraph 子图拿不到父图绑定?
确认创建子图时传入了 parent,且父图已完成绑定注册;子图采用惰性装配,首次解析时才 build,构造阶段不要提前调用 ensureGraph()。
Q6:DevEco 26 真机调试时 DI 初始化时机?
建议在 AbilityStage.onCreate 或 EntryAbility.onCreate 中调用 OhosMetroRuntime.configure(),早于任何页面与 Service 的依赖解析。
六、总结
Metro 的鸿蒙适配证明了「编译期框架也可以语义复刻」:生成物的本质是显式绑定表,把 KSP 的工作变成「照表施工」,运行时零反射、循环可检测、作用域可继承。本系列已有 kotlin-inject/Koin/Kodein-DI 三个 DI 适配,Metro 是第四块拼图,也补上了「多作用域」语义。
系列导读:本文为「Kotlin Multiplatform for OpenHarmony 实战」第 34 篇。全部代码与博客见 https://atomgit.com/wdracky/kmp-ohos-adapters 。
欢迎加入 KMP&CMP 鸿蒙社区:https://atomgit.com/CPF-KMP-CMP
【AI 工具推荐】AtomCode 专属邀请链接:https://atomgit.com/dashboard/atomcode?utm_source=av&isLogin=99
一句话总结:鸿蒙没有 KSP,但不需要 KSP——把 Metro 的编译期生成物还原成一张显式绑定表,用 MetroGraphBuilder 手写装配,就能在 ArkTS 侧拿到零反射、可检测循环、可继承作用域的依赖注入能力。适配的关键不在「移植代码生成器」,而在「读懂生成物、复刻其语义」。
更多推荐



所有评论(0)