最近架构优化的需要,了解了一下平时不是很愿意去了解的依赖注入框架(因为Dagger2实在是过于难用,koin以前感觉不是很成熟)。主要关注了两个框架,一个是Google基于Dagger开发的hilt框架,一个是基于Kotlin开发的koin框架。因为我们的项目里面依赖了Tinker,Hit不支持Application不使用无参构造方法,所以下定决心转去看了koin的使用。 这里我就不介绍什么是依赖注入了,如果你不了解可以网上搜到很多文章讲解这个。大致可以可以接为,给一个对象注入另一个对象就算是一种"注入",依赖注入框架只是帮助我们封装对象的创建和赋值细节,按照一定的规则去创建我们想要的对象并注入到目标对象里。
总览
koin框架是一个纯Kotlin实现,使用Kotlin DSL去描述对象注入和对象间依赖关系以及创建规则。站在使用方的角度,我们需要理解Koin下面的几个组成部分:
- Koin:Koin框架的使用入口,Koin自身核心功能的实现处
- Definition:定义,指的是我们写在dsl里面的对象关系、创建规则的相关定义
- Scope:对象生命周期的管理者
- Factory:创建对象的工厂,包括了多种方式,例如单例、每次生成新对象、生命周期内内生成唯一对象
- inject:注入对象,获取注入的对象
这些不同的组成部分我绘制成一个示意图,可以在一段时间后快速帮助自己回忆起Koin的组成部分。(讲道理一段时间后还能让使用者记住他是"如何使用,每个模块的分工以及如何串起来"的依赖注入框架,我还没遇到过)
使用
使用 startKoin 进行初始化:
kotlin
// in Application
startKoin {
}
闭包里面可以指定modules的定义,定义modules的时候还可以定义scope、factory:
kotlin
modules {
scope(named("myscope")) {
scoped {
// 对象创建
...
}
factory {
// 对象创建,每次新创建一个,但是可以随着scope销毁
}
}
factory {
// 对象创建,每次新创建一个
...
}
}
然后我们在需要使用对象的地方去声明对象并注入对象:
kotlin
val obj by inject()
val objWithScope by getKoin().getOrCreatedScope("",named("")).inject()
当对象依赖其他对象的时候,也可以通过get()去获取他的依赖性:
kotlin
class Demo(val tag:String){}
// 参数调用get()
factory {
Demo(get()) // inject内部也调用的是get()
}
原理分析
为了更清晰的理解Koin的运作方式,我们大概来看下他几个关键模块的实现
初始化
startKoin的时候,GlobalContext会创建并初始化我们的KoinApplication对象,KoinApplication是实际的工作对象Koin的包装,负责初始化Koin对象、加载modules等。
加载modules
最终会调用 Koin 的 loadModules 方法: 这里最重要的就是两件事:
- InstanceRegistry加载Module
- ScopeRegistry加载Scope
loadModule
loadModule就是把给存放InstanceFactory的Map从Module读取到到InstanceRegistry里面: 那么Module里面的mapping是如何生成的呢?往回追溯,是我们在factory、scoped等方法的时候确定的,他们会通过indexPrimaryType去往mapping写入内容: mapping的key其实就是一个字符串索引,这个索引通过InstanceFactory的type+索引名+scope索引名来确定喂一性。 factory: scoped:
loadScopes
这个比loadModules更简单,就是把Module里面声明的scope读出来,存到ScopeRegistry里面去,这里也能看出来 InstanceRegistry和ScopeRegistry符合单一职责原则,各管各的。
到这一步,对于Koin对象来说,我声明的对象依赖和生命周期都被你读取过去了,那通过Koin帮我创建对象也就不是什么难事了。
inject-创建对象
当我们用 get
、inject
去注入对象的时候,Koin会根据module+scope的信息帮我们创建我们需要的对象。 inject、get在内部也都是调用的get: 七七八八经过几部调用,会调用InstanceRegistry的resolveInstance: 这时候就会从 _instances 里面取出相对于的 InstanceFactory 去创建对象: 至于这个内层的get方法,其实是Scope对象的方法。所以会结合特定的域的生命周期去创建对象。那外层直接调用 inject
的时候是调用的哪个 Scope 对象呢,那当然是 rootScope 的了,ScopeRegistry默认提供的一个Scope: 而InstanceFactory从最前面的模块图可以看出来,他有3个子类:
- SingleInstanceFactory: 创建的是单例
- ScopedInstanceFactory: 根据域创建,同一个域复用对象
- FactoryInstanceFactory:创建行为和父类默认一直,每次都创建新对象
总结
看到这里相信Koin你也基本掌握基础用法了。Koin的用法还不止这些基础用法,包括Jetpack组件注入的封装,包括Kotlin跨平台的支持。总得来看Koin还是有下面一些优势:
- 简单简洁易用,快速学习,容易上手
- 轻量,运行时,不影响编译速度,不会像dagger或者hilt一样生成一堆代码
- 支持和Kotlin跨平台一起使用,潜力无限