Koin框架,适用于使用Kotlin开发 ,是一款轻量级的依赖注入框架,无代理,无代码生成,无反射,相对于dagger 而言更加适合Kotlin语
目前依赖注入比较常见的是dagger框架,dagger框架经过证明有助于帮助代码中各个模块进行解耦,所以我们前提条件是支持引入依赖注入框架的。 另一方面,在依赖注入框架的选择上,我们对比了dagger和koin,发现了dagger的一些不足,比如上手难,需要手动注入等问题,同时研究了目前比较流行的koin框架,发现Koin在一些方面会比dagger来的好,同时也更符合项目往kotlin迁移的趋势,所以对这两种框架做了一些对比来看看Koin是否有取代dagger的能力
github地址:https://github.com/InsertKoinIO/koin 作者:Arnaud Giuliani,来自法国图卢兹 koin目前在slack #koin 、Stackoverflow - #koin tag上面都开设了讨论区,在Twitter@insertkoin_io上面推送最新状态,其中Stackoverflow关于koin的有221个讨论,最近几天都有新增讨论 koin项目目前Pull requests总共185个,其中打开状态18个,最近一个打开时间是2020.5.13;关闭状态是167个,最近一个关闭时间是2020.3.23。总体来看活跃度还是比较高的,处理速度也挺快,目前打开的数量并不多 版本迭代情况:第一个版本是0.8.0,目前稳定版本是2.1.5,重大版本发布历史为:0.8.0(2018.1.4)->0.8.2(2018.2.2)->0.9.0(2018.3.3)->0.9.1(2018.3.13)->0.9.2(2018.4.18)->1.0.0 Beta(2018.7.10)->1.0(2018.9.14)->2.0(2019.5.28)->2.1.5,可以看到2018年处于初期更新比较频繁,2019年推出的2.0版本主要对性能等方面进行了非常大的优化,后面就进入了稳定期,主要是小版本的更新迭代
// Koin for Androidimplementation 'org.koin:koin-android:2.1.5'// or Koin for Lifecycle scopingimplementation 'org.koin:koin-androidx-scope:2.1.5'// or Koin for Android Architecture ViewModelimplementation 'org.koin:koin-androidx-viewmodel:2.1.5'
// Given some classesclass Controller(val service : BusinessService)class BusinessService()// just declare itval myModule = module {single { Controller(get()) }single { BusinessService() }}
class MyApplication : Application() {override fun onCreate(){super.onCreate()// start Koin!startKoin {// Android contextandroidContext(this@MyApplication)// modulesmodules(myModule)}}}
private val service: BusinessService by inject()
使项目更简洁,相比dagger,koin的优势就是简易上手,dagger会比较复杂一些,同样的module,dagger需要定义Component和Module以及一些注解,koin只要一个module定义就够了
注入更简单,koin注入只需要在注入类声明一个变量,而dagger需要一个个手动注入,使用dagger注入单例需要初始化时一个个调用,这样不仅麻烦还可能因为集中初始化导致ANR出现,特别在大型项目中引入dagger更加痛苦,因为你要处理每一个注入对象,下面是使用dagger代码:
fun injectAll(component: MainComponent) {component.inject(this)component.inject(mainController)...}koin在kotlin上面提供了很多扩展库,比如 by viewmodel()方式注入会将ViewModel绑定当前View的生命周期,这些扩展库更加充分利用了kotlin特性,会比dagger来的强大
koin增加了启动耗时,不过相对的也省去了dagger编译的耗时,而且koin启动耗时还是可以优化的,但是dagger编译耗时却是不可优化的
//dagger测试private fun daggerTest(): LibraryResult {log("Running Dagger...")lateinit var kotlinComponent: KotlinDaggerComponentlateinit var javaComponent: JavaDaggerComponentreturn LibraryResult("Dagger", mapOf(Variant.KOTLIN to runTest(setup = { kotlinComponent = DaggerKotlinDaggerComponent.create() },test = { kotlinComponent.inject(kotlinDaggerTest) }),Variant.JAVA to runTest(setup = { javaComponent = DaggerJavaDaggerComponent.create() },test = { javaComponent.inject(javaDaggerTest) })))}//koin测试private fun koinTest(): LibraryResult {log("Running Koin...")return LibraryResult("Koin", mapOf(Variant.KOTLIN to runTest(setup = {startKoin {modules(koinKotlinModule)}},test = { get<Fib8>() },teardown = { stopKoin() }),Variant.JAVA to runTest(setup = {startKoin {modules(koinJavaModule)}},test = { get<FibonacciJava.Fib8>() },teardown = { stopKoin() })))}
startKoin {modules(syncModule)}Thread {loadKoinModules(module { asyncModule })}.start()
从上面可以看出koin框架在setup阶段会比dagger耗时,因为koin注册Moudle是通过记录每个module的类定义来实现的,因此当注册的module越多越耗时,而dagger是编译时生成注册对象的,不会占用运行时间。注入阶段由于只获取一个对象,因此两个框架相差不多
另一方面koin框架在这方面也在不断优化,之前1.0版本效果更差,2.0改进了不少,相信后面版本会对setup耗时做进一步的优化
/*** Declare a Single definition* @param qualifier* @param createdAtStart* @param override* @param definition - definition function*/inline fun <reified T> single(qualifier: Qualifier? = null,createdAtStart: Boolean = false,override: Boolean = false,noinline definition: Definition<T>): BeanDefinition<T> {return Definitions.saveSingle(qualifier,definition,rootScope,makeOptions(override, createdAtStart))}
再来看一下Definitions.saveSingle这个方法:
inline fun <reified T> saveSingle(qualifier: Qualifier? = null,noinline definition: Definition<T>,scopeDefinition: ScopeDefinition,options: Options): BeanDefinition<T> {val beanDefinition = createSingle(qualifier, definition, scopeDefinition, options)scopeDefinition.save(beanDefinition)return beanDefinition}inline fun <reified T> createSingle(qualifier: Qualifier? = null,noinline definition: Definition<T>,scopeDefinition: ScopeDefinition,options: Options,secondaryTypes: List<KClass<*>> = emptyList()): BeanDefinition<T> {return BeanDefinition(scopeDefinition,T::class,qualifier,definition,Kind.Single,options = options,secondaryTypes = secondaryTypes)}
/*** inject lazily given dependency for Android koincomponent* @param qualifier - bean qualifier / optional* @param scope* @param parameters - injection parameters*/inline fun <reified T : Any> ComponentCallbacks.inject(qualifier: Qualifier? = null,noinline parameters: ParametersDefinition? = null) = lazy(LazyThreadSafetyMode.NONE) { get<T>(qualifier, parameters) }
/*** get given dependency for Android koincomponent* @param name - bean name* @param scope* @param parameters - injection parameters*/inline fun <reified T : Any> ComponentCallbacks.get(qualifier: Qualifier? = null,noinline parameters: ParametersDefinition? = null): T = getKoin().get(qualifier, parameters)
/*** Get a Koin instance* @param qualifier* @param scope* @param parameters*/@JvmOverloadsinline fun <reified T> get(qualifier: Qualifier? = null,noinline parameters: ParametersDefinition? = null): T = _scopeRegistry.rootScope.get(qualifier, parameters)
接着rootScope.get():
/*** Get a Koin instance* @param qualifier* @param scope* @param parameters*/@JvmOverloadsinline fun <reified T> get(qualifier: Qualifier? = null,noinline parameters: ParametersDefinition? = null): T {return get(T::class, qualifier, parameters)}/*** Get a Koin instance* @param clazz* @param qualifier* @param parameters* @return instance of type T*/fun <T> get(clazz: KClass<*>,qualifier: Qualifier? = null,parameters: ParametersDefinition? = null): T {return if (_koin._logger.isAt(Level.DEBUG)) {val qualifierString = qualifier?.let { " with qualifier '$qualifier'" } ?: ""_koin._logger.debug("+- '${clazz.getFullName()}'$qualifierString")val (instance: T, duration: Double) = measureDurationForResult {resolveInstance<T>(qualifier, clazz, parameters)}_koin._logger.debug("|- '${clazz.getFullName()}' in $duration ms")return instance} else {resolveInstance(qualifier, clazz, parameters)}}@Suppress("UNCHECKED_CAST")private fun <T> resolveInstance(qualifier: Qualifier?,clazz: KClass<*>,parameters: ParametersDefinition?): T {if (_closed) {throw ClosedScopeException("Scope '$id' is closed")}//TODO Resolve in Root or linkval indexKey = indexKey(clazz, qualifier)return _instanceRegistry.resolveInstance(indexKey, parameters)?: findInOtherScope<T>(clazz, qualifier, parameters) ?: getFromSource(clazz)?: throwDefinitionNotFound(qualifier, clazz)}
@Suppress("UNCHECKED_CAST")internal fun <T> resolveInstance(indexKey: IndexKey, parameters: ParametersDefinition?): T? {return _instances[indexKey]?.get(defaultInstanceContext(parameters)) as? T}
private fun createInstanceFactory(_koin: Koin,definition: BeanDefinition<*>): InstanceFactory<*> {return when (definition.kind) {Kind.Single -> SingleInstanceFactory(_koin, definition)Kind.Factory -> FactoryInstanceFactory(_koin, definition)}}
上手简单,没有学习成本及维护成本
注入简单,不需要像dagger一样定义Module和Component,不需要手动调用inject方法
扩展性高,koin提供了各种扩展库来丰富对依赖注入的各种需求
初始化耗时的成本,这个和注册的对象数量成正比,不过提供了异步初始化API
包体积成本,大概增加150KB,包括各种常用扩展库




