0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入探讨 Hilt的工作原理

谷歌开发者 来源:Android开发者 作者:Android 2021-10-19 15:11 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

所涉主题

  • 多种 Hilt 注解协同工作并生成代码的方式。
  • 当 Hilt 配合 Gradle 使用,Hilt Gradle 插件如何在幕后工作以改善整体体验。

多种 Hilt 注解协同工作并生成代码的方式

Hilt 使用注解处理器生成代码。对注解的处理发生在编译器将源文件转换为 Java 字节码期间。顾名思义,注解处理器作用于源文件中的注解。注解处理器通常会检查注解,并根据注解类型来执行不同的任务,例如代码检查或生成新文件。 在 Hilt 中,三个最重要的注解就是:@AndroidEntryPoint@InstallIn 以及 @HiltAndroidApp
  • @AndroidEntryPoint
    https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html

  • @InstallIn
    https://dagger.dev/api/latest/dagger/hilt/InstallIn.html

  • @HiltAndroidApp
    https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html

@AndroidEntryPoint

AndroidEntryPoint 在您的 Android 类中启用字段注入,例如 Activity、Fragment、View 以及 Service。

  • AndroidEntryPoint
    https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html

如下示例所示,通过向 PlayActivity 添加 AndroidEntryPoint 注解,即可轻松将 MusicPlayer 注入到我们的 Activity 中。
@AndroidEntryPointclass PlayActivity : AppCompatActivity() {
  @Inject lateinit var player: MusicPlayer
  // ...}

如果您使用 Gradle,您可能熟悉上文所述的简化语法。但这并不是真实的语法,而是 Hilt Gradle 插件为您提供的语法糖。接下来我们将探讨更多关于 Gradle 插件的内容,在此之前,我们先来看看这个例子在没有语法糖的情况下应该是什么样子的。
@AndroidEntryPoint(AppCompatActivity::class)class PlayActivity : Hilt_PlayActivity() {
  @Inject lateinit var player: MusicPlayer
  // ...}

现在,我们看到原始基类 AppCompatActivityAndroidEntryPoint 注解的真实入参。PlayActivity 实际上继承了生成的类 Hilt_PlayActivity,该类由 Hilt 注解处理器生成,并包含所有执行注入操作需要的逻辑。针对上述内容生成的基类,其代码简化示例如下:

@Generated("dagger.hilt.AndroidEntryPointProcessor")class Hilt_PlayActivity : AppCompatActivity {
  override fun onCreate() {    inject()    super.onCreate()  }
  private fun inject() {    EntryPoints.get(this, PlayActivity_Injector::class).inject(this as PlayActivity);  }}
  • AppCompatActivity
    https://developer.android.google.cn/reference/androidx/appcompat/app/AppCompatActivity

在示例中,生成的类继承自 AppCompatActivity。然而,通常情况下生成的类会继承传入 AndroidEntryPoint 注解的类。这使得注入操作可以在任何您需要的基类中执行。

生成类的主要目的是处理注入操作。为了避免字段在注入之前被意外访问,有必要尽可能早地执行注入操作。因此,对于 Activity,注入操作在 onCreate 中被执行。

在 inject 方法中,我们首先需要一个注入器的实例——PlayActivity_Injector。在 Hilt 中,对于 Activity,注入器是一个入口点,我们可以使用 EntryPoints 工具类获得一个注入器的实例。

您可能想到了,PlayActivity_Injector 也是由 Hilt 注解处理器生成的。格式如下:

@Generated("dagger.hilt.AndroidEntryPointProcessor")@EntryPoint@InstallIn(ActivityComponent::class)interface PlayActivity_Injector {
  fun inject(activity: PlayActivity)
}

生成的注入器是一个被装载到 ActivityComponent 的 Hilt 入口点。它仅包含一个让我们注入 PlayActivity 实例的方法。如果您曾在 Android 应用中使用过 Dagger (不通过 Hilt),您可能会熟悉这些直接在组件上编写的注入方法。

@InstallIn

InstallIn 用于表明模块或者入口点应该被装载到哪个组件中。在如下示例中,我们将 MusicDataBaseModule 装载到 SingletonComponent 中:
@Module@InstallIn(SingletonComponent::class)object MusicDatabaseModule {  // ...}

  • InstallIn
    https://dagger.dev/api/latest/dagger/hilt/InstallIn.html

  • SingletonComponent
    https://dagger.dev/api/latest/dagger/hilt/components/SingletonComponent.html

通过 InstallIn,应用中任何传递依赖项内都可以提供模块和入口点。然而,部分情况下我们需要收集所有由InstallIn 注解提供的内容以获取每个组件的完整模块和入口点。 Hilt 在特定的包下生成了元数据注解,以便更轻松地收集和发现这些由 InstallIn 注解所提供的内容。生成的注解格式如下:
package hilt_metadata
@Generated("dagger.hilt.InstallInProcessor")@Metadata(my.database.MusicDatabaseModule::class)classMusicDatabaseModule_Metadata{}

通过将元数据放进特定的包下,Hilt 注解处理器可以轻松地在您应用中所有的传递依赖项中找到生成的元数据。至此,我们可以使用元数据注解中所包含的信息来找到由 InstallIn 注解所提供内容的自身引用。在本示例中指的是 MusicDatabaseModule

HiltAndroidApp

最后,HiltAndroidApp 注解可以让您的 Android Application 类启用注入。此处,您可以将其视为与 AndroidEntryPoint 注解完全相同。第一步,开发者仅需在 Application 类上添加 @HiltAndroidApp 注解。
@HiltAndroidAppclass MusicApp : Application {
  @Inject lateinit var store: MusicStore
}

  • HiltAndroidApp
    https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html

然而,HiltAndroidApp 还有另外一个重要的作用——生成 Dagger 组件。

当 Hilt 注解处理器遇到 @HiltAndroidApp 注解时,会在包装类中生成一些列组件,该包装类与 Application 类同名,前缀为 HiltComponents_。如果您之前使用过 Dagger,这些组件就是添加了 @Component@Subcomponent 注解的类,而在 Dagger 中通常需要您手动编写。

a1b92714-30aa-11ec-82a8-dac502259ad0.png

为了生成这些组件,Hilt 在上述元数据包中查找所有被添加 @InstallIn 注解的类。添加了 @InstallIn 注解的模块被放置在相应组件声明的模块列表中。添加了 @InstallIn 注解的入口点被放置在声明相应组件的父类型的位置。

从这里开始,Dagger 处理器接管并根据 @Component@Subcomponent 注解生成组件的具体实现。如果您曾使用过 Dagger (不通过 Hilt),那么大概率您已经直接处理了这些类。但是,Hilt 对开发者隐藏了这种复杂操作。

这是一篇关于 Hilt 的文章,我们就不详细介绍 Dagger 生成的代码了。如果您有兴趣,详情请查阅:

  • Ron Shapiro 和 David Baker 的演讲:

    https://www.youtube.com/watch?v=wCvXe2LsN5o

  • Dagger codegen 101 的备忘单:

    https://medium.com/androiddevelopers/dagger-code-generation-cheat-sheets-6b4fa2da4e7a

Hilt Gradle 插件

现在您已经了解了 Hilt 中代码生成的工作原理,接下来让我们看看 Hilt Gradle 插件。Hilt Gradle 插件执行很多有用的任务,包括字节码改写和类路径聚合。
  • Hilt Gradle 插件

    https://dagger.dev/hilt/gradle-setup#hilt-gradle-plugin

字节码改写

顾名思义,字节码改写就是改写字节码的过程。与注解处理只能生成新代码不同,字节码改写可以修改现有代码。如果谨慎使用,这将是非常强大的功能。

为了说明我们为何在 Hilt 中使用字节码改写,让我们回到 @AndroidEntryPoint

@AndroidEntryPoint(AppCompatActivity::class)class PlayActivity : Hilt_PlayActivity {
  override fun onCreate(…) {    val welcome = findViewById(R.id.welcome)  }
}
虽然继承 Hilt_PlayActivity 基类在实践中有效,但它可能会导致 IDE 报错。由于生成的类在您成功编译代码后才存在,因此您经常会在 IDE 中看到红色波浪线。此外,您将无法享有诸如方法重载这种自动补全的能力,并且您将无法访问基类中的方法。 失去这些功能不仅会降低您的编码速度,而且这些红色波浪线也会极大程度地分散您的注意力。 Hilt Android 插件通过在您的类上添加 AndroidEntryPoint 注解来启动字节码改写。启用 Hilt Android 插件后,您只需要在类上添加 @AndroidEntryPoint 注解,同时您可以使其继承普通的基类。
@AndroidEntryPointclass PlayActivity : AppCompatActivity { // <-- 无需引用生成的基类
  override fun onCreate() {    val welcome = findViewById(R.id.welcome)  }}

由于此语法无需引用生成的基类,所以不会引起 IDE 报错。在字节码改写期间,Hilt Gradle 插件会将您的基类替换为 Hilt_PlayActivity。由于此过程直接操作字节码,对开发者是不可见的。

然而,字节码改写仍有一些缺点:

  • 该插件必须修改底层字节码,而不是源代码,这容易出错。

  • 因为在改写操作时字节码已经被编译,所以问题通常出现在运行时而不是编译时。

  • 改写操作使调试变得复杂,因为当出现问题时,源文件可能并不代表当前正在执行的字节码。


由于这些原因,Hilt 尝试尽可能减少依赖字节码改写。

类路径聚合

最后,让我们看看 Hilt Gradle 插件的另一个有用功能: 类路径聚合。要了解什么是类路径聚合,以及为什么需要它,让我们看另一个示例。

在本示例中 :app 依赖一个独立的 Gradle 模块 :database:app:database 都提供了被 InstallIn 注解的模块。

如您所见,Hilt 会在特定的 hilt_metadata 包下生成元数据,在生成组件时,会用它们查找所有被添加 @InstallIn 注解的模块。

不使用类路径聚合的处理对于单层依赖关系仍然可以正常工作,现在让我们看看当添加另一个 Gradle 模块 :cache 作为 :database 的依赖项时会发生什么。

:cache 被编译时,虽然它会生成元数据,但在编译 :app 时该元数据无法使用,因为它是一个传递依赖项。因此,Hilt 无法知晓 CacheModule,它会意外地从生成的组件中排除。 当然,您可以使用 api 而不是 implementation 声明 :cache 的依赖关系,从而在技术层面解决这个问题,但不推荐这样做。使用 api 不仅会让增量构建变得更糟糕,还把维护工作也变成一场噩梦。 这就是 Hilt Gradle 插件发挥作用的地方。

即使使用 implementation,Hilt Gradle 插件也可以自动从 :app 的传递依赖项中聚合所有的类。

此外,与直接使用 api 相比,Hilt Gradle 插件还具有许多优点。

首先,对比在整个应用中手动使用 api 依赖关系,类路径聚合更不容易出错并且不需要维护。您可以像往常一样简单地使用 implementation,其余的将由 Hilt Gradle 插件处理。

其次,Hilt Gradle 插件仅在应用级别聚合类,因此与使用 api 不同,项目中库的编译不受影响。

最后,类路径聚合为您的依赖项提供了更好的封装,因为不可能在源文件中意外引用这些类,并且它们不会出现在代码补全提示中。

总结

本文我们揭示了各种 Hilt 注解协同工作以生成代码的方式。 我们还关注了 Hilt Gradle 插件,并了解它是如何在幕后使用字节码改写和类路径聚合,让 Hilt 的使用变得更安全、更轻松。
编辑:jq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    20344

    浏览量

    255374
  • API
    API
    +关注

    关注

    2

    文章

    2484

    浏览量

    67072
  • 代码
    +关注

    关注

    30

    文章

    4977

    浏览量

    74431

原文标题:Hilt 工作原理 | MAD Skills

文章出处:【微信号:Google_Developers,微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    ADF4108 PLL频率合成器:特性、应用与工作原理解析

    的特性和广泛的应用范围受到工程师们的青睐。今天,我们就来深入探讨ADF4108的各项特性、应用场景以及工作原理,希望能为大家在相关设计中提供一些有价值的参考。 文件下载: ADF4108.pdf 一、ADF4108的特性亮点 1. 高带宽与宽电源范围 ADF4108具有8
    的头像 发表于 04-20 11:20 209次阅读

    深入解析AD8367S可变增益放大器:特性、应用与设计要点

    都能稳定工作。今天,我们将深入探讨Analog Devices公司的AD8367S可变增益放大器,详细介绍其特性、工作原理、应用场景以及设计过程中的注意事项。 文件下载: AD8367S.pdf 一、产品概述 AD8367S是一
    的头像 发表于 01-14 16:45 712次阅读

    锂电池制造关键:深入探讨辊压工艺的核心参数

    本文深入探讨了辊压工艺中设备变量、工艺参数与材料属性之间的量化关系,特别是针对工艺放大和设备转移时的参数设定提供了理论依据。尽管原文主要基于颗粒力学模型,其核心原理对于锂电行业中的干法造粒及极片辊压
    的头像 发表于 01-13 18:03 585次阅读
    锂电池制造关键:<b class='flag-5'>深入探讨</b>辊压工艺的核心参数

    深入探讨PCB布局布线的专业设计要点与常见挑战

    本文深入探讨PCB布局布线的专业设计要点与常见挑战,并介绍上海创馨科技如何凭借资深团队与丰富经验,为客户提供从精密布局、优化布线到生产制造的一站式高可靠性PCB解决方案。
    的头像 发表于 01-04 15:29 439次阅读

    雷击浪涌发生器的原理、基本构造与工作流程以及作用

    雷击浪涌发生器,作为防雷测试与研究领域的重要工具,其设计与应用充分体现了对自然雷电现象的模拟与利用。本文将深入探讨雷击浪涌发生器的工作原理、核心作用及其在现代防雷技术发展中的重要地位。
    的头像 发表于 12-15 09:18 791次阅读
    雷击浪涌发生器的原理、基本构造与<b class='flag-5'>工作</b>流程以及作用

    深入解析 onsemi NVHL060N065SC1 N 沟道 MOSFET

    在电子工程师的日常设计工作中,MOSFET 是不可或缺的重要元件。今天,我们就来深入探讨 onsemi 推出的 NVHL060N065SC1 N 沟道 MOSFET,看看它有哪些独特的性能和应用特点。
    的头像 发表于 12-01 09:28 824次阅读
    <b class='flag-5'>深入</b>解析 onsemi NVHL060N065SC1 N 沟道 MOSFET

    三相电源防雷器的作用、工作原理及实际应用中的考虑因素

    ,作为电力防护体系中的关键组件,承担着保护三相交流电源系统免受雷电侵袭的重任。本文将深入探讨三相电源防雷器的作用、工作原理及其在现代防雷技术中的应用价值。
    的头像 发表于 11-26 13:42 752次阅读

    信号发生器的构成和工作原理

    信号发生器作为现代电子测试与测量领域中的关键设备,其精密的构造和巧妙的工作原理使之能够产生各种所需的电信号。本文将深入探讨信号发生器的构成及其工作原理,以帮助读者更好地理解这一重要电子设备
    的头像 发表于 11-13 11:45 588次阅读
    信号发生器的构成和<b class='flag-5'>工作原理</b>

    兴感半导体角度磁编码器的工作原理和技术优势

    在现代工业控制及自动化、机器人技术和精密测量领域,角度磁编码器作为一种关键传感器,其核心性能直接影响着系统的精度与可靠性。随着技术的不断进步,角度磁编码器在多个领域的应用越来越广泛,本文将深入探讨工作原理、技术优势以及在工业控制及自动化的应用。
    的头像 发表于 10-30 11:22 3171次阅读
    兴感半导体角度磁编码器的<b class='flag-5'>工作原理</b>和技术优势

    为数据中心保驾护航:UPS电源的工作原理与选型指南

    (不间断电源)系统正是保障业务连续性的关键防线。本文将深入探讨UPS电源的工作原理、技术特点、应用场景及选型要点,帮助数据中心管理者做出更明智的决策。一、UPS电源工作原理:不止是备用
    的头像 发表于 09-05 11:34 1664次阅读
    为数据中心保驾护航:UPS电源的<b class='flag-5'>工作原理</b>与选型指南

    一文了解Arm神经超级采样 (Arm Neural Super Sampling, Arm NSS) 深入探索架构、训练和推理

    本文将从训练、网络架构到后处理和推理等方面,深入探讨 Arm 神经超级采样 (Arm Neural Super Sampling, Arm NSS) 的工作原理,希望为机器学习 (ML) 工程师和移动端图形开发者来详细解释 Arm NSS 的运行机制,及其如何在移动端硬件
    的头像 发表于 08-14 16:11 3297次阅读

    工业化超声波清洗设备的工作原理与实际应用

    行业对高品质清洗技术的强烈需求。用户在选择设备时,常常关注“工业超声波清洗设备工作原理”、“工业超声波清洗优势”及“实际应用场景”这类长尾关键词。本文将深入探讨
    的头像 发表于 08-04 17:07 1461次阅读
    工业化超声波清洗设备的<b class='flag-5'>工作原理</b>与实际应用

    频率晶体的工作原理与应用解析:从压电效应到高精度时钟设计

    本文将系统解析频率晶体的工作原理、制造过程与实际应用,并结合行业发展,探讨其未来演进趋势。
    的头像 发表于 07-24 10:00 1329次阅读
    频率晶体的<b class='flag-5'>工作原理</b>与应用解析:从压电效应到高精度时钟设计

    三相稳压器的工作原理工作流程及应用场景

    三相稳压器作为一种稳定电压的关键设备,在工业、商业以及家庭场景中都有着极为广泛的应用。它能够精准调节电网电压的波动,让设备始终在稳定的电压环境中运行,有效避免因电压起伏而对设备造成损害。接下来,本文将深入剖析三相稳压器的工作原理,并着重
    的头像 发表于 07-21 10:10 1676次阅读

    超声波清洗机的工作原理和清洗技术特点是什么?

    超声波清洗机的工作原理和清洗技术特点超声波清洗机是一种高效的清洗设备,广泛应用于各个工业领域。本文将深入探讨超声波清洗机的工作原理以及其清洗技术特点,以帮助读者更好地了解这一先进的清洗技术。目录1.
    的头像 发表于 06-27 15:54 1757次阅读
    超声波清洗机的<b class='flag-5'>工作原理</b>和清洗技术特点是什么?