Hilt 扩展 | MAD Skills

Hilt 扩展 | MAD Skills

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

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

案例: WorkManager 扩展

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

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

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

@HiltWorker
public 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 等库生成代码。

Hilt 扩展注解

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

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

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

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

测试案例如下:

@HiltAndroidTest
class 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 中执行成员注入的基类。

镜像绑定

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

总结

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

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

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

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

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

欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io WebMaster | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.