The pragmatic Kotlin & Kotlin Multiplatform Dependency Injection framework

实用的Kotlin和Kotlin多平台依靠注入框架

Android Studio环境为 Android Studio Flamingo | 2022.2.1

Koin的最新版本为2.4.0

Koin是一个轻量级的依靠注入框架,它允许Android应用程序轻松办理组件之间的依靠关系。

Koin的首要方针是使依靠注入变得简略,易于理解和运用。它采用纯Kotlin编写,无需代码生成或反射,而是基于函数式DSL和注解,供给了一个简略而强壮的办法来声明和办理依靠项。

增加依靠

dependencies {
    def koin = "3.4.0"
    implementation("io.insert-koin:koin-core:$koin")
    implementation("io.insert-koin:koin-android:$koin")
    implementation("io.insert-koin:koin-android-compat:$koin")
}
  • core为Koin的核心
  • -android是Koin为Android供给的一些扩展办法
  • -compat是Koin为Android组件供给的一些扩展办法

除了上面三个以外,Koin还适配了compose、ktor,可谓是Android端和服务端都能够运用Koin来进行依靠注入,但是它和Hilt的不同是,Koin在kotlin的环境中运用。

开始运用

class KoinApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@KoinApp)
            modules(normalModule)
        }
    }
}

startkoin{}敞开Koin功能,然后进行一些配置

  • androidLogger()敞开Koin的运行日志
  • androidContext()绑定Application上下文,后边能够直接从Koin中获取
  • module()传入Koin的模块,此模块便是咱们界说的依靠注入项

运用Koin注入目标

首要来运用Koin来注入一个惯例的目标和单例目标,它不需求在目标的结构办法前面加入任何的注解,能够在原有的代码中无侵入式运用,这样就能够在之前的代码中进行Koin改造,下面来看看Koin是怎么注入目标的

// 界说两个目标,分别用来演示惯例的目标和单例目标注入
class KoinTest {
    fun test() {
        Log.d(TAG, "KoinTest test $this")
    }
}
class SingletonTest {
    fun test() {
        Log.d(TAG, "SingletonTest test $this")
    }
}
// normalMoudle便是来办理惯例的目标注入
val normalModule = module {
    factory { KoinTest() }
}
// singleModule则是用来单例目标注入
val singleModule = module {
    single { SingletonTest() }
}
val moduleList = listOf(normalModule, singleModule)
startKoin {
    androidLogger(Level.DEBUG)
    androidContext(this@KoinApp)
    // 将moduleList传入modules中,这样Koin就会协助咱们完结依靠注入
    modules(moduleList)
}

上面代码已经协助咱们完结了依靠注入,接着咱们在运用注入目标的时分直接运用Koin的扩展办法by inject()就能够获取到对应的实例。

class MainActivity : BaseActivity<ActivityMainBinding>() {
    // 运用Koin进行目标注入
    private val koinTest: KoinTest by inject()
    private val singletonTest: SingletonTest by inject()
    override fun initViewBinding(): ActivityMainBinding {
        return ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onResume() {
        super.onResume()
        koinTest.test()
        singletonTest.test()
    }
}
# log
KoinTest test com.example.koin.KoinTest@e0d5daf
SingletonTest test com.example.koin.SingletonTest@29665bc

留意看log中目标,KoinTest运用factory{}来进行注入的,应该是每次注入都会生成一个新的目标,而SingletonTest运用的是single{}来进行注入,每次注入目标都是不会变的,大局都是一个目标,下面咱们看看在新的Activity中拿到的注入目标是否契合咱们的预期。

class SecActivity:BaseActivity<ActSecBinding>() {
    private val koinTest: KoinTest by inject()
    private val singletonTest: SingletonTest by inject()
    override fun initViewBinding(): ActSecBinding {
        return ActSecBinding.inflate(layoutInflater)
    }
    override fun onResume() {
        super.onResume()
        koinTest.test()
        singletonTest.test()
    }
}
# log
KoinTest test com.example.koin.KoinTest@17dbb11
SingletonTest test com.example.koin.SingletonTest@29665bc

从log中能够看出,KoinTest确实是新的一个目标,而SingletonTest和之前注入的目标是同一个,这样咱们就已经能够运用Koin来完结不同类型的目标注入了。

Koin不只能够运用by inject()来注入,也能够直接运用get()来注入一个目标,二者的差异在于一个是懒加载形式,一个是直接获取形式

