Android使用Koin依赖项注入

关注小编个人简介,技术不迷路

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的作用域相关知识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容