官网:insert-koin.io/
Koin是一个为Kotlin规划的轻量级依靠注入结构(依靠检索容器)。
关键词:DSL
、Light
、无代码生成。
引进依靠
koin是为Kotlin语言规划的结构,因而在大都运用到Kotlin的当地都能够运用。一起,Koin还为Android、Android Compose、Ktor供给了专用版别。
本节仅介绍Koin在Android中的引进方法,其它运用场景请参阅官方文档。
-
在项目根级gradle脚本中增加maven库房
repositories { ... mavenCentral() }
-
在模块等级增加对koin的依靠
koin_android_version= "3.2.2" implementation "io.insert-koin:koin-android:$koin_android_version" // (可选) Java兼容包 implementation "io.insert-koin:koin-android-compat:$koin_android_version" // (可选) Jetpack WorkManager支撑 implementation "io.insert-koin:koin-androidx-workmanager:$koin_android_version" // (可选) Jetpack Navigation支撑 implementation "io.insert-koin:koin-androidx-navigation:$koin_android_version"
运用
供给依靠
运用startKoin
开始供给依靠,Android中一般会在Application的onCreate
生命周期中界说。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
// 供给Application实例的依靠项
androidContext(this@MyApplication)
// koin以模块的方法组织依靠项,运用modlues系列函数装载模块
modules(appModule)
}
}
private val appModule = module {
// 供给具体的依靠项
}
}
koin以模块的方法组织依靠项,因而,咱们的依靠项需求界说在module中,运用module
函数能够界说一个module实例,而咱们的依靠项就界说在传入到module
函数的lambda中。
private val appModule = module {
factory {
UserListAdapter()
}
factory {
File("config.json")
}
}
上面的比如中,咱们在appModule
中界说了两个factory依靠项,当咱们需求注入对应的依靠项实例时,Koin就会自动执行lambda,创立新的实例执行注入。
注入依靠项
界说好依靠项以后,咱们就能够运用get
函数来注入/获取依靠项实例:
在一般类中注入
假如要在一般类中注入依靠项,需求为这个类完成KoinComponent
接口,然后就能够在其间调用get
来获取依靠实例了。
class ConfigParser : KoinComponent {
private configFile: File = get()
// private val configFile = get<File>()
private fun readConfig() {
// 在函数中也能获取到依靠项
val configFile: File = get()
}
}
在Android组件中注入
koin-android为Android中的ComponentCallbacks
供给了支撑注入依靠的扩展,因而在Activity
、Fragment
、Service
等Android组件中,能够直接注入依靠,而无需完成KoinComponent
接口。
class MainActivity : AppCompatActivity() {
private val adapter: UserListAdapter = get()
...
}
延迟注入
借助Kotlin特点委托的语法特性,咱们能够很简单的完成延迟注入,一起Koin也供给了inject
函数来支撑延迟注入。
class MainActivity : AppCompatActivity() {
private val adapter: UserListAdapter by lazy { get() }
// 运用inject函数
// private val adapter: UserListAdapter by inject()
// private val adapter by inject<UserListAdapter>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 运用到时才会去创立实例
list.adapter = adapter
}
}
供给依靠时注入
能够在界说依靠项时,还获取其它的依靠项来初始化。
private val appModule = module {
factory {
File("config.json")
}
factory {
// Configuration接受一个File目标,此处Koin会自动获取config.json
// 的文件实例设置进去。
Configuration(get())
}
}
限定符 Qualifier
与Dagger相同,Koin在默许情况下也是经过依靠项的类型来确认要注入哪个类型实例的,假如咱们在供给依靠时,界说了同一类型的两个不同实例,Koin将无法确认注入哪一个实例。为此,Koin供给了限定符Qualifier
用来区别不同的目标。
Koin供给了多种方法来创立Qualifier,其间最常运用的是named
函数。在界说依靠项时,将Qualifier作为参数传递,即可对当时依靠项进行约束。
private val appModule = module {
factory(named("config-file")) { File("config.json") }
factory(named("data-file")) { File("data.txt") }
}
运用时,在获取依靠项的当地运用同样的Qualifier,即可获取指定的依靠项:
val configFile: File = get(named("config-file"))
val dataFile: File by inject(named("data-file"))
除了named
+字符串参数的方法创立Qualifier外,Koin还供给了许多其它的方法来创立Qualifier:比如运用枚举目标、Class目标作为参数……更具体的内容请查阅源码或许官方文档,此处不再赘述。
效果域 Scope
经过效果域咱们能够约束依靠项的生命周期以及查找规模,实际上Koin界说的每个依靠项都有其效果域,当界说依靠项未指定scope时,会运用rootScope
作为效果域。
界说Scope
在module
中运用scope
函数能够界说一个效果域,界说Scope时,需求传递一个Qualifier
,当我需求创立Scope实例时,这儿的Qualifier
用于协助咱们找到Scope的界说。
val paramModule = module {
scope(named("config")) {
...
}
}
在Scope上界说依靠项
在scope
中,咱们能够运用factory
函数以及scoped
函数界说归于这个效果域下的依靠项。
scope(named("config")) {
factory {
File("config.json")
}
scoped {
Configuration(get())
}
}
factory
函数界说了一个生产依靠项的目标工厂,当需求注入依靠项时,Koin就会经过这个目标工厂创立新的实例目标,factory
界说的依靠项不会被缓存起来,每次注入时都会创立新的实例。
scoped
函数能够在当时效果域上界说一个单例的依靠项,当需求注入此依靠项时,Koin会先检查该依靠项在当时效果域上是否已有现成的实例,假如有就直接运用,没有就创立一个缓存起来。
除了上述两种方法外,还有一个single
函数,它用于界说一个在rootScope
上的scoped依靠项。
private val appModule = module {
single {
createOKHTTPClient()
}
}
运用Scope
运用scope时,首要需求获取scope的实例:
val configScope = getKoin().getOrCreateScope(
scopeId = "config-scope",
qualifier = named("config")
)
在Koin
目标上调用getOrCreateScope
用于获取或许创立一个scope实例,其间scopeId
用于缓存这个scope实例时的key,qualifier
则用于在Koin中找到该Scope的界说。
当有了scope实例时,就能够经过它获取依靠项了:
val configFile: File by configScope.inject()
val config: Configuration = configScope.get()
当这个scope不再需求运用时,能够运用colse
函数关闭,此时会毁掉当时scope缓存的一切依靠项实例。
configScope.close()
相关效果域
默许情况下咱们只能获取在当时效果域下界说的依靠项,运用linkTo
函数能够将其它的效果域相关到当时效果域上,这样,就能够在当时效果域上获取到指定效果域中的依靠项了。
scope(named("common")) {
scoped {
FileUtil()
}
}
configScope.get<FileUtil>() // 未相关前configScope无法获取到FileUtil
val commonScope = getKoin().getOrCreateScope(
scopeId = "common-scope",
qualifier = named("common")
)
configScope.linkTo(commonScope) // 相关后configScope能够获取到FileUtil
别的,一切新界说的效果域都会默许相关rootScope
,也就是说,rootScope
中界说的依靠项,在任何其它效果域上都能够访问到。
在Android组件中运用Scope
Koin为Android组件:Activity
、Fragment
、Service
、ViewModel
等供给了默许的Scope支撑,这些Scope会与组件的生命周期进行绑定,在组件创立时初始化,组件毁掉时同步毁掉。
如需运用组件的Scope,需求先在module中运用组件的类型作为Qualifier
界说Scope:
val activityModule = module {
scope<MainActivity> {
factory {
DataListAdapter()
}
}
}
然后承继对应组件的ScopeXXX版别,比如ScopeActivity。
这样就能够在组件内访问到这个scope中的依靠项了:
class MainActivity : ScopeActivity() {
val adapter: DataListAdapter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
list.adapter = adapter
...
}
}
传递参数
Koin支撑在获取依靠项时传入指定的参数来构造目标实例。
首要,在界说依靠项时,用于创立目标实例的lambda会接纳一个ParametersHolder
目标,咱们能够经过这个目标获取到传入的用来构造目标实例的参数。
factory { params: ParametersHolder ->
SharedPreferences(get(), params.get())
}
ParametersHolder
为前5个参数供给了解构支撑,因而上面的代码也能够这样写:
factory { (name: String) ->
SharedPreferences(get(), name)
}
假如参数的类型并未有其它同类型的依靠项界说时,也能够直接运用get
函数,koin会自动测验从参数中查找匹配的实例注入,可是并不引荐这样做,这可能会造成必定的误解:
factory {
SharedPreferences(get(), get()/* 在界说的依靠项中找不到时,会测验从参数中匹配 */)
}
在获取依靠项实例时,咱们就需求传递对应的参数,get
函数和inject
函数会接纳一个ParametersDefinition
目标,这是一个 () -> ParametersHolder
函数,没错,咱们在界说依靠项时运用的ParametersHolder
目标就是这儿生成的:
inline fun <reified T : Any> ComponentCallbacks.get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T
typealias ParametersDefinition = () -> ParametersHolder
运用方法也很简单,在ParametersDefinition
中运用parametersOf
函数将咱们要传递的参数打包成一个ParametersHolder
即可。
val mainSp: SharedPreferences by inject() {
parametersOf("main")
}
val prefSp: SharedPreferences by inject() {
parametersOf("pref")
}
⚠️ 需求留意的是,运用
single
或scoped
界说的依靠项,传入不同的参数并不会为每一个参数都生成对应的实例目标。假如咱们将上面的SharedPreferences
改为运用single
界说,那么mainSp
和prefSp
会得到同一个main preferences目标。
绑定接口实例
由于默许情况下,Koin运用类型来准确匹配依靠项,因而当咱们需求注入一个接口的实例时就会呈现一些问题:
interface IService
class ServiceImpl : IService
val appModule = module {
single { ServiceImpl() }
}
上面这样界说时咱们只能运用ServiceImpl
类型来获取依靠项实例。
// val service: IService by inject() // 运行时报错
val service: ServiceImpl by inject()
因而,假如咱们想将实例注入到一个接口界说的变量上,就需求将目标强转成IService
类型:
val appModule = module {
// 运用as强转
single { ServiceImple() as IService }
// 或许指定为single函数标明类型
single<IService> { ServiceImpl() }
}
可是这时又只能运用IService
来获取依靠项了:
val service: IService by inject()
// val service: ServiceImpl by inject() // 运行时报错
附加类型的绑定能够让一个依靠项实例绑定到不同的类型上,Koin供给了bind
和binds
函数来完成附加类型绑定:
val appModule = module {
single { ServiceImple() } bind IService::class
}
这样ServiceImple
目标实例就能够一起注入给ServiceImple
变量或许IService
变量了。
注入ViewModel
Koin也供给了对Jetpack ViewModel组件的支撑,咱们能够运用专门的DSL界说ViewModel依靠项实例:
val vmModule = module {
viewModel { MainViewModel(get()) }
}
然后在Activity或许Fragment中运用:
class MainActivity : AppCompatActivity() {
val vm: MainViewModel by viewModel()
// val vm: MainViewModel = getViewModel()
}
class MainFragment : Fragment {
// 经过sharedViewModel能够获取到Fragment宿主的ViewModel
val actVm: MainViewModel by sharedViewModel()
// val actVm: MainViewModel = getSharedViewModel()
}
注入Fragment
Koin供给了KoinFragmentFactory
用于办理注入Fragment。要运用此功用,首要需求在startKoin
中初始化KoinFragmentFactory
的实例:
startKoin {
fragmentFactory()
...
}
然后在module中界说Fragment依靠项:
val fragModule = module {
fragment { MyFragment() }
}
然后就能够在Activity中运用了:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在当时Activity设置KoinFragmentFactory
setupKoinFragmentFactory()
// 然后就能够经过androidx相关的api设置MyFragment了
supportFragmentManager.beginTransaction()
.replace<MyFragment>(R.layout.activity_main)
.commit()
}
}