科丁 基础知识

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


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 。 底线很简单-通过这种方式,您可以区分相同类型的两个依赖项。 例如,您需要根据情况获取应用程序或特定Activityontext ,因此在声明依赖项时需要为此指定标签。 Kodein允许Kodein声明一个没有标签的依赖项,它是基础的,如果您在接收到依赖项时未指定标签,我们将得到它,另外一个需要加标签,并且在收到依赖项时,需要指定标签。

 val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... } 

tag参数的类型为Any ,因此您不仅可以使用字符串。 但是请记住,用作标记的类必须实现equalshashCode方法。 始终有必要将标记作为命名参数传递给函数,而不管是创建依赖项还是接收依赖项。

依赖注入的类型


有几种在Kodein提供依赖关系的KodeinKodein -创建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) } } 

在上面的示例中,当我们首先获得具有参数510的依赖项时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 SubcomponentsDagger2呢? 一切都很简单,您只需要从Kodein实例继承,就可以访问继承人中父代的所有依赖项。

 val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } val subKodein = Kodein { extend(kodein) //   } 

重新定义


默认情况下,您无法覆盖依赖关系,否则用户会疯狂寻找应用程序无法正常工作的原因。 但是可以使用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开发人员共享获取依赖项的两种方式- injectionretieval 。 简而言之, injection是指类在创建时即在构造函数中接收所有依赖项的情况,而retrieval是指类本身负责获取其依赖项的情况。

使用injection您的类对Kodein并且该类中的代码更加Kodein ,但是,如果您使用Kodein ,那么您将有机会更灵活地管理依赖项。 在retrieval的情况下,仅在第一次依赖时才延迟retrieval所有依赖。

依赖关系的Kodein方法


Kodein类的实例具有三种返回依赖项,依赖项工厂或依赖项提供程序的方法-分别是instance()factory()provider() 。 因此,如果使用factoryprovider提供依赖项,则不仅可以接收函数执行的结果,还可以接收函数本身。 请记住,您可以在所有变体中使用标签。

  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的情况下,默认情况下所有依赖项的初始化都是延迟的。 这使您可以仅在需要时获取依赖关系,并在系统创建的类中获取依赖关系。

ActivityFragment和其他具有各自生命周期的类,全都与它们有关。

要在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() //     Gson 

 val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() /*...*/ myTrigger.trigger() //        kodeinWithTrigger 

懒惰的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    

调用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延迟初始化


对于延迟的初始化,Kodein存在一个对象LateInitKodein您可以创建该对象,将其创建委托给它,然后在初始化对象本身之后,将该属性设置为baseKodein,之后您就可以访问依赖项了。

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ gson.fromJson(someStr) 

获取指定类型的所有实例


您可以向Kodein索要指定类型的实例及其形式的所有后代List一切都只在指定的标记内。要做到这一点的方法有allInstancesallProvidersallFactories

  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是延迟的,因此您可以将实例初始化委托给KodeinKotlin中内置函数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(直接)使用直接访问主要区别在于不再有延迟初始化,在调用时将立即获得相关性instanceprovider并且具有类似的功能。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如果不确定使用什么,开发人员建议您这样做。但是,例如当您不能将两个参数强制转换为相同类型时,上下文可能会很有用。

例如,您拥有ActivityPresenter,并且您希望使用一个对象Kodein,根据接收它们的类,以不同的方式提供几种不同类型的依赖项。为了领导ActivityPresenter一种类型-你需要一个可选的接口,工厂将不得不检查所生成的参数的类型。该方案不是很方便。因此,我们看一下如何使用上下文:

 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)) //  } val inflater: LayoutInflater by instance() } 

正如你所看到的-你可以得到的,例如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兼容性

以及与文档的链接:


感谢您的阅读,希望本文对您有所帮助!

Source: https://habr.com/ru/post/zh-CN431696/


All Articles