Appearance
Koin 依赖注入与 4.0 迁移
回到总览:状态管理、路由、架构相关模块:委托属性(
by inject()是属性委托的经典用例,Koin 4.0 已弃用)· Gradle 生命周期(Koin 4.0 基于 Kotlin 2.0 / K2 编译器)
一句话定义
Koin 是纯 Kotlin 写的轻量依赖注入(DI)框架,用 DSL 声明「模块 → 定义(single / factory / viewModel)」,运行时按类型解析依赖图。4.0 是其基于 Kotlin 2.0 的重写版:统一多平台 ViewModel API、强化 Verify() 测试、并删除一批旧 API。
代码索引
本主题计划在 Kotlin 2.0 host 上升级后补可运行 lab(见文末「对应实验」)。当前文档以最小代码示例说明机制。
为什么需要
- 为什么 10 年 Android 工程师还要关心 DI 框架,而不是手写
new?- 一句话答:手写构造把依赖关系硬编码进调用方,改动一处要改一片、难测试;DI 把「谁提供、谁消费」声明化,便于替换实现、做单元测试与跨模块解耦(详见 生产参考架构 的 DI 对比)。
- 为什么 Koin 4.0 强制要求 Kotlin 2.0.20+,否则会崩溃?
- 一句话答:4.0 用了 Kotlin 2.0 新增的
kotlin.uuid.Uuid做跨平台唯一 ID(KoinPlatformTools.generateId()),在 <2.0.20 上没有该 API,运行期直接崩溃。升级 Koin 前先升级 Kotlin。
- 一句话答:4.0 用了 Kotlin 2.0 新增的
- 为什么 Koin 4.0 把
inject()/getViewModel()等标成 error 级弃用?- 一句话答:4.0 把 ViewModel API 统一到多平台包
koin-core-viewmodel,旧 API 与新架构冲突;编译器直接报错逼你迁移到koinInject()/koinViewModel(),避免新旧混用。
- 一句话答:4.0 把 ViewModel API 统一到多平台包
1. 实现原理
1. 模块与定义:single / factory / viewModel
概念先于代码:模块(
module { })是一组定义;single全图唯一实例,factory每次新建,viewModel绑定到作用域(Activity / 导航)。解析时 Koin 按「被请求类型」在图中查找定义并构造。
kotlin
val appModule = module {
single { HttpClient(get()) } // 全图单例
factory { UserService(get()) } // 每次注入新建
viewModel { HomeViewModel(get()) } // 多平台 ViewModel 定义
}
startKoin {
modules(appModule)
}
// 消费端(非 Compose):val userService: UserService by inject() // 旧;4.0 用 koinInject()- 可能执行顺序
startKoin { modules(...) }把所有定义登记进全局图。- 首次请求
UserService:Koin 找到factory定义,递归先解析其依赖HttpClient(single,构造一次后复用),再构造UserService。 - 后续请求
UserService:因是factory,再次新建实例;HttpClient复用同一单例。
- 预期现象:单例依赖在整张图里只构造一次;
factory每次产生新对象。这正是与手动object/DCL 相比,「声明即生效、关系可替换」的价值。 - 观察重点:依赖解析是按类型的,定义里的
get()是「向图请求该类型实例」的占位,不是直接 new。
2. Koin 4.0 的 ViewModel API 统一(多平台)
4.0 把 Android / Compose / 其他框架的 ViewModel API 收进 koin-core-viewmodel(及 -navigation),旧 DSL 弃用。迁移成本很低——只改 import 到 org.koin.core.module.dsl.*:
kotlin
// 4.0 写法
import org.koin.core.module.dsl.*
val vmModule = module {
viewModelOf(::HomeViewModel) // 推荐:singleOf / viewModelOf 系列
// 旧:viewModel { HomeViewModel(get()) } // 已弃用
}
// Compose 消费端
@Composable fun Home() {
val vm: HomeViewModel = koinViewModel() // 取代旧 getViewModel()
}3. Verify() 测试 API(4.0 强化)
4.0 的 verify() 可校验模块定义完整性,并支持「带注入参数的定义」校验,还能自动给出缺失定义的修复建议:
kotlin
class AppTest {
@Test fun checkModules() = runTest {
appModule.verify(
injections = injectedParameters(
definition(ComponentA::class)
)
)
}
}Koin 4.0 破坏性变更速查
| 旧(弃用/移除) | 新 | 级别 |
|---|---|---|
inject() | koinInject() | error |
getViewModel() | koinViewModel() | error |
rememberKoinInject() | koinInject() | error |
koinNavViewModel() | koinViewModel() | 弃用 |
checkModules 整套 | Verify() | 弃用 |
stateViewModel() / getSharedStateViewModel() | viewModel() / activityViewModel() | error |
其他内部移除:
@KoinReflectAPI全部 API、旧KoinContextHandler→GlobalContext、旧时间 API → Kotlin Time API。
Koin-Fu(实验性,前瞻)
Koin 团队正在用 Koin-Fu 重做 DSL:目标是去掉 get() 调用、支持可空参数、提供统一的 singleOf 体验。当前仍实验性,生产勿用,但值得关注方向——它想解决「构造函数 DSL 必须静态组合类型、从而无法传可空参数」的固有限制。
Android / Flutter / Web / Backend 对照
- Android / Kotlin:Koin 与 Hilt 是两条 DI 主线。Hilt 编译期代码生成、与 Android 组件生命周期绑定更深;Koin 运行时解析、更轻、跨平台(KMP/Compose Multiplatform)天然友好(详见 生产参考架构 DI 对比)。
- Flutter / Dart:Dart 无官方 DI 框架级方案,常见用
get_it/provider/riverpod;迁移时要意识到 Koin 的「模块 + 类型解析」在 Dart 侧需换成这些容器的「注册 token + 作用域」。 - Web / Backend:Ktor + Koin 是常见后端组合;非主线,对照了解即可。
2. 特点与优势
- Koin vs Hilt:Koin 运行时解析、零注解处理器、KMP 友好;Hilt 编译期生成、与 Android 生命周期深绑定、样板更少但更「Android 中心」。选型见 生产参考架构。
singlevsfactory:前者全图单例(注意线程安全与状态共享),后者每次新建(无状态或短生命周期对象)。by inject()vskoinInject():前者是属性委托(缓存语义),后者是函数调用;Compose 里用后者更可控(见 委托属性)。
3. 用法
1. 多模块工程拆分 module
kotlin
// core 模块
val coreModule = module { single { Database() } }
// feature 模块
val featureModule = module { viewModel { DetailViewModel(get()) } }
startKoin { modules(coreModule, featureModule) }2. AndroidX Startup 加速启动(4.0 新增)
4.0 的 koin-androidx-startup 用 AndroidX Startup 拉起 Koin,官方称加载最多快 40%;可与 Lazy Modules 叠加:
kotlin
// gradle.properties / build 引入 koin-androidx-startup 后
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
initKoin { /* onKoinStartup 委托声明启动配置 */ }
}
}常见误配、事故后果与排障
1. 事故:升级 Koin 4.0 但 Kotlin 仍是 1.9 → 运行期崩溃
- 误配原因:没同步升 Kotlin 到 2.0.20+,
kotlin.uuid.Uuid不存在。 - 后果:
KoinPlatformTools.generateId()抛NoClassDefFoundError/ 崩溃。 - 排障与修法:先升 Kotlin 到 2.0.20+,再升 Koin 4.0;CI 里把 Kotlin 版本写死进
libs.versions.toml统一约束。
2. 误配:新旧 ViewModel API 混用导致编译报错
- 排障与修法:全局把
import ...old.viewmodel.*换成org.koin.core.module.dsl.*;用koinViewModel()替代getViewModel()。 IDE 的「Replace in Files」+ 编译报错清单可批量消掉。
3. 误配:Verify() 报缺失定义
- 排障与修法:4.0 的
verify()会自动建议缺失定义;按提示在 module 里补single/factory,或用includes(otherModule)把漏掉的模块并进待校验集合。
对应实验
计划补齐的实验(当前 Kotlin host 为 1.9.24,Koin 4.0 需 Kotlin 2.0.20+,待 host 升级 Kotlin 后落地):
labs/kotlin/host/di/koin-4-basics/— module +single/factory/viewModel+koinInject()+Verify()的最小可运行验证- 升级路径:将
labs/kotlin/host/build.gradle.kts的 Kotlin 升到 2.0.20+,并引入io.insert-koin:koin-core/koin-test后补该 lab
复习检查题
Koin 4.0 为什么要求 Kotlin 2.0.20+?用低版本会怎样? 答:4.0 用 Kotlin 2.0 新增的
kotlin.uuid.Uuid生成跨平台唯一 ID;低于 2.0.20 没有该 API,运行期KoinPlatformTools.generateId()崩溃。升级 Koin 前必须先升 Kotlin。single与factory的核心区别?什么对象该用factory? 答:single全图唯一实例,factory每次注入新建。无状态、短生命周期、或每次需要新实例的对象(如每次请求的 UseCase)用factory;需共享状态/资源的用single。Koin 4.0 把
by inject()改成了什么?为什么? 答:改成函数式koinInject()(Compose 内为koinViewModel())。因为属性委托的「首次访问缓存」语义在 Compose 重组模型里不如函数调用可控(详见 委托属性)。Verify()相对旧的checkModules强在哪? 答:Verify()支持带注入参数(injectedParameters)的定义校验,并能自动给出缺失定义的修复建议;旧的checkModules已在 4.0 弃用。
速记
- 基线:Koin 4.0 = Kotlin 2.0.20+,先升 Kotlin 再升 Koin。
- 迁移:
inject()→koinInject()、getViewModel()→koinViewModel()、checkModules→Verify(),多半只改 import。 - ViewModel API:4.0 统一进
koin-core-viewmodel,import 用org.koin.core.module.dsl.*。 - 启动:
koin-androidx-startup最多快 40%,可叠 Lazy Modules。 - Koin-Fu:实验性 DSL 重构,关注但不上生产。