使用Hilt的Android DI

I have been developing Android applications since 2012, and at that time the main dependency injection library was RoboGuice. Dagger 1 went 1.0 in May of 2013. Dagger 2’s initial release was in May of 2015. I have had the privilege to use all three libraries and see the change to dependency injection for Android over the years.

自2012年以来,我一直在开发Android应用程序,当时主要的依赖项注入库是RoboGuice。 Dagger 1于2013年5月发布1.0。Dagger2的首次发布于2015年5月。我荣幸地使用了这三个库,并看到了多年来Android依赖注入的变化。

RoboGuice was an Android port of Google Guice. It supported more features than just dependency injection, such as layout injection. RoboGuice depended heavily on reflection and one would notice that impact on startup of the app, as well as only being able to find problems during runtime.

RoboGuice是Google Guice的Android版本。 它不仅支持依赖项注入(例如布局注入),还支持更多功能。 RoboGuice在很大程度上依赖于反思,并且人们会注意到这对应用程序启动的影响,并且只能在运行时发现问题。

Dagger 1 added an annotation processor. This did two things:

Dagger 1添加了注释处理器。 这做了两件事:

  1. It provided build time verification

    它提供了构建时间验证
  2. It began the migration away from reflection

    它开始了从反思的迁移

    Dagger 1 however didn’t completely get rid of reflection¹ so some errors were only found at runtime, however due to how the graph was built those errors tended to show when the graph was created.

    但是,Dagger 1并未完全摆脱反射¹,因此仅在运行时发现了一些错误,但是由于图形的构建方式,这些错误倾向于在创建图形时显示。

Dagger 2 (Dagger for the rest of the article) completed the migration from reflection to only use generated code. This allowed developers to remove Dagger from proguard files since all the code is static and compiled. All Dagger issues can now be found at compile time, improving on Dagger 1’s build time verification. Also using this generated code, startup times for building the graph was significantly improved. Dagger, while awesome and beneficial, still has some drawbacks:

Dagger 2(本文其余部分为Dagger)完成了从反射到仅使用生成代码的迁移。 由于所有代码都是静态的并且已编译,因此开发人员可以从Proguard文件中删除Dagger。 现在可以在编译时发现所有Dagger问题,从而改进了Dagger 1的构建时间验证。 同样,使用此生成的代码,可以大大缩短构建图形的启动时间。 匕首虽然很棒而且有益,但仍然存在一些缺点:

  1. It has a steep learning curve

    学习曲线陡峭
  2. It can get really complex when dealing with scopes

    在处理范围时可能会变得非常复杂
  3. It can have a lot of boilerplate

    它可以有很多样板
  4. It has an in-code dependency on generated code

    它对生成的代码具有代码内依赖性

Dagger for Android tried to help but exacerbated these issues.

适用于Android的Dagger试图帮助但加剧了这些问题。

Hilt was built on top of Dagger so it retains all of Dagger’s benefits, and addresses these issues.

Hilt建立在Dagger之上,因此保留了Dagger的所有优点,并解决了这些问题。

入门 (Getting Started)

To use Dagger, one has to understand Components, Modules, component creation, the difference between constructor and field injection, and how the component ties to field injection just to get started. Then if one wants to do something cool we add scopes like Singleton and Reusable, not to mention custom scopes and SubComponents, Binds. The list can just keep going.

要使用Dagger,必须了解ComponentsModules ,组件创建,构造函数和字段注入之间的区别,以及如何将组件与字段注入联系起来。 然后,如果想要做一些很酷的东西,我们添加范围像SingletonReusable ,更何况自定义范围和SubComponentsBinds 。 该清单可以继续下去。

Hilt doesn’t completely remove the learning curve but it does make it manageable. After including the libraries, annotation processor, and plugin (more on that later), using Hilt is fairly straight-forward.

