携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
一直想了解依赖注入框架,从Dagger2到Hilt再到Koin,一直没有付诸行动。因为Koin是用Kotlin写的,所以我相信应该更加契合Kotlin,所以决定学习Koin。这里没有依赖注入的说教,也没有不同依赖注入的框架的对比,只有纯粹的Koin学习。
Koin是什么
首先Koin是一个依赖注入的框架。
其次,Koin因为全部使用Kotlin进行编写,所以可以使得其接口以DSL(领域专业语言)的形式呈现出来,使用起来简单明了。
Koin的核心部分是KoinApplication以及Module。
- KoinApplication提供了一个容器,以配置参数以及进行对象实例化。
- Module则用于描述用于注入的内容
KoinApplication
KoinApplication有两种创建的形式:
- koinApplication{ } 直接创建KoinApplication。
- startKoin{ } 创建KoinApplication并将其注册到GlobalScope中以便使用GlobalScope API。(通常使用这种方式)
可供配置的功能如下:
- logger() - 指定日志的等级。(Android通常使用androidLogger()替代)
- modules() - 加载Koin Module到容器中。
- properties() - 加载给定的键值对到容器。(Map形式)
- fileProperties() - 从给定文件中加载键值对到容器。(Android通常用androidFileProperties()替代,文件一定要放在assets目录下,默认文件名为koin.properties,可传入自定义文件名)
- environmentProperties() - 从操作系统环境加载键值对到容器。
- createEagerInstances() - 创建以createAtStart标记的单例定义。
- androidContext() - 应该只有Android独有的方法,将context加载到容器中,以便在Module中能使用。
Module
Koin Module承载着将要注入或者组合的定义,创建方法如下:
- module{ } - 创建Koin Module。
使用以下方法描述module的内容:
- factory{ } - 提供一个工厂定义。(每次注入都是新的实例)
- single{ } - 提供一个单例定义。(顾名思义)
- get() - 获取依赖。
- bind() - 增加一个类型绑定到给定的定义。
- binds() - 增加一个类型的数组绑定到给定的定义。
- scope{ } - 定义一个作用域。
- scoped{ } - 为一个定义指定其作用域。
- named() - 命名定义。
定义
使用Koin需要在Module中描述需要注入的对象的生成规则(即定义——Definition)。
请看如下Module:
class A()
class B()
class C(val a:A)
class D(val view:View)
interface F { fun doSomething() }
class FImp : F { fun doSomething(){ ... } }
val myModule = module {
//1
single{ A() }
//2
factory{ B() }
//3
factory{ C(get()) }
//4
single<F> { FImp() }
//5
single { FImp() } bind F::class
//6
single(named:("special")){ A() }
//7
single{ (view:View) -> D(view) }
//8
single(createdAtStart=true){ A() }
}
- single关键字
- 标记该定义为单例模式,作用域内只有一个实例。(上述代码1)
- factory关键字
- 标记该定义为工厂模式,即传统的注入一次创建一个新的实例。(上述代码2)
- get方法
- 实现注入的方法,自动匹配所需的类型并找到对应的注入定义,在module外同样适用该方法获取注入。及时返回。(上述代码3)获取注入还有by inject()方法,该方法为懒加载。
- 接口注入
- FImp为接口F的实现类,通过指定泛型为F,即可返回实现类为FImp的接口F。(上述代码4)
- 类型绑定
- Koin默认只对应一个类型,但可以通过bind方法多绑定一个类,如上述代码5,该注入定义则对应FImp类以及接口F。
- 定义著名
- 对于相同的类型,有不同的定义则需要通过著名来区分。如上述代码6,通过named()与代码1进行区分,在注入的时候指定对应的名字就可以调用不同的定义。
- 传参
- 类D的构造方法需要传入一个view,则可以如上述代码7那样进行定义。在注入时,通过parameterOf方法传入所需的参数。
- Flag
- 如上述代码8,createdAtStart表明该定义的实例需要在一开始的时候进行初始化。createdAtStart也可用在module上,表明该module的所有定义的实例都需要在一开始初始化。
注意,Koin并不能很好地处理泛型,如以下例子:
module{
single{ ArrayList<Int>() }
single{ ArrayList<String>() }
}
Koin并不能区分这两个定义,遇到这种情况需要使用著名来加以区分:
module{
single(named("Ints")){ ArrayList<Int>() }
single(named("Stings")){ ArrayList<String>() }
}