Koin、Dagger2、Hilt 目前都是非常流行的库,面对这么多层出不穷的新技术,我们该做如何选择,是一直困扰我们的一个问题。
相对于Dagger2 而言Koin更加适合Kotlin语言。
Koin官网:https://start.insert-koin.io/#/quickstart/kotlin
GitHub:https://github.com/InsertKoinIO/getting-started-koin-core
如果要在项目中使用 Koin,需要在项目中添加 Koin 的依赖,我们只需要在 App 模块中的 build.gradle 文件中添加以下代码。
implementation “org.koin:koin-core:2.1.5”
implementation “org.koin:koin-androidx-viewmodel:2.1.5”
如果需要在项目中使用 Koin 进行依赖注入,需要在 Application 或者其他的地方进行初始化。
class KoinApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
AndroidLogger(Level.DEBUG)
androidContext(this@KoinApplication)
modules(appModule)
}
}
}
当初始化完成之后,就可以在项目中使用 Koin 了,首先我们来看一下如何在项目中注入 Repository, Repository 有一个子类 TasksRepository,代码和上文介绍的一样,需要在其构造函数构造 localDataSource 和 remoteDataSource 两个 DataSource。
class TasksRepository @Inject constructor(
private val localDataSource: DataSource,
private val remoteDataSource: DataSource
) : Repository
那么在 Koin 中如何注入呢,很简单,只需要几行代码就可以完成。
val repoModule = module {
single {
LocalDataSource(get()) }
single {
RemoteDataSource() }
single {
TasksRepository(get(), get()) }
}
// 添加所有需要在 Application 中进行初始化的 module
val appModule = listOf(repoModule)
和上面 Hilt 长长的代码比起来,Koin 是不是简单很多,那么 Room、Retrofit、ViewModel 如何注入呢,也很简单,代码如下所示。
// 注入 ViewModel
val viewModele = module {
viewModel {
MainViewModel(get()) }
}
// 注入 Room
val localModule = module {
single {
AppDataBase.initDataBase(androidApplication()) }
single {
get<AppDataBase>().personDao() }
}
// 注入 Retrofit
val remodeModule = module {
single {
GitHubService.createRetrofit() }
single {
get<Retrofit>().create(GitHubService::class.java) }
}
// 添加所有需要在 Application 中进行初始化的 module
val appModule = listOf(viewModele, localModule, remodeModule)
上面 Koin 的代码严格意义上讲,其实不太规范,在这里只是为了和 Hilt 进行更好的对比。
到这里是不是感觉 Hilt 相比于 Koin 是不是简单很多,在阅读 Hilt 文档的时候花了很长时间才消化,而 Koin 只需要花很短的时间。
不仅仅如此而已,根据 Koin 文档介绍,Koin 不需要用到反射,那么无反射 Koin 是如何实现的呢,因为 Koin 基于 kotlin 基础上进行开发的,使用了 kotlin 强大的语法糖(例如 Inline、Reified 等等)和函数式编程,来看一个简单的例子。
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
}
内联函数支持具体化的类型参数,使用 reified 修饰符来限定类型参数,可以在函数内部访问它,由于函数是内联的,所以不需要反射。
但是在另一方面 Koin 相比于 Hilt 错误提示不够友好,Hilt 是基于 Dagger 基础上进行开发的,所以 Hilt 自然也拥有了 Dagger 的优点,编译时正确性,对于一个大型项目来说,这是一个非常严重的问题,因为我们更喜欢编译错误而不是运行时错误。
我们总共从以下几个方面对 Hilt 和 Koin 进行全方面的分析:
使用上对比:Hilt 使用起来要比 Koin 麻烦很多,其入门门槛高于 Koin,在阅读 Hilt 文档的时候花了很长时间才消化,而Koin 只需要花很短的时间,依赖注入部分的代码 Hilt 多于Koin,在一个更大更复杂的项目中所需要的代码也更多,也越来越复杂。