击键并不能完全消除学习曲线,但可以使学习曲线变得易于管理。 包括库,注释处理器和插件(稍后会详细介绍)之后,使用Hilt非常简单。

  1. Annotate the Application class with @HiltAndroidApp

    使用@HiltAndroidApp注释Application

  2. Annotate any Activities, Fragments, Services, Views, and BroadcastReceivers with @AndroidEntryPoint

    使用@AndroidEntryPoint注释任何ActivitiesFragmentsServicesViewsBroadcastReceivers @AndroidEntryPoint

  3. Now inject dependencies in these classes using the following format:

    现在,使用以下格式在这些类中注入依赖项:

    @Inject lateinit var bar: Bar

    @Inject lateinit var bar: Bar

    @Inject lateinit var gson: Gson

    @Inject lateinit var gson: Gson

    This is field Injection.

    这是现场注入。

For Hilt and Dagger to inject these dependencies, we must tell Dagger how to create it. In our classes, simply annotate the constructor with @Inject: class Bar @Inject constructor(private val foo: Foo){} This is constructor injection. Classes provided by Android or other libraries must be provided with a Module:

为了使Hilt和Dagger注入这些依赖项,我们必须告诉Dagger如何创建它。 在我们的类中,只需使用@Inject注释构造函数: class Bar @Inject constructor(private val foo: Foo){}这是构造函数注入。 Android或其他库提供的类必须随模块一起提供:

@Module
@InstallIn(ApplicationComponent::class)
class AppModule {
@Provides
fun provideGson(): Gson {
return GsonBuilder()
.registerTypeAdapter(
OffsetDateTime::class.java, OffsetDateTimeTypeAdapter()
).registerTypeAdapter(
LocalTime::class.java, LocalTimeTypeAdapter()
).create()
}
}

And that will get you started using Hilt for dependency injection².

这将使您开始使用Hilt进行依赖项注入²。

范围 (Scopes)

Scopes are really important for managing memory and object creation. Dagger provides three scopes by default.

范围对于管理内存和对象创建非常重要。 Dagger默认提供三个范围。

  1. No scope as shown in the previous examples. Dagger will create a new instance of the object every time it is injected.

    如上例所示,没有作用域。 每次注入时,Dagger都会创建该对象的新实例。
  2. @Singleton which maintains one instance of the object for the entire lifespan of the graph.

    @Singleton会在图形的整个生命周期中维护对象的一个​​实例。

  3. @Reusable, my personal favorite, this scope holds on to and reuses an instance as long as there is memory for it. @Reusable does a great job of balancing memory and object creation impact.

    @Reusable ,我个人最喜欢的,只要有内存,该作用域就会保留@Reusable用实例。 @Reusable在平衡内存和对象创建影响@Reusable做得很好。

While these scopes work well, there are many times where these scopes are too broad, or to random to be effective. Dagger allows developers to define custom scopes, but to fully utilize them, Components and/or SubComponents have to be created and maintained. Hilt has added five scopes tied directly to the Android lifecycle that provides those needed scopes without incurring the extra complexity. These scopes are:

尽管这些范围运作良好,但在很多情况下,这些范围太宽泛,或者太随意而无法生效。 Dagger允许开发人员定义自定义范围,但要充分利用它们,必须创建和维护Components和/或SubComponents Components 。 Hilt已添加了五个与Android生命周期直接相关的范围,这些范围提供了这些所需的范围,而不会引起额外的复杂性。 这些范围是:

  1. @Singleton | ApplicationComponent(This was already provided by Dagger but Hilt ties it to the Application lifecycle)

    @Singleton | ApplicationComponent (Dagger已经提供了该组件,但Hilt将其与Application生命周期联系在一起)

  2. @ActivityRetainedScoped |ActivityRetainedComponent

    @ActivityRetainedScoped | ActivityRetainedComponent

  3. @ActivityScoped | ActivityComponent

    @ActivityScoped | ActivityComponent

  4. @FragmentScoped | FragmentComponent

    @FragmentScoped | FragmentComponent

  5. @ViewScoped | ViewComponent and ViewWithFragmentComponent

    @ViewScoped | ViewComponentViewWithFragmentComponent

  6. @ServiceScoped | ServiceComponent

    @ServiceScoped | ServiceComponent

