Koin核心概念:Module、Single、Factory、Scoped的定义与使用
依赖注入框架,说白了就是帮我们管理对象的创建和生命周期。Koin作为轻量级方案,它的核心就四个概念:Module、Single、Factory、Scoped。嗯,这四个东西搞明白了,Koin你就掌握了八成。
我记得刚接触Koin时,觉得它比Dagger Hilt简单太多了。没有注解处理器,没有代码生成,纯Kotlin DSL搞定一切。但简单归简单,核心概念理解不透彻,照样会踩坑。
Module:你的依赖清单
Module就是用来声明依赖的地方。你可以把它理解成一个清单,告诉Koin:「嘿,这些对象由你来管理」。
val appModule = module {
// 在这里声明依赖
single { Retrofit.Builder().build() }
factory { UserRepository(get()) }
}
每个Module可以包含多个依赖声明。我个人习惯按功能模块拆分,比如网络模块一个Module,数据库模块一个Module。这样结构清晰,也好维护。
Single:全局唯一实例
Single创建的对象,在整个应用生命周期内只有一个实例。说白了就是单例模式,但由Koin帮你管理。
val networkModule = module {
single { Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
single { ApiService(get()) }
}
什么时候用Single?
- 全局共享的配置对象,比如Retrofit、OkHttpClient
- 无状态的服务类
- 创建成本高的对象
Factory:每次都是新的
Factory和Single正好相反。每次请求依赖时,Koin都会创建一个新的实例。
val viewModelModule = module {
factory { UserViewModel(get()) }
factory { ProductViewModel(get()) }
}
我在项目中遇到过一个问题:有个网络请求的Response对象,我用了Single声明。结果第二次请求时,拿到的还是第一次的数据。排查了半天,才发现应该用Factory。你想想看,每次请求都是独立的数据,怎么能共享同一个实例呢?
Factory的典型场景:
- ViewModel(每个页面需要独立的实例)
- 数据持有类(比如某个列表的状态)
- 短生命周期的对象
Scoped:限定范围内的单例
Scoped是Koin里比较高级的特性。它允许你在某个作用域内保持单例,超出作用域就销毁。
val userScopeModule = module {
scope(named("user_session")) {
scoped { UserSession() }
scoped { UserPreferences(get()) }
}
}
使用Scoped需要手动管理作用域的生命周期:
// 创建作用域
val sessionScope = getKoin().createScope("session_1", named("user_session"))
// 获取作用域内的依赖
val session = sessionScope.get<UserSession>()
// 销毁作用域
sessionScope.close()
为什么会需要Scoped?举个例子:用户登录后,我们需要在整个会话期间共享UserSession对象。但用户退出登录后,这个对象就应该被销毁。用Single的话,退出登录后对象还在内存里,可能造成数据泄露。用Factory的话,每次获取都是新对象,没法共享状态。Scoped正好解决了这个问题。
- Single:全局唯一,应用生命周期
- Factory:每次新实例,无生命周期管理
- Scoped:作用域内唯一,作用域生命周期
四种声明方式对比
| 特性 | Single | Factory | Scoped |
|---|---|---|---|
| 实例数量 | 1个 | 每次新创建 | 作用域内1个 |
| 生命周期 | 应用级别 | 无管理 | 作用域级别 |
| 内存占用 | 常驻内存 | 用完即回收 | 作用域内常驻 |
| 适用场景 | 全局配置、无状态服务 | ViewModel、数据对象 | 用户会话、Activity/Fragment范围 |
实战中的选择策略
嗯,这里我分享一个实战中的选择策略。我曾经接手过一个项目,里面所有依赖都用Single声明。结果呢?内存泄漏、数据错乱,各种问题。后来我总结了一套选择规则:
- 先问自己:这个对象需要共享状态吗?不需要?用Factory。
- 需要共享状态,但只在某个范围内?用Scoped。
- 全局共享,且生命周期和应用一致?用Single。
说白了就是:能用Factory就别用Single,能用Scoped就别用Single。Single虽然方便,但滥用会导致内存问题。
知识体系图
下面这张图展示了Koin四种声明方式的核心关系:
写在最后
Koin的这四个核心概念,说白了就是帮你管理对象的「生老病死」。Single是长生不老,Factory是随用随弃,Scoped是限定范围。Module则是你给Koin的「管理清单」。
我个人建议,刚开始用Koin时,先从Single和Factory入手。等项目复杂了,再引入Scoped。别一上来就搞复杂的设计,容易把自己绕进去。
记住一点:依赖注入的目的是让代码更清晰、更易测试。如果某个设计让你觉得别扭,那很可能就是过度设计了。
公众号:蓝海资料掘金营,微信deep3321