Skip to content

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。
  • 为什么 Koin 4.0 把 inject() / getViewModel() 等标成 error 级弃用?
    • 一句话答:4.0 把 ViewModel API 统一到多平台包 koin-core-viewmodel,旧 API 与新架构冲突;编译器直接报错逼你迁移到 koinInject() / koinViewModel(),避免新旧混用。

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()
  • 可能执行顺序
    1. startKoin { modules(...) } 把所有定义登记进全局图。
    2. 首次请求 UserService:Koin 找到 factory 定义,递归先解析其依赖 HttpClient(single,构造一次后复用),再构造 UserService。
    3. 后续请求 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 中心」。选型见 生产参考架构。
  • single vs factory:前者全图单例(注意线程安全与状态共享),后者每次新建(无状态或短生命周期对象)。
  • by inject() vs koinInject():前者是属性委托(缓存语义),后者是函数调用;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

复习检查题 ​

  1. Koin 4.0 为什么要求 Kotlin 2.0.20+?用低版本会怎样? 答:4.0 用 Kotlin 2.0 新增的 kotlin.uuid.Uuid 生成跨平台唯一 ID;低于 2.0.20 没有该 API,运行期 KoinPlatformTools.generateId() 崩溃。升级 Koin 前必须先升 Kotlin。

  2. single 与 factory 的核心区别?什么对象该用 factory? 答:single 全图唯一实例,factory 每次注入新建。无状态、短生命周期、或每次需要新实例的对象(如每次请求的 UseCase)用 factory;需共享状态/资源的用 single。

  3. Koin 4.0 把 by inject() 改成了什么?为什么? 答:改成函数式 koinInject()(Compose 内为 koinViewModel())。因为属性委托的「首次访问缓存」语义在 Compose 重组模型里不如函数调用可控(详见 委托属性)。

  4. 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 重构,关注但不上生产。

站点构建时间:2026/8/24 23:43:17