Dagger provides some great documentation about these scopes, when they are created and destroyed and additional hierarchy. These scopes are tied to the lifecycle of their Android counterparts. Similar to Android’s class hierarchy, scopes can inherit from parent scopes; i.e. fragments can use objects that are scoped to fragment, activity, and application. If Bar is used in fragments and activities, it should be scoped to Activity like so:@ActivityScoped class Bar @Inject constructor(private val foo: Foo){} If gson is only used in services we can scope it as follows:

Dagger在创建和销毁这些作用域以及其他层次结构时提供了一些出色的文档 。 这些范围与Android同类产品的生命周期息息相关。 与Android的类层次结构类似,范围可以从父范围继承。 即,片段可以使用范围限定为片段,活动和应用程序的对象。 如果Bar用于片段和活动中,则应将其范围限定为Activity,如下所示: @ActivityScoped class Bar @Inject constructor(private val foo: Foo){}如果gson仅用于服务中,则可以对其进行如下范围调整:

@Module@InstallIn(ServiceComponent::class)
class AppModule {
@Provides
@ServiceScoped fun provideGson(): Gson {
return GsonBuilder()
.registerTypeAdapter(
OffsetDateTime::class.java, OffsetDateTimeTypeAdapter()
).registerTypeAdapter(
LocalTime::class.java, LocalTimeTypeAdapter()
).create()
}
}

Since Hilt removes the need for building our own Components, we need a way to link our Modules with the appropriate component. That is where @InstallIn comes into play. This allows developers to better compartmentalize dependencies by scope easily and readers of the module can quickly see where the dependencies, especially unscoped dependencies, are mainly used.

由于Hilt消除了构建自己的Components需要,因此我们需要一种将Modules与适当的组件链接的方法。 这就是@InstallIn起作用的地方。 这使开发人员可以更好地按范围更好地划分依赖关系,并且模块的读者可以快速查看主要在哪里使用依赖关系,尤其是无范围的依赖关系。

These scopes provide a simple way to implement the best practices to maintain a small memory footprint and still benefit from sharing objects.

这些作用域提供了一种实现最佳实践的简单方法,以维持较小的内存占用空间,并且仍然受益于共享对象。

样板 (Boilerplate)

Why is Boilerplate mentioned so much in connection with dependency injection? Developers like to write code, especially interesting code. Boilerplate is not interesting code. But what exactly is boilerplate code? My favorite definition of boilerplate is:

为什么在依赖注入方面提到Boilerplate这么多? 开发人员喜欢编写代码,尤其是有趣的代码。 Boilerplate不是有趣的代码。 但是样板代码到底是什么? 我最喜欢的样板定义是:

Something that is repetitive that can be automated.Jake Wharton — Helping Dagger Help You @ 1:52

重复的东西可以自动化。杰克·沃顿( Jake Wharton)– 帮助匕首帮助您@ 1:52

Components are repetitive and can be automated. By introducing two new annotations @HiltAndroidApp and @AndroidEntryPoint the vast majority of boilerplate is removed from Dagger. But generally there is only one main component per application, so how is that boilerplate? If you only ever write one application, then yes, the main component is not repetitive. However, many developers write multiple applications, making that component repetitive. Additionally subcomponents are extremely repetitive, especially if you extend them across seven different scopes.

组件是重复的,可以自动化。 通过引入两个新的注释@HiltAndroidApp@AndroidEntryPoint ,从Dagger中删除了大部分样板。 但是通常每个应用程序只有一个主要组件,那么该样板如何? 如果您只编写一个应用程序,那么可以,主要组件不是重复的。 但是,许多开发人员编写了多个应用程序,从而使该组件重复出现。 另外,子组件具有极高的重复性,尤其是在将它们扩展到七个不同的作用域时。

