Android中依赖项注入的未来

我提请您注意杰米·桑森Jamie Sanson)原始文章的译文
图片


在Android 9 Pie之前创建活动


依赖注入(DI)是一种常见的模型,出于多种原因,它被用于所有形式的开发中。 多亏了Dagger项目,它被用作Android开发中的模板。 Android 9 Pie的最新更改使我们在DI方面更加强大,尤其是使用新的AppComponentFactory类。




对于现代Android开发,DI非常重要。 这使您可以减少获取类之间使用的服务的链接时的代码总量,并且通常可以将应用程序很好地划分为多个组件。 在本文中,我们将重点介绍Dagger 2,这是Android开发中最常用的DI库。 假定您已经具有有关其工作原理的基本知识,但是没有必要了解所有的细微之处。 值得注意的是,本文有点冒险。 这很有趣,但在撰写本文时,Android 9 Pie甚至没有出现在平台版本面板上 ,因此,该主题可能至少几年都与日常开发无关。


今天在Android中进行依赖注入


简而言之,我们使用DI向我们的依赖类(即完成工作的那些)提供“依赖”类的实例。 假设我们使用存储库模式来处理与数据相关的逻辑,并希望在Activity中使用我们的存储库向用户显示一些数据。 我们可能希望在多个地方使用同一存储库,因此我们使用依赖注入使在一系列不同类之间共享同一实例更加容易。


首先,我们将提供一个存储库。 我们将在模块中定义Provides函数,使Dagger知道这正是我们要实现的实例。 请注意,我们的存储库需要一个上下文实例来处理文件和网络。 我们将为它提供应用程序上下文。


 @Module class AppModule(val appContext: Context) { @Provides @ApplicationScope fun provideApplicationContext(): Context = appContext @Provides @ApplicationScope fun provideRepository(context: Context): Repository = Repository(context) } 

现在,我们需要定义Component来处理要使用我们的Repository的类的实现。


 @ApplicationScope @Component(modules = [AppModule::class]) interface ApplicationComponent { fun inject(activity: MainActivity) } 

最后,我们可以配置Activity以使用我们的存储库。 假设我们在其他地方创建了ApplicationComponent的实例。


 class MainActivity: AppCompatActivity() { @Inject lateinit var repository: Repository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //    application.applicationComponent.inject(this) //       } } 

仅此而已! 我们只是使用Dagger在应用程序中设置依赖项注入。 有几种方法可以做到这一点,但这似乎是最简单的方法。


当前方法有什么问题?


在以上示例中,我们看到了两种不同类型的进样,一种比另一种更为明显。


您可能错过的第一个方法是在构造函数中嵌入 。 这是通过类的构造函数提供依赖项的方法,这意味着使用依赖项的类对实例的起源一无所知。 这被认为是依赖注入的最纯粹的形式,因为它完美地将注入逻辑封装到了我们的Module类中。 在我们的示例中,我们使用这种方法来提供存储库:


 fun provideRepository(context: Context): Repository = Repository(context) 

为此,我们需要Context ,它是在provideApplicationContext()函数中提供的。


我们看到的第二个更明显的事情是该类在该领域的实现MainActivity中使用了此方法来提供商店。 在这里,我们使用Inject注释将字段定义为注射的接收者。 然后,在onCreate函数中onCreate我们告诉ApplicationComponent必须将依赖项注入到我们的字段中。 它看起来不像嵌入到构造函数中那样干净,因为我们对组件有明确的引用,这意味着嵌入的概念正在渗入我们的依赖类中。 Android Framework类的另一个缺陷,因为我们需要确保首先要做的是提供依赖项。 如果这是在生命周期的错误时间点发生的,我们可能会意外地尝试使用尚未初始化的对象。


理想情况下,您应该完全摆脱类字段中的实现。 此方法会跳过不需要了解其信息的类的实现信息,并可能导致生命周期问题。 我们看到了尝试做得更好的尝试,而Android上的Dagger是一种非常可靠的方法,但最终如果只使用构造函数注入会更好。 当前,我们无法将这种方法用于许多框架类,例如“活动”,“服务”,“应用程序”等,因为它们是由系统为我们创建的。 目前看来,我们仍坚持将类引入领域。 尽管如此,Android 9 Pie正在准备一些有趣的事情,这可能会从根本上改变一切。


Android 9 Pie中的依赖注入


如本文开头所述,Android 9 Pie具有一个AppComponentFactory类。 它的文档非常稀少,并且可以这样简单地发布在开发者的网站上:


用于控制清单元素创建的接口。

这很有趣。 这里的“清单元素”是指我们在AndroidManifest文件中列出的类,例如Activity,Service和Application类。 这使我们能够“控制这些元素的创建”……所以,现在我们可以为创建活动创建规则了吗? 真高兴!


让我们深入研究。 我们将首先扩展AppComponentFactory并重写AppComponentFactory方法。


 class InjectionComponentFactory: AppComponentFactory() { private val repository = NonContextRepository() override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return when { className == MainActivity::class.java.name -> MainActivity(repository) else -> super.instantiateActivity(cl, className, intent) } } } 

现在,我们需要在application标签内的清单中声明我们的组件工厂。


 <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:name=".InjectionApp" android:appComponentFactory="com.mypackage.injectiontest.component.InjectionComponentFactory" android:theme="@style/AppTheme" tools:replace="android:appComponentFactory"> 