  • inject()回来的是一个Lazy目标,内部仍旧调用的是get()办法
  • get()回来的是需求注入的目标实例

在平常运用中按需选择。

和Hilt一对比,是否觉得运用Koin来完结依靠注入更方便了呢,在写法上面确实给人一种更加快捷的感觉,但是Koin和Hilt各有利弊吧,在后续的文章中会对这方面进行一个详细的介绍。

Koin中运用ApplicationContext

在敞开Koin的时分,咱们运用androidContext()传入了Application目标,这就为咱们在后续需求运用Application的时分供给了无需传入参数的快捷,咱们能够直接从Koin的get()获取到。

class ContextTest(
    private val context: Application
) {
    fun test() {
        Log.d(TAG, "ContextTest test: ${context.getString(R.string.app_name)}")
    }
}
val normalModule = module {
	// 这里直接运用get()来注入context目标
    factory { ContextTest(get()) }
}
# log
ContextTest test: koin

Koin中运用ViewModel

在Koin中注入一个ViewModel目标时,大致能够分为两种,一种是无参的ViewModel,不运用Koin的情况下,咱们能够直接运用activity-ktx扩展库的by viewModels()来获取到ViewModel目标,这样确实要比Koin简略一些,但是关于第二种有参的ViewModel来说,Koin在写法上面要略微具有优势一些,下面咱们具体来看下两种的写法,从代码层面直观感受下Koin的魅力地点。

注入无参ViewModel

// 界说一个无参的ViewModel
class KoinViewModel : ViewModel() {
    fun test() {
        Log.d(TAG, "KoinViewModel test")
    }
}
val viewModelModule = module {
    // 运用Koin的viewModel{}来注入KoinViewModel目标
    viewModel { KoinViewModel() }
}
// 在Activity中运用 by viewModel() 懒加载来获取KoinViewModel目标
private val koinViewModel: KoinViewModel by viewModel()
koinViewModel.test()
# log
KoinViewModel test

对应ViewModel这种特殊的目标来说,Koin供给了viewModel{}办法协助咱们轻松的注入此目标,然后在运用的时分直接经过by viewModel()扩展办法来获取对应的ViewModel目标,它的内部完结也便是咱们平常运用的ViewModelProvider来创立与之对应的ViewModel,Koin内部封装了一个KoinViewModelFactory,它是继承自ViewModelProvider.Factory

注入有参ViewModel

// 界说一个有参数的ViewModel
class ParamsViewModel(
    private val repository: Repository
) : ViewModel() {
    fun test() {
        repository.test()
    }
}
class Repository() {
    fun test() {
        Log.d(TAG, "Repository test")
    }
}
val viewModelModule = module {
    single { Repository() }
    // 参数注解运用get()获取,无需手动传入
    viewModel { ParamsViewModel(get()) }
}
private val paramsViewModel: ParamsViewModel by viewModel()
paramsViewModel.test()
# log
Repository test

关于有参数的ViewModel来说,Koin仅仅要求咱们多一步此参数的注入,其余和无参的运用过程几乎是如出一辙,ParamViewModel需求咱们传入一个Repository目标,那么咱们就在moudle中将此目标经过single{}来先注入进去,告诉Koin它是一个单例目标,直接曩昔也行(也能够运用factor{}来进行注入),然后咱们在注入ViewModel的时分经过Koin的get()办法获取即可。看到这是不是觉得Koin在此处深得人心,再也不必繁琐的经过ViewModelProvider.Factory来手动创立需求的参数了。

好了,Koin的第一篇暂时就说这么些了,后边咱们接着介绍Koin的作用域相关知识。