Hilt added support for ViewModels and Workers. I wrote another article and released a pair of libraries that provide constructor injection for both ViewModels and Workers. Thankfully, Hilt provides all this functionality for us in a very similar manner, providing great value and preventing a ton of boilerplate code. It would be really cool/beneficial if Hilt added more integrations with other Jetpack factory based libraries.

Hilt添加了对ViewModelsWorkers支持。 我写了另一篇文章,并发布了两个库,它们为ViewModelsWorkers提供构造函数注入。 值得庆幸的是,Hilt以非常相似的方式为我们提供了所有这些功能,提供了巨大的价值并避免了很多样板代码。 如果Hilt添加了与其他基于Jetpack工厂的库的更多集成,那将真的很酷/有益。

对生成代码的代码内依赖 (In-code dependency on generated code)

It is really annoying when you have to build a project to add code. The Dagger annotation processor generates a class or classes that extend your components that have to be referenced directly in code. So to get any help from the IDE to prevent having to write import statements or get code completion, you have to build your project after creating components. This seems like a petty thing, but when you are adding Dagger to multiple projects or starting to use Dagger for the first time it is very painful. To make it so there is no reflection and no proguard configuration for Dagger this code has to be referenced. The Hilt developers solved this with the plugin they added. The plugin changes every @AndroidEntryPoint to extend the generated class. While the plugin isn’t required, you can manually make this change, why not let the plugin and build system do all of that work for you.

当您必须构建一个项目以添加代码时,这确实很烦人。 Dagger注释处理器会生成一个或多个扩展您必须直接在代码中引用的组件的类。 因此,要从IDE获得任何帮助以防止必须编写导入语句或完成代码,就必须在创建组件之后构建项目。 这似乎是一件小事,但是当您将Dagger添加到多个项目中或第一次开始使用Dagger时,这非常痛苦。 为了做到这一点,没有反射,也没有Dagger的proguard配置,必须引用此代码。 Hilt开发人员使用他们添加的插件解决了这个问题。 该插件会更改每个@AndroidEntryPoint以扩展生成的类。 尽管不需要插件,但是您可以手动进行更改,为什么不让插件和构建系统为您完成所有这些工作。

Hilt is awesome. It takes a great library and simplifies it, adding great functionality for Android development. I am grateful the Jetpack team listened to the Android developer community, giving an opinion on Android development and developing some amazing libraries for us.

剑拔is张。 它需要一个出色的库并对其进行了简化,从而为Android开发添加了出色的功能。 我非常感谢Jetpack团队听取了Android开发人员社区的意见,对Android开发提出了意见,并为我们开发了一些很棒的库。

  1. Reflection is an amazing and powerful tool, however it has some drawbacks that impact significantly performance and when used improperly. Retrofit is a great example of proper reflection use, infrequent use and performance impacts are overshadowed by other long running operations (I/O).

    反射是一种了不起的功能强大的工具,但是它具有一些缺点,这些缺点会严重影响性能以及使用不当的情况。 翻新是正确使用反射的一个很好的例子,不经常使用,对性能的影响被其他长时间运行的操作(I / O)所掩盖。
  2. Migration from Dagger to Hilt isn’t that difficult either. I took a 65K loc project and migrated to Hilt in four hours.

    从Dagger迁移到Hilt也不难。 我参加了一个65K loc项目,并在四个小时内迁移到Hilt。

Update 2020–07–30: With the release of dagger-hilt 2.28.2-alpha ApplicationComponent is to be renamed to SingletonComponent.

2020-07-30更新:随着 dagger-hilt 2.28.2-alpha 的发布, ApplicationComponent将重命名为SingletonComponent。

翻译自: https://medium.com/swlh/di-for-android-using-hilt-cb359629fdf3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值