最后,我们可以启动我们的应用程序……它可以正常工作! 我们的NonContextRepository通过MainActivity构造函数提供的。 优雅地!


请注意,有一些预订。 我们不能在这里使用Context ,因为甚至在它存在之前,都会调用我们的函数-这很令人困惑! 我们可以走得更远,以便构造函数实现我们的Application类,但让我们看看Dagger如何使它变得更加容易。


见面-Dagger Multi-Binds


我将不深入探讨Dagger多重绑定操作的细节,因为这不在本文的讨论范围之内。 您需要知道的是,它提供了一种无需手动调用构造函数即可插入类构造函数的好方法。 我们可以使用它轻松地以可扩展的方式实现框架类。 让我们看看这一切如何加起来。


让我们先设置“活动”,以找出下一步的工作。


 class MainActivity @Inject constructor( private val repository: NonContextRepository ): Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //       } } 

这立即表明几乎没有提及依赖项注入。 我们唯一看到的是构造函数之前的Inject注释。


现在,您需要更改组件和Dagger模块:


 @Component(modules = [ApplicationModule::class]) interface ApplicationComponent { fun inject(factory: InjectionComponentFactory) } 

 @Module(includes = [ComponentModule::class]) class ApplicationModule { @Provides fun provideRepository(): NonContextRepository = NonContextRepository() } 

没什么大改变。 现在我们只需要实现组件工厂,但是如何创建清单元素? 这里我们需要一个ComponentModule 。 让我们看看:


 @Module abstract class ComponentModule { @Binds @IntoMap @ComponentKey(MainActivity::class) abstract fun bindMainActivity(activity: MainActivity): Any @Binds abstract fun bindComponentHelper(componentHelper: ComponentHelper): ComponentInstanceHelper } @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @Retention(AnnotationRetention.RUNTIME) @MapKey internal annotation class ComponentKey(val clazz: KClass<out Any>) 

是的,嗯,只是一些注释。 在这里,我们将Activity与地图联系起来,在ComponentHelper类中实现此地图,并提供此ComponentHelper全部在两个Binds指令中。 Dagger通过MainActivity注释知道如何实例化MainActivity Inject因此它可以将提供程序“绑定”到此类,自动提供构造函数所需的依赖项。 我们的ComponentHelper如下。


 class ComponentHelper @Inject constructor( private val creators: Map<Class<out Any>, @JvmSuppressWildcards Provider<Any>> ): ComponentInstanceHelper { @Suppress("UNCHECKED_CAST") override fun <T> resolve(className: String): T? = creators .filter { it.key.name == className } .values .firstOrNull() ?.get() as? T } interface InstanceComponentHelper { fun <T> resolve(className: String): T? } 

简而言之,我们现在为这些类别的供应商提供了一个类别地图。 当我们尝试通过名称解析一个类时,我们只需找到该类的提供程序(如果有),调用它以获取该类的新实例,然后将其返回。


最后,我们需要对AppComponentFactory进行更改以使用新的帮助器类。


 class InjectionComponentFactory: AppComponentFactory() { @Inject lateinit var componentHelper: ComponentInstanceHelper init { DaggerApplicationComponent.create().inject(this) } override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return componentHelper .resolve<Activity>(className) ?.apply { setIntent(intent) } ?: super.instantiateActivity(cl, className, intent) } } 

再次运行代码。 一切正常! 真高兴。


构造函数实施问题


这样的标题可能看起来并不令人印象深刻。 尽管我们可以通过将大多数实例注入到构造函数中来将其嵌入到普通模式中,但我们尚无明显的方法以标准方式为依赖项提供上下文。 但是Android中的Context就是全部。 访问设置,网络,应用程序配置等需要它。 我们的依赖关系通常是使用与数据相关的服务的事物,例如网络和设置。 我们可以通过将依赖关系重写为纯函数来解决此问题,或者通过在Application类中使用上下文实例初始化所有内容来解决此问题,但是要确定实现此目标的最佳方法还需要进行大量工作。


这种方法的另一个缺点是范围的定义。 在Dagger中,以良好的类关系分离来实现高性能依赖项注入的关键概念之一是对象图的模块化和范围的使用。 尽管此方法不禁止使用模块,但它限制了范围的使用。 与我们的标准框架类相比, AppComponentFactory存在于完全不同的抽象级别上-我们无法以编程方式获取到它的链接,因此我们无法指示它为不同范围内的Activity提供依赖项。


在实践中,有很多方法可以解决我们的范围问题,其中一种方法是使用FragmentFactory将片段嵌入到具有范围的构造函数中。 我不会详细介绍,但是事实证明,现在我们有了一种控制片段创建的方法,这不仅使我们在范围上具有更大的自由度,而且具有向后兼容性。


结论


Android 9 Pie引入了一种在构造函数中使用嵌入的方式,以在我们的框架类中提供依赖项,例如“ Activity”和“ Application”。 我们看到,使用Dagger Multi-binding,我们可以轻松地在应用程序级别提供依赖项。


实现我们所有组件的构造函数非常有吸引力,我们甚至可以做一些事情使其与上下文实例一起正常工作。 这是一个充满希望的未来,但是只能从API 28开始使用。如果您希望覆盖不到0.5%的用户,可以尝试一下。 否则,您应该等待,看看这种方法在几年后是否仍然有用。

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


All Articles