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。这样结构清晰,也好维护。

小提示:Module可以组合使用。你可以在Application中加载多个Module,Koin会自动合并它们。

Single:全局唯一实例

Single创建的对象,在整个应用生命周期内只有一个实例。说白了就是单例模式,但由Koin帮你管理。

val networkModule = module {
    single { Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    }
    
    single { ApiService(get()) }
}

什么时候用Single?

  • 全局共享的配置对象,比如Retrofit、OkHttpClient
  • 无状态的服务类
  • 创建成本高的对象
注意:Single是懒加载的。第一次获取时才创建实例,不是应用启动时就创建。如果你需要立即初始化,可以用single { ... }配合createOnStart()。

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声明。结果呢?内存泄漏、数据错乱,各种问题。后来我总结了一套选择规则:

  1. 先问自己:这个对象需要共享状态吗?不需要?用Factory。
  2. 需要共享状态,但只在某个范围内?用Scoped。
  3. 全局共享,且生命周期和应用一致?用Single。

说白了就是:能用Factory就别用Single,能用Scoped就别用Single。Single虽然方便,但滥用会导致内存问题。

避坑指南:我曾经在Fragment里用Single声明了一个列表适配器。结果多个Fragment共用了同一个适配器实例,数据全乱了。后来改成Factory,每个Fragment都有自己的适配器,问题解决。

知识体系图

下面这张图展示了Koin四种声明方式的核心关系:

Koin 四种声明方式核心关系图 Koin 容器 Single Factory Scoped Module 全局唯一 应用生命周期 常驻内存 懒加载 每次新实例 无生命周期管理 用完即回收 适合ViewModel 作用域内唯一 作用域生命周期 手动管理 适合用户会话 依赖清单 组织依赖声明 可组合使用 按功能拆分

写在最后

Koin的这四个核心概念,说白了就是帮你管理对象的「生老病死」。Single是长生不老,Factory是随用随弃,Scoped是限定范围。Module则是你给Koin的「管理清单」。

我个人建议,刚开始用Koin时,先从Single和Factory入手。等项目复杂了,再引入Scoped。别一上来就搞复杂的设计,容易把自己绕进去。

记住一点:依赖注入的目的是让代码更清晰、更易测试。如果某个设计让你觉得别扭,那很可能就是过度设计了。


公众号:蓝海资料掘金营,微信deep3321