Android Koin 源码解析

548 阅读2分钟

前言

Koin 是一个用纯 Kotlin 编写的轻量级依赖注入(Dependency Injection, DI)框架,其设计目标是简单易用,不依赖代码生成或复杂的注解处理机制。

Github: github.com/InsertKoinI…

通过源码目录结构我们可以知道,Koin使用的是基于 Kotlin Multiplatform 开发的项目,所以能够实现多平台的依赖注入。

入门

官方文档

需要添加依赖

dependencies {
    implementation("io.insert-koin:koin-android:$koin_android_version")
}

示例代码如下:

class TestA(val type: String)

val factoryModule = module {
factory { TestA(System.currentTimeMillis().toString()) }
}

class KoinApp : Application() {

    override fun onCreate() {
        super.onCreate()
        // 1. 初始化
        startKoin {
            androidContext(this@KoinApp)
            modules(factoryModule)
        }
        
        // 2. 使用扩展函数 get(), 获取对应的实例。或是使用委托 https://book.kotlincn.net/text/delegated-properties.html
        val koinTestA: TestA = get()
    }
}
  1. 使用 startKoin 扩展方法初始化Koin

    1. androidContext: KoinApplication 的扩展方法,为了给内部的全局Context变量赋值,后续所有的module都可以通过get()方法获取这个context(Application)。
    2. modules:KoinApplication 中的方法,实际上调用的是 Koin 对象中的 loadModules 方法,往Map中注册对应的数据。
  2. 使用扩展方法 get() 获取实例

通过上面两步,我们就能够通过Koin实现依赖注入。那么下面就详细的分析一下Koin的实现原理。

源码分析

startKoin

 /**
* Start a Koin Application as StandAlone
*/
@KoinApplicationDslMarker
fun startKoin(koinApplication: KoinApplication): KoinApplication = KoinPlatformTools.defaultContext().startKoin(koinApplication)

/**
* Start a Koin Application as StandAlone
*/
// KoinAppDeclaration 是 KoinApplication 匿名拓展函数 KoinApplication.() -> Unit
@KoinApplicationDslMarker
fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication = KoinPlatformTools.defaultContext().startKoin(appDeclaration)

@KoinApplicationDslMarker
fun startKoin(appConfiguration: KoinConfiguration): KoinApplication = KoinPlatformTools.defaultContext().startKoin(appConfiguration.config)
 /**
* Koin Application
* Help prepare resources for Koin context
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
@KoinApplicationDslMarker
class KoinApplication private constructor() {
    val koin = Koin()
    private var allowOverride = true

    // ...

    /**
    * Load definitions from modules
    * @param modules
    */
    fun modules(modules: List<Module>): KoinApplication {
        if (koin.logger.isAt(Level.INFO)) {
            val duration = measureTime { loadModules(modules) }
    val count = koin.instanceRegistry.size()
            koin.logger.display(Level.INFO, "Started $count definitions in ${duration.inMs} ms")
        } else {
            loadModules(modules)
        }
        return this
    }
    
    private fun loadModules(modules: List<Module>) {
        koin.loadModules(modules, allowOverride = allowOverride, createEagerInstances = false)
    }
    
    // ...
}

KoinApplication中主要就是 Koin 字段和 modules 方法。有注释可以知道,主要是为 Koin 对象准备一些资源。

