当前位置: 首页 > 工具软件 > Koin > 使用案例 >

拥抱Koin - 轻量级依赖注入框架

殷学
2023-12-01

Koin是什么?

Koin是一个轻量级的依赖注入框架,基于Kotlin的DSL进行DI的配置,全程无反射无代码生成。

与Dagger的比较

Dagger是Andorid官方推荐使用的DI框架,功能十分强大,但学习曲线陡峭,使用成本也相对较高。Koin在某些方面相对于Dagger具有以下优势:

  • 使用成本低,比如一个完整的MVVM架构的项目,使用dagger的话,需要对M/V/VM各层分别实现Module,SubComponent等文件,会增加很多项目文件,但是如果使用Koin的话,要简单得多
  • 编译速度快,Koin没有额外的代码生成,编译速度很快
  • 轻量,Dagger在编译后会生大量代码,增加安装包体积,Koin没有这种烦恼

基于DSL

Koin最重要的特点是借助Kotlin的语法优势,提供了多个DSL式的API帮助我们进行DI的配置。DSL本质上是一些尾lambda的高阶函数,同时配合kotlin的类型推到,让我们节省了很多模板代码的书写。例如 factory{...},类似于Dagger的@Provide,提供依赖对象,每次使用到的时候都会生成新的实例。其定义如下:

typealias Definition<T> = Scope.(DefinitionParameters) -> T
    
inline fun <reified T> factory(
            qualifier: Qualifier? = null,
            override: Boolean = false,
            noinline definition: Definition<T>
): BeanDefinition<T> {
        return Definitions.saveFactory(
                qualifier, definition, rootScope, makeOptions(override))
}

常用的DSL主要有:

  • module { }  - 类似于Dagger的@Module,里面提供所需的依赖
  • factory { } - 类似于Dagger的@Provide,提供依赖对象,每次使用到的时候都会生成新的实例
  • single { } - 与factory功能一样,factory提供多实例对象,single提供单例

快速上手

我们尝试在一个MVVM项目应用Koin。首先添加Koin的依赖

implementation "org.koin:koin-android-viewmodel:$koin_version"

// androidx工程使用
implementation "org.koin:koin-androidx-viewmodel:$koin_version"

koine-android-viewmodle可以配合ViewModle使用Koin,Koin还有其他类型的依赖,我们选用它是为了要在MVVM中使用。

然后我们想Dagger一样定义我们所需要的Module,我们可以将Module在不同文件定义,但是项目比较简单是也可以定义在一起。

ViewModel层

我们为VIewModle层提供DI用的Module

val viewModelModule = module {
    viewModel { LoginViewModel(get()) } //get<AuthRepo>()
}

val repoModule = module {
    factory  <AuthRepo> { AuthRepo(get(), get()) } // AuthRepo(get<AuthApi>(), get<AuthDao>() 
}

在module中通过viewmodel{...}声明ViewModel的factory,factory会为Activity或者Fragment提供ViewModel,被存入到其ViewModelStore中。各依赖的构造函数中所需的参数通过get()注入,通过类型推断省略了泛型。

Model层

接下来,实现Model层所需的DI用的Module


val remoteModule = module {

    single<Retrofit> {
        Retrofit.Builder()
                .baseUrl(Constants.HOST_API)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    }

    single<AuthApi> { get<Retrofit>().create(AuthApi::class.java) }
}


val localModule = module {

    single<AppDatabase> { AppDatabase.getInstance(androidApplication()) }

    single<AuthDao> { get<AppDatabase>().authDao() }
}

我们将remote请求所需的retrofit,api以及local所需的db、dao以single的形式provide

View层

我们为Activity注入所需要的ViewModel

class LoginActivity : AppCompatActivity() {
    private val mViewModel: LoginViewModel by viewModel()    
}

通过 by viewModel()可以简单的从Koin中获取ViewModel,相对于ViewModelProviders.of()方式更加简单q

启动Koin

在APP启动时,我们将上述所有Module注入到全局容器,后续创建各对象时从容器中集中检索,将所需要的依赖注入到对象中,完成依赖注入

startKoin(this, listOf(repoModule, viewModelModule))

实现细节

最后我们简单了解一下Koin的源码。依赖注入的基本思想非常简单:将各种依赖的单例或者factory注册到全局容器中,然后在需要使用创建这些依赖对象是,通过key(比如class类型)在全局容器中检索,并进行创建。

Koin通过ScopeDefinition定义不同Scope的全局容器

class ScopeDefinition (
    /*other params*/,
    private val _definitions: HashSet<BeanDefinition<*>> = hashSetOf()) {
    // ...
    val definitions: Set<BeanDefinition<*>>
        get() = _definitions
    // ...
}

ScopeDefinition中有一个BeanDefinitions的Set,用来注册各个依赖对象的创建信息

data class BeanDefinition<out T>(
    primaryType: KClass<*>,
    val definition: Definition<T>
    //... other params
)

BeanDefinition有两个关键构造参数,primaryType是依赖对象的类型,用来作为检索的key,definition实际我们在DSL中声明的factory。

inline fun <reified T : ViewModel> Module.viewModel(
        qualifier: Qualifier? = null,
        override: Boolean = false,
        noinline definition: Definition<T>
): BeanDefinition<T> {
    val beanDefinition = factory(qualifier, override, definition)
    beanDefinition.setIsViewModel()
    return beanDefinition
}

从viewmodel{...}的实现可以看出,factory()创建了BeanDefinition,factory()内部会将创建的BeanDefinition注入到全局容器

//创建BeanDefinition
inline fun <reified T> createFactory(
        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.Factory,
            options = options,
            secondaryTypes = secondaryTypes
        )
 }

//注入全局容器
inline fun <reified T> saveFactory(
        qualifier: Qualifier? = null,
        noinline definition: Definition<T>,
        scopeDefinition: ScopeDefinition,
        options: Options
    ): BeanDefinition<T> {
        val beanDefinition = createFactory(qualifier, definition, scopeDefinition, options)
        scopeDefinition.save(beanDefinition)
        return beanDefinition
    }

 

最后

本文简单介绍了Koin的特点、使用方法以及实现原理。Koin上手简单,非常适合基于Kotlin开发的MVVM项目,想要了解更多使用细节,请移步官网 https://github.com/InsertKoinIO/koin。当然如果您的项目比较复杂,让然推荐使用Dagger,毕竟是官方指定产品,可以处理一些Koin不能胜任的复杂需求。

 类似资料: