查看原文
其他

Hilt 扩展 | MAD Skills

Android Android 开发者 2021-11-05
本文是 MAD Skills 系列中有关 Hilt 的第四篇文章!在本文中,我们将探讨如何编写自定义的 Hilt 扩展。如果您需了解本系列前三篇文章,请查阅: 

  • Hilt
    https://dagger.dev/hilt/


如果您更喜欢通过视频了解此内容,可以在此处查看:

△ Hilt 扩展——MAD Skills

案例: WorkManager 扩展



Hilt 扩展是一个生成代码的库,常通过注解处理器实现。生成的代码作为构成 Hilt 依赖项注入关系图的模块或入口点。


Jetpack 中 WorkManager 的集成库就是一个扩展的例子。WorkManager 扩展帮助我们减少向 worker 提供依赖项时所需的模板代码及配置。该库由两部分组成,分别为 androidx.hilt:hilt-work 和 androidx.hilt:hilt-compiler。第一部分包含 HiltWorker 注解以及一些运行时的辅助类,第二部分是一个注解处理器,根据第一部分中注解提供的信息生成模块。


  • Jetpack 中 WorkManager
    https://developer.android.google.cn/training/dependency-injection/hilt-jetpack#workmanager


扩展的使用非常简单,仅需在您的 worker 上添加 @HiltWorker 注解:

@HiltWorkerpublic class ExampleWorker extends Worker { // ...}

扩展编译器会生成一个添加了 @Module 注解的类:

@Generated("androidx.hilt.AndroidXHiltProcessor")@Module@InstallIn(SingletonComponent.class)@OriginatingElement( topLevelClass = ExampleWorker.class)public interface ExampleWorker_HiltModule { @Binds @IntoMap @StringKey("my.app.ExmapleWorker") WorkerAssistedFactory<? extends ListenableWorker> bind( ExampleWorker_AssistedFactory factory);}

该模块为 worker 定义了一个可以访问 HiltWorkerFactory 的绑定。然后,配置 WorkerManager 使用该 factory,从而使 worker 的依赖项注入可用。



Hilt 聚合



启用扩展的一个关键机制是 Hilt 能够从类路径中发现模块和入口点。这被称为聚合,因为模块和入口点被聚合到带有 @HiltAndroidApp 注解的 Application 中。

由于 Hilt 具有聚合能力,任何通过添加 @InstallIn 注解生成 @Module 及 @EntryPoint 的工具都可以被 Hilt 发现,并在编译期成为 Hilt DI 图中的一部分。这使得扩展可以轻松地以插件形式集成到 Hilt,无需开发者处理任何额外工作。



注解处理器



生成代码的常规途径是使用注解处理器。源文件转换为 class 文件之前,注解处理器会在编译器中运行。当资源带有处理器所声明的已支持的注解时,处理器会进行处理。处理器可以生成进一步需要被处理的方法,因此编译器会不断循环运行注解处理器,直到没有新的内容产生。一旦所有的环节都完成,编译器才会将源文件转换为 class 文件。

△ 注解处理示意图

由于循环机制,处理器可以相互作用。这非常重要,因为这使得 Hilt 的注解处理器可以处理由其他处理器生成的 @Module 或 @EntryPoint 类。这也意味着您的扩展也可以建立在其他人编写的扩展之上!


WorkManager extension processor 根据带有 @HiltWorker 注解的类生成代码,同时验证注解用法并使用 JavaPoet 等库生成代码。


  • WorkManager extension processor
    https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/

  • JavaPoet
    https://github.com/square/javapoet



Hilt 扩展注解



Hilt API 中有两个重要的注解: @GeneratesRootInput 和 @OriginatingElement。扩展应该使用这些注解才能与 Hilt 正确集成。


扩展应该使用 @GeneratesRootInput 来启用代码生成的注解。这让 Hilt 注解处理器知道它应该在生成组件之前完成扩展注解处理器的工作。例如,@HiltWorker 注解本身是被 @GeneratesRootInput 注解修饰的:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.CLASS)@GeneratesRootInputpublic @interface HiltWorker {}