那Koin 中又主要有什么内容呢?

 /**
* Koin
*
* Gather main features to use on Koin context
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
class Koin {

    // ===============> 1. 实际存储各个module内容的对象
    @KoinInternalApi
    val scopeRegistry = ScopeRegistry(this) // 用于存储Scope

    @KoinInternalApi
    val instanceRegistry = InstanceRegistry(this) // 用于存储实例

    @KoinInternalApi
    val propertyRegistry = PropertyRegistry(this)

    @KoinInternalApi
    val extensionManager = ExtensionManager(this)

    @KoinInternalApi
    var logger: Logger = EmptyLogger()
        private set

    @KoinInternalApi
    fun setupLogger(logger: Logger) {
        this.logger = logger
    }

    // ====================> 2. 获取对应实例的方法
    /**
* Lazy inject a Koin instance
* @param qualifier
* @param scope
* @param parameters
*
* @return Lazy instance of type T
*/
inline fun <reified T : Any> inject(
        qualifier: Qualifier? = null,
        mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
        noinline parameters: ParametersDefinition? = null,
    ): Lazy<T> = scopeRegistry.rootScope.inject(qualifier, mode, parameters)

    /**
* Lazy inject a Koin instance if available
* @param qualifier
* @param scope
* @param parameters
*
* @return Lazy instance of type T or null
*/
inline fun <reified T : Any> injectOrNull(
        qualifier: Qualifier? = null,
        mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
        noinline parameters: ParametersDefinition? = null,
    ): Lazy<T?> = scopeRegistry.rootScope.injectOrNull(qualifier, mode, parameters)

    /**
* Get a Koin instance
* @param qualifier
* @param scope
* @param parameters
*/
inline fun <reified T : Any> get(
        qualifier: Qualifier? = null,
        noinline parameters: ParametersDefinition? = null,
    ): T = scopeRegistry.rootScope.get(qualifier, parameters)

    // ...
    
    /**
    * Load module & create eager instances
    *
    * @param allowOverride - allow to override definitions
    * @param createEagerInstances - run instance creation for eager single definitions
    */
    fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances : Boolean = false) {
        val flattedModules = flatten(modules)
        instanceRegistry.loadModules(flattedModules, allowOverride)
        scopeRegistry.loadScopes(flattedModules)
    
        if (createEagerInstances){
            createEagerInstances()
        }
    }

//......    
}

KoinApplication.modules

image.png

startKoin {
    //......
    modules(factoryModule)
}

// KoinApplication
/**
* Load definitions from modules
* @param modules
*/
fun modules(modules: List<Module>): KoinApplication {
    if (koin.logger.isAt(Level.INFO)) {
        val duration = measureTime { loadModules(modules) }
val count = koin.instanceRegistry.size()
        koin.logger.display(Level.INFO, "Started $count definitions in ${duration.inMs} ms")
    } else {
        loadModules(modules)
    }
    return this
}

private fun loadModules(modules: List<Module>) {
    koin.loadModules(modules, allowOverride = allowOverride, createEagerInstances = false)
}

// Koin
/**
* Load module & create eager instances
*
* @param allowOverride - allow to override definitions
* @param createEagerInstances - run instance creation for eager single definitions
*/
fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances : Boolean = false) {
    val flattedModules = flatten(modules)
    instanceRegistry.loadModules(flattedModules, allowOverride)
    scopeRegistry.loadScopes(flattedModules)

    if (createEagerInstances){
        createEagerInstances()
    }
}

InstanceRegistry 如下:

@Suppress("UNCHECKED_CAST")
@OptIn(KoinInternalApi::class)
class InstanceRegistry(val _koin: Koin) {

    private val _instances = safeHashMap<IndexKey, InstanceFactory<*>>()
    val instances: Map<IndexKey, InstanceFactory<*>>
        get() = _instances

    private val eagerInstances = safeHashMap<Int, SingleInstanceFactory<*>>()

    internal fun loadModules(modules: Set<Module>, allowOverride: Boolean) {
        modules.forEach { module ->
loadModule(module, allowOverride)
            addAllEagerInstances(module)
        }
}

    private fun addAllEagerInstances(module: Module) {
        module.eagerInstances.forEach { factory ->
eagerInstances[factory.beanDefinition.hashCode()] = factory
        }
}
    private fun loadModule(module: Module, allowOverride: Boolean) {
        module.mappings.forEach { (mapping, factory) ->
saveMapping(allowOverride, mapping, factory)
        }
}
    // ...

    @KoinInternalApi
    fun saveMapping(
        allowOverride: Boolean,
        mapping: IndexKey,
        factory: InstanceFactory<*>,
        logWarning: Boolean = true,
    ) {
        _instances[mapping]?.let {
if (!allowOverride) {
                overrideError(factory, mapping)
            } else if (logWarning) {
                _koin.logger.warn("(+) override index '$mapping' -> '${factory.beanDefinition}'")
            }
        }
_koin.logger.debug("(+) index '$mapping' -> '${factory.beanDefinition}'")
        _instances[mapping] = factory
    }

