Hilt 简介与编译时处理概述

说起依赖注入(DI),大家可能都听过 Dagger。但 Dagger 的学习曲线确实有点陡,配置起来也麻烦。Hilt 就是 Google 在 Dagger 基础上封装的「开箱即用」方案。说白了,它帮你省掉了手动写 Component、Module 绑定这些模板代码 —— 你只要加几个注解,框架就帮你自动生成 Dagger 组件。

我最早接触 Hilt 是在一个中型电商项目里,当时团队想统一 DI 方案,又不想让新手被 Dagger 吓跑。用了 Hilt 之后,新人上手快了不少,但编译报错还是让人头疼。嗯,这就是咱们这门课的核心 —— 搞懂 Hilt 在编译时干了什么,以及怎么诊断那些「额外请求」导致的编译问题。

Hilt 到底在编译时做了什么?

一句话概括:Hilt 是一个基于 Dagger 的注解处理器 + 代码生成器。它在编译期间扫描你写的 @HiltAndroidApp、@AndroidEntryPoint、@Module、@Provides 等注解,然后生成对应的 Dagger 组件和工厂类。

举个例子:你写了一个 @AndroidEntryPoint 注解在 Activity 上,Hilt 就会生成一个 Hilt_YourActivity 基类,并帮你创建 ApplicationComponent、ActivityComponent 等组件树。

核心流程:
  • 注解扫描:收集所有 Hilt 相关注解(入口、模块、绑定)。
  • 验证:检查配置有没有遗漏,比如 @HiltAndroidApp 是否只在 Application 上。
  • 生成 Dagger 组件:框架帮你定义 Component、Subcomponent。
  • 生成工厂类:为每个 @Provides 或 @Inject 构造函数生成 Factory。
  • 触发 Dagger 编译:Dagger 接着生成最终的对象图代码。

这里有个关键点:Hilt 本质上是一个「编译器插件」,它不会在运行时做反射注入。所有依赖都在编译期确定好,运行时就是普通对象创建。这也是为什么 Hilt 比某些运行时 DI 框架更高效 —— 但代价就是编译错误更难排查。

为什么需要「额外请求诊断」?

你在项目中肯定遇到过类似场景:明明 @Provides 写了,也加了 @InstallIn,编译却报「找不到依赖」。更诡异的是,有时候加一个 @Singleton 就好了,去掉又坏。这背后就是 Hilt 的「额外请求」机制搞的鬼。

我管它叫「额外请求」,是指 Hilt 在生成代码时,会隐式地向 Dagger 请求一些你没有直接写的绑定。比如:

  • 入口点扫描:@AndroidEntryPoint 标记的 Activity/Fragment 中,Hilt 会请求所有被 @Inject 的成员变量。
  • 模块的依赖传递:一个 Module 里 @Provides 的方法参数,会被视为额外请求。
  • 限定符冲突:两个相同类型但不同 @Qualifier 的绑定,Hilt 会请求一个明确的限定符。
  • 组件作用域:@Singleton、@ActivityScoped 等作用域会要求对应的组件必须存在。
一个小技巧:当编译报错信息里出现 @Binds 或 @Provides 但你不记得自己写过时,八成就是 Hilt 的额外请求报错。这时候先看错误栈里的「requested by」字段。

Hilt 与 Dagger 的协作关系

它们的关系可以用一句话形容:Hilt 是前台老板,Dagger 是后台干活的。Hilt 处理完注解后,会把「半成品」交给 Dagger 继续处理生成最终代码。

阶段由谁负责产出
1. 注解处理Hilt 注解处理器Hilt_ 基类、组件接口、模块代理
2. 组件生成Dagger 注解处理器DaggerApplicationComponent 等实现类
3. 工厂合成Dagger 自动各 Provide 方法的工厂类
4. 编译检查Dagger 编译期依赖图合法性检查,报错或生成成功

看到了吧?编译时的报错多半来自 Dagger 那一步,但根因可能是 Hilt 的配置遗漏。所以诊断「额外请求」时,我们要能区分:是 Hilt 没生成正确的组件,还是 Dagger 在组件里找不到依赖。

知识体系总览

Hilt编译时处理与诊断知识结构 源码注解 @HiltAndroidApp / @EntryPoint Hilt注解处理 扫描 → 验证 → 生成编码 Dagger编译 组件实现 → 工厂生成 编译成功/失败 诊断额外请求的切入点 额外请求类型 入口点请求 模块参数请求 典型报错 找不到依赖 绑定冲突 解决手段 检查@InstallIn 添加限定符 常见额外请求包括:@Binds、@Provides 报错信息中的「requested by」 学会读Dagger错误栈 记住:80%的额外请求问题都能通过「@Inject 构造函数 vs @Provides 模块」的对比定位

几类典型的额外请求场景

我根据项目踩坑经验,总结了三类最常见的「额外请求」陷阱:

  1. 跨模块依赖:你在 feature 模块里用 @Provides 提供了一个对象,但忘了 @InstallIn(SingletonComponent::class)。Hilt 会额外请求这个绑定,但因为组件不匹配,报错。
  2. 限定符隐式依赖:两个 @Module 里都用 @Named("api") 提供了相同类型,Hilt 必须额外请求一个 @Qualifier 来区分。如果你没写,它就报冲突。
  3. 入口点注解缺失:你在 Activity 里 @Inject 了一个 Presenter,但 Activity 没加 @AndroidEntryPoint。Hilt 会额外请求注入点,但找不到对应入口,编译失败。
我曾经踩过一次坑:在一个多模块项目里,一个公共库的 @Provides 没加 @Singleton,结果 Activity 和 Fragment 各自创建了不同实例,导致数据不一致。查了半天才意识到是 Hilt 额外请求了不同组件的作用域绑定。从那以后,我只要看到「duplicate bindings」就优先检查作用域注解。

为什么这门课要用「诊断」的视角?

你想想看,市面上讲 Hilt 的教程大多教你「怎么用」。但真到排错时,你打开编译输出,满屏的 Dagger 错误信息 —— 那就是天书。咱们这门课反过来:先搞懂 Hilt 在编译时做了什么「额外请求」,然后学会看诊断信息。

说白了,你不需要记住所有注解用法,只需要学会一个通用排查流程:

  • 看错误栈里「requested by」字段 → 找到谁发起了请求
  • 检查发起者的 @InstallIn 和组件作用域 → 确认组件匹配
  • 检查绑定是否有冲突(重复、限定符缺失) → 修正代码

这个流程我用了两年,几乎能解决 95% 的编译时 DI 问题。后边的章节会一步步展开每个环节的具体技巧。

一个小习惯:我每次新建 Hilt 模块时,都会先写一个空的 @Module + @InstallIn,然后逐步往里加 @Provides。这样如果编译出错了,我能立刻定位是哪个新加的绑定出了问题。别一次性写一堆注解再编译,那样排查太痛苦。