对于第一次
Kodein
人来说
Kodein
我找不到可理解的指南,并且该文档在所有地方都不是透明且一致的,因此,我想与您分享该库的主要功能。 一些库功能将发布,但这基本上是高级部分。 在阅读本文时,您会发现一切正常开始,并开始使用
Kodein
实现依赖关系。 本文基于
Kodein 5.3.0
,因为
Kodein 6.0.0
需要
Support Library 28
或
AndroidX
并且由于许多第三方库尚未提供兼容版本,因此绝对不会切换到它们。

Kodein
是用于实现依赖项注入(DI)的库。 如果您不熟悉此概念,请阅读
有关Dagger2的
文章的开头,作者在其中简要介绍DI的理论方面。
在本文中,我们将以Android为例来考虑所有内容,但据开发人员称,Kodein在Kotlin支持的所有平台(JVM,Android,JS,Native)上的行为均相同。
安装方式
由于Java具有
type erasure
的事实,因此出现了一个问题-编译器擦除了通用类型。 在字节码级别,
List<String>
和
List<Date>
只是
List
。 尽管如此,仍然有一种获取有关泛型类型的信息的方法,但是它将花费很多,并且仅在JVM和Android上有效。 在这方面,
Kodein
开发人员建议使用以下两种依赖关系之一:一种在工作时接收有关广义类型的信息(
kodein-generic
),而另一种则不使用(
kodein-erased
)。 例如,当使用
kodein-erased
List<String>
和
List<Date
>将被保存为
List<*>
,并且使用
kodein-generic
所有内容将与指定的类型一起保存,即
List<String>
和
List<Date>
。
如何选择?
不要在JVM下写-使用
kodein-erased
,否则是不可能的。
在JVM下进行写操作,性能问题对您来说非常重要-您可以使用
kodein-erased
,但是要小心,这种体验在这些词的意义上可能是意料之外的。 如果您创建的常规应用程序没有任何特殊的性能要求,请使用
kodein-generic
。
最终,如果考虑到DI对性能的影响,那么大多数依赖关系通常只创建一次,或者创建依赖关系以供重复使用,因此通过此类操作,您不太可能对应用程序的性能产生重大影响。
因此,安装:
首先-在存储库之间的build.gradle中应该是jcenter()(如果不存在)-添加。
buildscript { repositories { jcenter() } }
接下来,在依赖关系块中,添加上述基本依赖关系之一:
implementation "org.kodein.di:kodein-di-generic-jvm:$version"
implementation "org.kodein.di:kodein-di-erased-jvm:$version"
由于我们在谈论Android,因此将会有更多的依赖项。 您当然可以不用它,Kodein可以正常运行,但是为什么拒绝其他对Android有用的功能(我将在本文结尾处讨论它们)? 选择是您的,但我建议补充。
这里也有选项。
首先,您没有使用
SupportLibrary
implementation "org.kodein.di:kodein-di-framework-android-core:$version"
第二次使用
implementation "org.kodein.di:kodein-di-framework-android-support:$version"
第三-您正在使用AndroidX
implementation "org.kodein.di:kodein-di-framework-android-x:$version"
我们开始创建依赖关系
使用
Dagger2
,我习惯于在应用程序启动时在Application类中创建和初始化依赖项。
使用Kodein,可以这样完成:
class MyApp : Application() { val kodein = Kodein { } }
依赖项声明始终以
bind<TYPE>() with
标签
Kodein依赖项标记的功能与
Dagger2
Qualifier
Dagger2
。 在
Dagger2
您需要执行单独的
Qualifier
或使用
@Named("someTag")
,它实际上也是
Qualifier
。 底线很简单-通过这种方式,您可以区分相同类型的两个依赖项。 例如,您需要根据情况获取应用程序或特定
Activity
的
ontext
,因此在声明依赖项时需要为此指定标签。
Kodein
允许
Kodein
声明一个没有标签的依赖项,它是基础的,如果您在接收到依赖项时未指定标签,我们将得到它,另外一个需要加标签,并且在收到依赖项时,需要指定标签。
val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... }
tag
参数的类型为
Any
,因此您不仅可以使用字符串。 但是请记住,用作标记的类必须实现
equals
和
hashCode
方法。 始终有必要将标记作为命名参数传递给函数,而不管是创建依赖项还是接收依赖项。
依赖注入的类型
有几种在
Kodein
提供依赖关系的
Kodein
,
Kodein
-创建
Kodein
。 单例将位于创建的
Kodein
实例的框架内。
单身人士介绍
让我们从一个例子开始:
val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } }
因此,我们提供(提供)
IMyDatabase
,在
RoomDb
将隐藏
RoomDb
实例。
RoomDb
实例将在依赖项的第一个请求时创建;在
Kodein
新的
Kodein
实例之前,不会
Kodein
。 创建一个单例是同步的,但是如果需要,可以使它不同步。 这将提高生产率,但是您必须了解随之而来的风险。
val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } }
如果您不需要在第一次调用时而是在创建
Kodein
实例后立即创建
Kodein
实例,请使用另一个函数:
val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } }
不断创建依赖的新实例
在访问依赖关系以获取新实例时,可以不创建单调而是连续创建。 为此,使用了
provider
功能:
val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } }
在这种情况下,每次我们请求
IMainPresenter
依赖项时,都会创建一个新的
QuantityPresenter
实例。
不断创建依赖关系的新实例,并将参数传递给依赖关系的构造函数
就像上一个示例一样,每次添加依赖项时都可以获取它的新实例,但是要指定用于创建依赖项的参数。 参数最大为
5 。 为此,请使用
factory
方法。
val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } }
每次我们根据参数创建一个缓存实例
阅读上一段,您可能会认为,每次根据传递的参数接收一个新实例,而不是接收一个新实例,而是接收对相同参数的依赖项的相同实例,将是一个不错的选择。
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } }
在上面的示例中,当我们首先获得具有参数
5
和
10
的依赖项时
10
我们将创建一个
IntRandom(5, 10)
的新实例,当我们再次使用相同的参数调用该依赖项时,将获得先前创建的实例。 因此,获得了具有延迟初始化的单例
map
。 与
factory
参数最多为
5 。
与单调一样,您可以在此处禁用同步。
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } }
在Kodein中使用软链接和弱链接
使用
singleton
或多
singleton
提供依赖项时
multiton
可以指定对存储实例的引用类型。 在上面我们考虑的通常情况下,这将是通常的
strong
链接。 但是可以使用
soft
链接和
weak
链接。 如果您不熟悉这些概念,
请在此处查看 。
因此,您的单调可能会在应用程序生命周期中重新创建,也可能不是。
val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } }
每个流单独的单例
这是相同的单例,但是对于每个请求依赖项的线程,将创建一个单例。 为此,请使用熟悉的参数
ref
。
val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } }
常量作为可嵌入的依赖项
您可以提供常量作为依赖项。 该文档提请您注意以下事实:使用
Kodein
您必须
Kodein
不带继承或接口的简单类型的常量,例如基元或数据类。
val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url"
创建依赖项而不更改类型
例如,您希望将依赖项作为单例提供,但不要将其隐藏在接口后面。 您根本无法在调用
bind
时指定类型
from
而是使用
from
而不是
with
。
val kodein = Kodein { bind() from singleton { Gson() }
上面示例中的依赖项将具有函数的返回类型,即将
Gson
类型的依赖项。
创建超类或接口的子类依赖项
Kodein
允许
Kodein
以不同方式为实现单个接口的一个或多个特定类的后代提供依赖项。
val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } }
Animal
类可以是超类也可以是接口,使用
.subtypes
我们可以获得
TypeToken<*>
类型的
TypeToken<*>
,从中我们已经可以获取Java类,并根据它以不同的方式提供依赖性。 如果您在许多情况下都使用
TypeToken
或其派生类作为构造函数参数,则此功能很有用。 同样,通过这种方式,您可以避免为相同类型创建相同的依赖项而导致不必要的代码。
创建需要其他依赖项作为参数的依赖项
通常,我们不仅创建没有参数作为依赖的类,而且创建需要将参数传递给构造函数的类。
class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway
为了创建一个具有以前在
Kodein
创建的依赖关系的类,只需将instance()函数调用作为参数传递
Kodein
足够了。 在这种情况下,创建的顺序并不重要。
bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() }
除了
instance()
可能还有对
provider()
或
factory()
调用;我们将在获取和实现依赖项的部分中仔细研究这些方法。
通过调用先前创建的依赖方法来创建依赖
听起来不太好,但是您可以调用
instance<TYPE>
来获取我们已经在某个地方提供的类,然后调用该类的方法以获取新的依赖项。
bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() }
模组
使用
Dagger2
,我习惯于
Dagger2
依赖性。 乍一看,在
Kodein
,一切看起来都不太好。 您需要在
Application
类中创建很多依赖关系,而我个人并不喜欢它。 但是有一个解决方案,
Kodein
还允许您创建模块,然后在需要的地方将它们连接起来。
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } val kodein: Kodein = Kodein { import(appModule) bind<ISchedulersContainer>() with singleton { SchedulersContainer() }
但是请注意,模块只是声明用于获取依赖关系的方法的容器;它们本身不会创建类。 因此,如果您在模块中将接收的接收声明为单例,然后将此模块导入到
Kodein
两个不同实例中,那么您将获得两个不同的单例,每个
Kodein
实例一个。
另外,每个模块的名称必须唯一。 但是,如果需要从另一个项目导入模块,则很难保证名称的唯一性;为此,您可以重命名模块或在其名称上添加前缀。
import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-"))
当模块相互依赖并构成某种层次结构时,我习惯于工作。
Kodein
可以一次将每个模块导入
Kodein
,因此,如果尝试将具有相同相关模块的两个模块导入一个
Kodein
,则应用程序将崩溃。 解决方案很简单-您需要使用
importOnce(someModule)
调用进行导入,这将检查先前是否导入了具有相同名称的模块,然后在必要时进行导入。
例如,在这种情况下,应用程序将崩溃:
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { importOnce(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
但是,如果
importOnce
调用正在第二次尝试连接,那么一切都会正常。 小心点
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { importOnce(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
传承
如果两次使用同一模块,将创建不同的依赖关系,但是继承和实现类似于
Dagger2
Subcomponents
的
Dagger2
呢? 一切都很简单,您只需要从
Kodein
实例继承,就可以访问继承人中父代的所有依赖项。
val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() }
重新定义
默认情况下,您无法覆盖依赖关系,否则用户会疯狂寻找应用程序无法正常工作的原因。 但是可以使用
bind
函数的附加参数来执行此操作。 此功能对于组织测试非常有用。
val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } bind<Api>(overrides = true) with singleton { OtherApiImpl() } }
默认情况下,模块及其依存关系不能覆盖
Kodein
对象中已声明的依存关系,但是在导入模块时,您可以指示现有的依存关系可以覆盖其依存关系,并且在该模块内部,您可以指定其他人可以覆盖的依存关系。
听起来不太清楚,让我们使用示例。 在这些情况下,应用程序将崩溃:
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule) }
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) }
并且在此情况下,模块依赖项将覆盖
Kodein
对象中声明的依赖
Kodein
。
val appModule = Kodein.Module("app") { bind<Gson>(overrides = true) with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) }
但是,如果您确实想要并且了解自己在做什么,那么可以创建一个模块,如果该模块与
Kodein
对象具有相同的依赖关系
Kodein
则将重新定义它们,并且应用程序不会崩溃。 我们为模块使用
allowSilentOverride
参数。
val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } }
该文档讨论了有关继承和重新定义依赖关系以及在继承人中复制依赖关系的更为复杂的情况,但此处将不考虑这些情况。
检索和注入依赖项
最后,我们弄清楚了如何以多种方式声明依赖关系,是时候弄清楚如何在其类中获取依赖关系了。
Kodein
开发人员共享获取依赖项的两种方式-
injection
和
retieval
。 简而言之,
injection
是指类在创建时即在构造函数中接收所有依赖项的情况,而
retrieval
是指类本身负责获取其依赖项的情况。
使用
injection
您的类对
Kodein
并且该类中的代码更加
Kodein
,但是,如果您使用
Kodein
,那么您将有机会更灵活地管理依赖项。 在
retrieval
的情况下,仅在第一次依赖时才延迟
retrieval
所有依赖。
依赖关系的Kodein
方法
Kodein
类的实例具有三种返回依赖项,依赖项工厂或依赖项提供程序的方法-分别是
instance()
,
factory()
和
provider()
。 因此,如果使用
factory
或
provider
提供依赖项,则不仅可以接收函数执行的结果,还可以接收函数本身。 请记住,您可以在所有变体中使用标签。
val kodein: Kodein = Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by factory() private val random: Random by instance() private val randomProvider: () -> Random by provider()
通过构造函数进行依赖注入
如您所知,这将与
injection
有关。 要实现,必须首先将类的所有依赖项放入其构造函数中,然后通过调用
kodein.newInstance
创建该类的实例。
class ProductApi(private val client: HttpClient, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instance(), instance()) } }
可空属性中的依赖注入
很可能您不知道是否已声明依赖项。 如果未在
Kodein
实例中声明依赖
Kodein
,则上例中的代码将导致
Kodein.NotFoundException
。 如果要获得
null
(如果没有依赖关系),则可以使用三个辅助函数:
instanceOrNull()
,
factoryOrNull()
和
providerOrNull()
。
class ProductApi(private val client: HttpClient?, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instanceOrNull(), instance()) } }
获取类内部的依赖关系。
如前所述,在使用
retrieval
的情况下,默认情况下所有依赖项的初始化都是延迟的。 这使您可以仅在需要时获取依赖关系,并在系统创建的类中获取依赖关系。
Activity
,
Fragment
和其他具有各自生命周期的类,全都与它们有关。
要在
Activity
实现依赖关系
Activity
我们只需要链接到Kodein实例,此后便可以使用众所周知的方法。 实际上,您已经在上面看到了
retrieval
示例,您只需要声明一个属性并将其委托给以下功能之一:
instance()
,
factory()
或
provider()
private val number: BigDecimal by kodein.instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by kodein.factory() private val random: Random? by kodein.instanceOrNull() private val randomProvider: (() -> Random)? by kodein.providerOrNull()
将参数传递给工厂
您已经看到,为了将参数传递给工厂,使用
instance
函数的
arg
参数就足够了。
但是,如果有几个参数(我之前说过一个工厂最多可以有5个参数)怎么办?您只需要将一个arg
类传递给M
具有重载构造函数的参数,并且可以使用2到5个参数。 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } val picker: IColorPicker by kodein.instance(arg = M(255, 211, 175, 215))
强制依赖初始化
正如他们所说-默认情况下,初始化是惰性的,但是您可以创建一个触发器,将其绑定到一个属性,多个属性或整个实例上Kodein
,然后再拉动该触发器,并将初始化依赖项。 val myTrigger = KodeinTrigger() val gson: Gson by kodein.on(trigger = myTrigger).instance() myTrigger.trigger()
val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() myTrigger.trigger()
懒惰的Kodein实例创建
在此之前,我们一直在显式创建一个实例Kodein
,但是可以使用LazyKodein
在构造函数中采用应返回一个对象的函数的类来延迟此属性的初始化Kodein
。例如,如果完全不知道是否需要来自给定Kodein实例的依赖项,则此方法很有用。 val kodein: Kodein = LazyKodein { Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } } private val number: BigDecimal by kodein.instance(arg = "13.4") number.toPlainString()
调用Kodein.lazy将导致类似的结果。 val kodein: Kodein = Kodein.lazy { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by kodein.instance(arg = "13.4") number.toPlainString()
Kodein延迟初始化
对于延迟的初始化,Kodein
存在一个对象LateInitKodein
。您可以创建该对象,将其创建委托给它,然后在初始化对象本身之后,将该属性设置为baseKodein
,之后您就可以访问依赖项了。 val kodein = LateInitKodein() val gson: Gson by kodein.instance() kodein.baseKodein = gson.fromJson(someStr)
获取指定类型的所有实例
您可以向Kodein索要指定类型的实例及其形式的所有后代List
。一切都只在指定的标记内。要做到这一点的方法有allInstances
,allProviders
,allFactories
。 val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by kodein.allInstances()
如果您打印到日志,您将在此处看到[32767,136.88,4562,12.46]。带有标签的依赖项不在列表中。使用KodeinAware界面简化依赖获取
此接口使您必须重写type属性Kodein
,并且作为返回,它提供对实例可用的所有功能的访问Kodein
。 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by allInstances() }
如您所见,现在您可以简单地编写by allInstances()
而不是by kodein.allInstances()
以前的内容,我们已经讨论了接收依赖项的触发器。在界面中,KodeinAware
可以覆盖触发器,并在调用此触发器时获取所有声明的依赖项。 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } override val kodeinTrigger = KodeinTrigger() val numbers: List<Number> by allInstances() override fun onCreate() { super.onCreate() kodeinTrigger.trigger() } }
由于对依赖项和实例的访问Kodein
是延迟的,因此您可以将实例初始化委托给Kodein
Kotlin中的内置函数lazy
。根据类的上下文,这种方法在类中可能很有用,例如在中Activity
。 class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance()
出于相同的原因,可以使用修饰符lateinit
。 class CategoriesActivity : Activity(), KodeinAware { override lateinit var kodein: Kodein private val myFloat: Float by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) kodein = (application as MyApplication).kodein }
无需委派属性即可访问依赖项
如果由于某种原因您不想使用属性委托,则可以通过DKodein
(直接)使用直接访问。主要区别在于不再有延迟初始化,在调用时将立即获得相关性instance
,provider
并且具有类似的功能。您DKodein
可以从现有的Kodein实例中获取它,也可以从头开始构建。 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with singleton { BigDecimal.TEN } } val directKodein: DKodein = kodein.direct val directKodein2: DKodein = Kodein.direct { bind<BigDecimal>() with singleton { BigDecimal.ONE } } val someNumber:BigDecimal = directKodein.instance() val someNumber2:BigDecimal = directKodein2.instance()
Kodein可以在框架中使用KodeinAware
,并且可以DKodein
在框架中DKodeinAware
进行实验。在任何上下文中获取依赖项
为了从一个对象中获取Kodein
多个相同类型的依赖项,我们已经研究了使用带参数的标签和工厂的选择,但是还有另外一件事-使用上下文(这不是Android中的上下文)。与带有标签的依赖项的区别:- 标签不能在我们创建依赖关系的函数中使用
- 使用上下文时,我们可以在依赖项创建函数中访问上下文实例
通常,您可以使用带有参数的工厂来代替上下文,并且Kodein
如果不确定使用什么,开发人员建议您这样做。但是,例如当您不能将两个参数强制转换为相同类型时,上下文可能会很有用。例如,您拥有Activity
和Presenter
,并且您希望使用一个对象Kodein
,根据接收它们的类,以不同的方式提供几种不同类型的依赖项。为了领导Activity
和Presenter
一种类型-你需要一个可选的接口,工厂将不得不检查所生成的参数的类型。该方案不是很方便。因此,我们看一下如何使用上下文: class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with contexted<CategoriesActivity>().provider { context.getActivityBigDecimal() } bind<BigDecimal>() with contexted<CategoriesPresenter>().factory { initialValue:BigDecimal -> context.getPresenterBigDecimal(initialValue) } } } class CategoriesActivity : Activity(), AppKodeinAware { fun getActivityBigDecimal() = BigDecimal("16.34") private val activityBigDecimal: BigDecimal by kodein.on(context = this).instance() } class CategoriesPresenter : AppKodeinAware { fun getPresenterBigDecimal(initialValue: BigDecimal) = initialValue * BigDecimal.TEN private val presenterBigDecimal: BigDecimal by kodein.on(context = this).instance(arg = BigDecimal("31.74")) }
当然,有个例子会让人耳目一新,实际上,您不太可能会遇到这种情况,但是这个例子说明了上下文是如何工作的。要声明依赖关系,您可以指定不是with provider()
,而是指定with contexted<OurContextClass>().provider
,其中OurContextClass
类的类型为,类的实例将作为上下文。contexted
只能是提供商或工厂。通过名为的变量访问返回依赖关系的函数中的此上下文context
。要获得与上下文的依赖关系,您首先需要Kodein
通过函数指定对象的上下文on()
,然后请求依赖关系。类似地,在中使用上下文injection
。 private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } }
Android扩展
在本文开头,我答应考虑的扩展选项Android
。如上所述,没有什么可以阻止您使用Kodein
它的,但是您可以使一切变得更方便。内置Android的Kodein
一个非常有用的东西是为Android准备的模块。要连接它,该类有必要延迟Application
实现KodeinAware
和初始化属性Kodein
(以访问实例Application
)。作为回报,您可以从类中获得大量已声明的依赖关系Application
,包括所需的一切Context
。如何连接-看一个例子。 class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication))
正如你所看到的-你可以得到的,例如LayoutInflater
。有关模块中声明的依赖关系的完整列表,请参见此处。如果要将这些依赖项从知道其上下文的Android类之外获取,请显式指定上下文。 val inflater: LayoutInflater by kodein.on(context = getActivity()).instance()
通过最接近的科德()快速获取父级科丁()
很简单,在Android中,某些对象依赖于其他对象。顶层是应用程序,其下是活动,然后是片段。您可以在Activity中实现KodeinAware
,并作为初始化进行调用closestKodein()
,从而Kodein
从获取实例Application
。 class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
您也可以在Android类之外获取它,但是您需要一个Android上下文才能从中调用该函数。如果使用它KodeinAware
,则还要为其指定上下文(重写相应的属性,并将Android上下文传递给函数kcontext()
)。 class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() }
在活动中创建一个单独的Kodein
很有必要从Activity中的父Kodein继承并进行扩展。解决方案非常简单。 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) } }
Kodein正在进行配置更改
是的,可以。有一个功能retainedKodein
。使用它时,Kodein
更改配置后将不会重新创建对象。 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } }
文章中没有说什么?
我并没有假装自己是完整的,而且我本人对某些事情也不了解,无法尝试陈述它们。以下列出了您可以了解的基本原理,可以自己学习:- 范围
- 实例绑定
- 多重装订
- 即时回调
- 外部来源
- 删除版本的陷阱
- 可配置的Kodein
- JSR-330兼容性
以及与文档的链接:感谢您的阅读,希望本文对您有所帮助!