    internal fun resolveDefinition(
        clazz: KClass<*>,
        qualifier: Qualifier?,
        scopeQualifier: Qualifier,
    ): InstanceFactory<*>? {
        val indexKey = indexKey(clazz, qualifier, scopeQualifier)
        return _instances[indexKey]
    }
    // ...
}

从上述的源码可以知道,我们在调用modules后,实际上是遍历所有我们传入的Module对象,然后继续根据Module对象中 mappings 集合,分别将我们声明的实例化逻辑添加到Map中。那 mappingsmapping: IndexKey 以及 factory: InstanceFactory<*> 是什么东西呢?它们在什么时候被赋值的?我们接着往下看。

Module

先回忆一下我们是如何声明一个Module来管理我们的依赖对象的

// normalMoudle就是来管理常规的对象注入
val normalModule = module {
    factory { KoinTest() }
}
// singleModule则是用来单例对象注入
val singleModule = module {
    single { SingletonTest() }
}

/**
* Define a Module
* @param createdAtStart
*
*/
@KoinDslMarker
fun module(createdAtStart: Boolean = false, moduleDeclaration: ModuleDeclaration): Module {
    val module = Module(createdAtStart)
    moduleDeclaration(module)
    return module
}

Module类如下:

 /**
* Koin Module
* Gather/help compose Koin definitions
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
@KoinDslMarker
class Module(
    @PublishedApi
    internal val _createdAtStart: Boolean = false,
) {
    val id = KoinPlatformTools.generateId()

    var eagerInstances = LinkedHashSet<SingleInstanceFactory<*>>()
        internal set

    @KoinInternalApi
    val mappings = LinkedHashMap<IndexKey, InstanceFactory<*>>()

    // ...
    // =========================> 1
    /**
* Declare a Single definition
* @param qualifier
* @param createdAtStart
* @param definition - definition function
*/
inline fun <reified T> single(
        qualifier: Qualifier? = null,
        createdAtStart: Boolean = false,
        noinline definition: Definition<T>,
    ): KoinDefinition<T> {
        val factory = _singleInstanceFactory(qualifier, definition)
        indexPrimaryType(factory)
        if (createdAtStart || this._createdAtStart) {
            prepareForCreationAtStart(factory)
        }
        return KoinDefinition(this, factory)
    }
    
    @KoinInternalApi
    fun indexPrimaryType(instanceFactory: InstanceFactory<*>) {
        val def = instanceFactory.beanDefinition
        val mapping = indexKey(def.primaryType, def.qualifier, def.scopeQualifier)
        saveMapping(mapping, instanceFactory)
    }

    @PublishedApi
    internal fun saveMapping(mapping: IndexKey, factory: InstanceFactory<*>) {
        mappings[mapping] = factory
    }

    /**
* Declare a Factory definition
* @param qualifier
* @param definition - definition function
*/
inline fun <reified T> factory(
        qualifier: Qualifier? = null,
        noinline definition: Definition<T>,
    ): KoinDefinition<T> {
        return factory(qualifier, definition, rootScopeQualifier)
    }

    // =========================> 2
    @PublishedApi
    internal inline fun <reified T> factory(
        qualifier: Qualifier? = null,
        noinline definition: Definition<T>,
        scopeQualifier: Qualifier,
    ): KoinDefinition<T> {
        val factory = _factoryInstanceFactory(qualifier, definition, scopeQualifier)
        indexPrimaryType(factory)
        return KoinDefinition(this, factory)
    }
}

@KoinInternalApi
inline fun <reified T> _singleInstanceFactory(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>,
    scopeQualifier: Qualifier = rootScopeQualifier,
): SingleInstanceFactory<T> {
    val def = _createDefinition(Kind.Singleton, qualifier, definition, scopeQualifier = scopeQualifier)
    return SingleInstanceFactory(def)
}

@KoinInternalApi
inline fun <reified T> _factoryInstanceFactory(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>,
    scopeQualifier: Qualifier = rootScopeQualifier,
): FactoryInstanceFactory<T> {
    val def = _createDefinition(Kind.Factory, qualifier, definition, scopeQualifier = scopeQualifier)
    return FactoryInstanceFactory(def)
}

@KoinInternalApi
inline fun <reified T> _scopedInstanceFactory(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>,
    scopeQualifier: Qualifier,
): ScopedInstanceFactory<T> {
    val def = _createDefinition(Kind.Scoped, qualifier, definition, scopeQualifier = scopeQualifier)
    return ScopedInstanceFactory(def)
}
// BeanDefinition
inline fun indexKey(clazz: KClass<*>, typeQualifier: Qualifier?, scopeQualifier: Qualifier): String {
    return buildString {
append(clazz.getFullName())
        append(':')
        append(typeQualifier?.value ?: "")
        append(':')
        append(scopeQualifier)
    }
}
  1. single :每次注入对象都是不会变的,全局都是一个对象
  2. factory:每次注入都会生成一个新的对象

分别调用对应的方法创建 Factory对象,然后为InstanceFactory生成一个IndexKey并将它们作为KV存到mappings中,最后创建并返回 KoinDefinition。

到这我们就清楚,在前文XXXRegistry中提到的需要存储的IndexKeyFactory就是这个地方创建并且暂时存储到 mappings 中,用于后续过程的遍历使用。

分析到这,我们大致了解了 Koin 是如何声明并且存储创建实例的工厂逻辑的。那么我们在调用注入函数时,是如何创建实例的呢?

inject()/get()

在 Android 中,我们在调用inject()或get()方法后,实际上就是会走到下面这个逻辑。

@OptIn(KoinInternalApi::class)
inline fun <reified T : Any> ComponentCallbacks.get(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null,
): T {
    return getKoinScope().get(qualifier, parameters)
}

获取正确的Scope,从中获取实例。Scope又是什么呢?

Scope

image.png

@Suppress("UNCHECKED_CAST")
@OptIn(KoinInternalApi::class)
@KoinDslMarker
class Scope(
    val scopeQualifier: Qualifier,
    val id: ScopeID,
    val isRoot: Boolean = false,
    @PublishedApi
    internal val _koin: Koin,
) : Lockable() {
    private val linkedScopes = LinkedHashSet<Scope>()
    @KoinInternalApi
    var sourceValue: Any? = null

    val closed: Boolean
        get() = _closed

    inline fun isNotClosed() = !closed
    private val _callbacks = LinkedHashSet<ScopeCallback>()

    @KoinInternalApi
    private var parameterStack: ThreadLocal<ArrayDeque<ParametersHolder>>? = null

    private var _closed: Boolean = false
    val logger: Logger get() = _koin.logger

    internal fun create(links: List<Scope>) {
        linkedScopes.addAll(links)
    }

    /**
* Get a Koin instance
* @param clazz
* @param qualifier
* @param parameters
*
* @return instance of type T
*/
fun <T> get(
        clazz: KClass<*>,
        qualifier: Qualifier? = null,
        parameters: ParametersDefinition? = null,
    ): T {
        return resolve(clazz, qualifier,parameters?.invoke())
    }

    // =======================> 最终都会调用这个方法
    private fun <T> resolve(
        clazz: KClass<*>,
        qualifier: Qualifier?,
        parameters: ParametersHolder? = null
    ): T {
        if (!_koin.logger.isAt(Level.DEBUG)) {
            return resolveInstance(qualifier, clazz, parameters)
        }
        // ...
        val result = measureTimedValue { resolveInstance<T>(qualifier, clazz, parameters) }
        // ...
        return result.value
    }

    private fun <T> resolveInstance(
        qualifier: Qualifier?,
        clazz: KClass<*>,
        parameters: ParametersHolder?,
    ): T {
        //...
        val instanceContext = ResolutionContext(_koin.logger, this, clazz, qualifier, parameters)
        return stackParametersCall(parameters, instanceContext)
    }

    private fun onParameterOnStack(parameters: ParametersHolder): ArrayDeque<ParametersHolder> {
        val stack = getOrCreateParameterStack()
        stack.addFirst(parameters)
        return stack
    }

    private fun <T> resolveFromContext(
        instanceContext: ResolutionContext
    ): T {
        return resolveFromInjectedParameters(instanceContext)
            ?: resolveFromRegistry(instanceContext) // =========> 我们主要看这个,会从我们之前提到的xxxxRegistry中获取
            ?: resolveFromStackedParameters(instanceContext)
            ?: resolveFromScopeSource(instanceContext)
            ?: resolveFromParentScopes(instanceContext)
            ?: throwNoDefinitionFound(instanceContext)
    }

    private fun <T> resolveFromRegistry(
        ctx: ResolutionContext
    ): T? {
        return _koin.instanceRegistry.resolveInstance(ctx.qualifier, ctx.clazz, this.scopeQualifier, ctx)
    }

    private inline fun <T> resolveFromInjectedParameters(ctx: ResolutionContext): T? {
        return if (ctx.parameters == null) null
            else {
            _koin.logger.debug("|- ? ${ctx.debugTag} look in injected parameters")
            ctx.parameters.getOrNull(clazz = ctx.clazz)
        }
    }

    private inline fun <T> resolveFromStackedParameters(ctx: ResolutionContext): T? {
        val current = parameterStack?.get()
        return if (current.isNullOrEmpty()) null
         else {
            _koin.logger.debug("|- ? ${ctx.debugTag} look in stack parameters")
            val parameters = current.firstOrNull()
            parameters?.getOrNull(ctx.clazz)
         }
    }

    private inline fun <T> resolveFromScopeSource(ctx: ResolutionContext): T? {
        if (isRoot) return null
        _koin.logger.debug("|- ? ${ctx.debugTag} look at scope source")
        return if (ctx.clazz.isInstance(sourceValue) && ctx.qualifier == null) { sourceValue as? T } else null
    }

    private fun <T> resolveFromParentScopes(ctx: ResolutionContext): T? {
        _koin.logger.debug("|- ? ${ctx.debugTag} look in other scopes")
        return findInOtherScope(ctx)
    }

    private fun <T> findInOtherScope(
        ctx: ResolutionContext,
    ): T? {
        return linkedScopes.firstNotNullOfOrNull { it.getOrNull(ctx) }
}
}