所生成的带有 @Module、@EntryPoint 以及 @InstallIn 注解的类都需要添加 @OriginatingElement 注解,该注解的输入参数是触发模块或入口点生成的顶层类。这就是 Hilt 判断生成的模块和入口点是否在本地测试的依据。例如,在 Hilt 测试中定义了一个添加 @HiltWorker 注解的内部类,模块的初始元素就是测试值。

测试案例如下:
@HiltAndroidTestclass SampleTest { @HiltWorker class TestWorker extends Worker { // … }}


生成的模块包含 @OriginatingElement 注解:
@Module@InstallIn(SingletonComponent.class)@OriginatingElement( topLevelClass = SampleTest.class)public interface SampleTest_TestWorker__HiltModule { // …}



心得



Hilt 扩展支持多种可能性,以下是创建扩展的一些心得:


项目中的通用模式


如果您的项目中有创建模块或入口点的通用模式,那么它们很大概率可以通过使用 Hilt 扩展实现自动化。举个例子,如果每一个实现特定接口的类都必须创建一个具有多绑定的模块,那么可以创建一个扩展,只需在实现类上添加注解即可生成多重绑定模块。


支持非标准成员注入


对于那些 Framework 中已经支持带有实例化能力的成员注入类型,我们需要创建一个 @EntryPoint。如果有多种类型需要被成员注入,那么自动创建入口点的扩展会很有用。例如,需要通过 ServiceLoader 发现服务实现的库负责实例化发现的服务。为了将依赖项注入到服务实现中,必须创建一个 @EntryPoint。通过使用 Hilt 扩展,可以使用在实现类上添加注解完成自动生成入口点。扩展可以进一步生成代码以使用入口点,例如由服务实现扩展的基类。这类似于 @AndroidEntryPoint 为 Activity 创建 @EntryPoint,并创建使用生成的入口点在 Activity 中执行成员注入的基类。


  • ServiceLoader
    https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html


镜像绑定


有时需要使用不同的限定符来镜像或重新声明绑定。当存在自定义组件时,这可能更常见。为了避免丢失重新声明的绑定,可以创建 Hilt 扩展以自动生成其他镜像绑定的模块。例如,考虑包含不同依赖项实现的应用中 "付费" 和 "免费" 订阅的情况。然后,每一层都有两个不同的自定义组件,这样您就可以确定依赖关系的作用域。当添加一个通用的未限定作用域的绑定时,定义绑定的模块可以在其 @InstallIn 中包含两个组件,也可以加载在父组件中,通常是单例组件。但是当绑定被限定作用域时,模块必须被复制,因为需要不同的限定符。实现一个扩展就可以生成两个模块,可以避免样板代码并确保不会遗漏通用绑定。



总结



Hilt 的扩展可以进一步增强代码库中的依赖项注入能力,因为它们可以实现与 Hilt 尚不支持的其他库集成。总而言之,扩展通常由两部分组成,包含扩展注解的运行时部分,以及生成 @Module 或 @EntryPoint 的代码生成器 (通常是注解处理器)。扩展的运行时部分可能有额外的辅助类,这些辅助类使用声明在生成的模块或入口点中绑定。代码生成器还可能生成与扩展相关的附加代码,它们无需专门生成模块和入口点。

扩展必须使用两个注解才能与 Hilt 正确交互:

  • @GeneratesRootInput 添加在扩展注解上。

  • @OriginatingElement 由扩展添加在生成的模块或入口点上。


最后,您可以查看 hilt-install-binding 项目,这是一个简单扩展的示例,它展示了本文中提到的概念:

https://github.com/danysantiago/hilt-install-binding


以上便是 MAD Skills 系列关于 Hilt 的全部内容,如需观看视频全集,请移步到 Hilt - MAD Skills 播放列表。感谢阅读本文!


  • Hilt - MAD Skills 播放列表
    https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_9Qo-RBRYhVmME1iR4oeTK


欢迎您通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!




推荐阅读

如页面未加载,请刷新重试

 点击屏末 | 阅读原文 | 即刻了解 Hilt 和 Jetpack 集成




视频 小程序 ,轻点两下取消赞 在看 ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存