定义
通过使用 Koin,你可以在模块中描述定义。本节我们将了解如何声明、组织和链接你的模块。
编写模块
Koin 模块是声明所有组件的空间。使用 module
函数来声明一个 Koin 模块:
val myModule = module {
// your dependencies here
}
在此模块中,你可以按如下方式声明组件。
定义单例
声明一个单例组件意味着 Koin 容器将保留你所声明组件的唯一实例。在模块中使用 single
函数来声明一个单例:
class MyService()
val myModule = module {
// declare single instance for MyService class
single { MyService() }
}
在 Lambda 表达式中定义组件
single
、factory
和 scoped
关键字帮助你通过 lambda 表达式声明组件。此 lambda 描述了你构建组件的方式。通常我们通过组件的构造函数来实例化组件,但你也可以使用任何表达式。
single { 类构造函数 // Kotlin 表达式 }
你的 lambda 的结果类型是你的组件的主要类型。
定义工厂
工厂组件声明是一种定义,它将每次在你请求此定义时都为你提供一个新实例(此实例不会被 Koin 容器保留,因为它之后不会将此实例注入到其他定义中)。使用 factory
函数和 lambda 表达式来构建组件。
class Controller()
val myModule = module {
// declare factory instance for Controller class
factory { Controller() }
}
INFO
Koin 容器不会保留工厂实例,因为它在每次请求定义时都会提供一个新实例。
解析和注入依赖
现在我们已经可以声明组件定义了,我们希望通过依赖注入来链接实例。要在 Koin 模块中解析实例,只需使用 get()
函数来获取请求的所需组件实例。这个 get()
函数通常用于构造函数中,以注入构造函数值。
INFO
要使用 Koin 容器进行依赖注入,我们必须采用构造函数注入 (constructor injection) 风格:在类的构造函数中解析依赖。通过这种方式,你的实例将使用 Koin 注入的实例来创建。
让我们用几个类来举例:
// 演示器 <- 服务
class Service()
class Controller(val view : View)
val myModule = module {
// 声明 Service 为单例
single { Service() }
// 声明 Controller 为单例,使用 get() 解析 View 实例
single { Controller(get()) }
}
定义:绑定接口
single
或 factory
定义使用其给定 lambda 定义中的类型,即: single { T }
该定义匹配的类型是此表达式中唯一匹配的类型。
让我们用一个类和已实现的接口来举例:
// Service 接口
interface Service{
fun doSomething()
}
// Service 实现
class ServiceImp() : Service {
fun doSomething() { ... }
}
在 Koin 模块中,我们可以按如下方式使用 as
Kotlin 类型转换操作符:
val myModule = module {
// 将只匹配 ServiceImp 类型
single { ServiceImp() }
// 将只匹配 Service 类型
single { ServiceImp() as Service }
}
你也可以使用推断类型表达式:
val myModule = module {
// 将只匹配 ServiceImp 类型
single { ServiceImp() }
// 将只匹配 Service 类型
single<Service> { ServiceImp() }
}
NOTE
第二种声明风格更受推荐,并将在文档的其余部分中使用。
额外的类型绑定
在某些情况下,我们希望从一个定义中匹配多个类型。
让我们用一个类和接口来举例:
// Service 接口
interface Service{
fun doSomething()
}
// Service 实现
class ServiceImp() : Service{
fun doSomething() { ... }
}
为了让定义绑定额外的类型,我们与类一起使用 bind
操作符:
val myModule = module {
// 将匹配 ServiceImp 和 Service 类型
single { ServiceImp() } bind Service::class
}
请注意,在这里我们可以直接使用 get()
解析 Service
类型。但如果我们有多个绑定 Service
的定义,我们就必须使用 bind<>()
函数。
定义:命名与默认绑定
你可以为你的定义指定一个名称,以帮助你区分同一类型的两个定义:
只需使用其名称请求你的定义:
val myModule = module {
single<Service>(named("default")) { ServiceImpl() }
single<Service>(named("test")) { ServiceImpl() }
}
val service : Service by inject(qualifier = named("default"))
get()
和 by inject()
函数允许你在需要时指定一个定义名称。这个名称是由 named()
函数生成的限定符 (qualifier)。
默认情况下,如果类型已经绑定到一个定义,Koin 将通过其类型或名称来绑定定义。
val myModule = module {
single<Service> { ServiceImpl1() }
single<Service>(named("test")) { ServiceImpl2() }
}
然后:
val service : Service by inject()
将触发ServiceImpl1
定义val service : Service by inject(named("test"))
将触发ServiceImpl2
定义
声明注入参数
在任何定义中,你都可以使用注入参数:这些参数将由你的定义注入和使用:
class Presenter(val view : View)
val myModule = module {
single{ (view : View) -> Presenter(view) }
}
与已解析的依赖项(使用 get()
解析)不同,注入参数是通过解析 API 传递的参数。 这意味着这些参数是使用 get()
和 by inject()
,并通过 parametersOf
函数传递的值:
val presenter : Presenter by inject { parametersOf(view) }
进一步阅读请参考注入参数章节
定义终止 - OnClose
你可以使用 onClose
函数,在定义上添加一个回调,当定义关闭时将调用此回调:
class Presenter(val view : View)
val myModule = module {
factory { (view : View) -> Presenter(view) } onClose { // 关闭回调 - 它是 Presenter }
}
使用定义标志
Koin DSL 还提供了一些标志。
启动时创建实例
一个定义或模块可以被标记为 CreatedAtStart
,以便在启动时(或在你需要时)创建。首先在你的模块或定义上设置 createdAtStart
标志。
定义上的 CreatedAtStart 标志
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module {
// 此定义进行立即创建
single<Service>(createdAtStart=true) { TestServiceImp() }
}
模块上的 CreatedAtStart 标志:
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module(createdAtStart=true) {
single<Service>{ TestServiceImp() }
}
startKoin
函数将自动创建带有 createdAtStart
标志的定义实例。
// 启动 Koin 模块
startKoin {
modules(myModuleA,myModuleB)
}
INFO
如果你需要在特定时间(例如在后台线程而不是 UI 线程中)加载某些定义,只需获取/注入所需的组件即可。
处理泛型
Koin 定义不考虑泛型类型参数。例如,下面的模块尝试定义两个 List 的定义:
module {
single { ArrayList<Int>() }
single { ArrayList<String>() }
}
Koin 不会使用这样的定义启动,它会认为你想要用一个定义覆盖另一个定义。
为了允许你使用这两个定义,你必须通过它们的名称或位置(模块)来区分它们。例如:
module {
single(named("Ints")) { ArrayList<Int>() }
single(named("Strings")) { ArrayList<String>() }
}