在上述的代码可以看到,会依次从下列不同的地方尝试获取实例。

private fun <T> resolveFromContext(
    instanceContext: ResolutionContext
): T {
    return resolveFromInjectedParameters(instanceContext)
        ?: resolveFromRegistry(instanceContext) // 我们主要看这个,会从我们之前提到的xxxxRegistry中获取
        ?: resolveFromStackedParameters(instanceContext)
        ?: resolveFromScopeSource(instanceContext)
        ?: resolveFromParentScopes(instanceContext)
        ?: throwNoDefinitionFound(instanceContext)
}

private fun <T> resolveFromRegistry(
    ctx: ResolutionContext
): T? {
    return _koin.instanceRegistry.resolveInstance(ctx.qualifier, ctx.clazz, this.scopeQualifier, ctx)
}

// InstanceRegistry
internal fun resolveDefinition(
    clazz: KClass<*>,
    qualifier: Qualifier?,
    scopeQualifier: Qualifier,
): InstanceFactory<*>? {
    val indexKey = indexKey(clazz, qualifier, scopeQualifier)
    return _instances[indexKey]
}

// 其实这个地方就是使用下面的规则来标识实例
// 全类名 + typeQualifier(类别限定,防止在同一scope下出现多个相同的实例) + ScopeId
inline fun indexKey(clazz: KClass<*>, typeQualifier: Qualifier?, scopeQualifier: Qualifier): String {
    return buildString {
append(clazz.getFullName())
        append(':')
        append(typeQualifier?.value ?: "")
        append(':')
        append(scopeQualifier)
    }
}

还有一个问题,没有解决——怎么实现single{}和factory{}的?

其实也容易想到:在一个地方缓存下创建过的实例来实现Singleton。

这张图片就是继承InstanceFactory来实现不同功能的类。SingleInstanceFactory类中就包含 value 字段用于存储实例。

到这里,我们就大致的了解了Koin整体的运行逻辑。

总结

image.png

与Hilt的区别:

  • 运行时注入:Koin 没有像Hilt一样,在编译阶段为提供者和接受者生成对应的中间代码,而是将整个设计到的中间代码都抽象了出来。提供者只需要创建Module,接受者只需要调用get() 来进行注入。存在类型转化的性能开销
  • 支持提前创建实例:单例的创建能够在初始化时就进行创建
  • 错误暴露滞后:因为是运行时,所以在只有在运行的时候才能发现一些错误