Hilt核心概念:@HiltAndroidApp、@AndroidEntryPoint、@Inject
说起 Hilt 的初始化延迟问题,其实很多坑都出在三个注解上。
我自己刚学 Hilt 的时候,就因为在 @AndroidEntryPoint 上的一个疏忽,debug 了整整一个下午。
所以这一章,咱们把它们一次性讲透彻。
@HiltAndroidApp —— 应用的“总开关”
这个注解必须放在你的 Application 类上。
它负责生成 Hilt 的全局组件,包括组件树和依赖容器。
说白了,没有它整个 Hilt 都跑不起来。
关键点
- 每个应用只能有一个
@HiltAndroidApp。
如果你在 library 模块里加了它,编译直接报错。 - 如果你的项目是多 module 结构,只有主 app module 的 Application 需要标注。
依赖 module 不需要加这个。 - Hilt 初始化默认是懒加载(lazy),但
@HiltAndroidApp会在 Application.onCreate() 之前触发第一波初始化。
这恰好是延迟问题的第一个诱因——如果你在这里做了耗时操作……嗯,后面我们会重点讲。
@HiltAndroidApp
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// Hilt 初始化完成,可以安全注入
}
}
@AndroidEntryPoint —— 组件的“注入开关”
它用在你想要注入依赖的 Android 组件上:Activity、Fragment、Service、BroadcastReceiver,甚至 View。
我习惯把它理解成“为这个组件开通了 Hilt 的专线”。
为什么需要它?
因为 Hilt 需要知道哪些组件要参与注入过程。
没有它,你写在字段上的 @Inject 根本不会生效。
我曾经踩过的坑:
在 Fragment 上加了 @AndroidEntryPoint,但忘了给它的宿主 Activity 也加上。
结果注入一直失败,报错信息还特别隐晦。
后来发现:如果 Fragment 依赖 Activity 级别的组件(比如 Navigation),Activity 必须也要标注。
不过如果你的 Fragment 只用全局组件,那 Activity 不加也行。这取决于你的依赖范围。
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
MyRepository repository; // 字段注入
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// repository 可用
}
}
易混淆点:
Fragment 的注入时机是 onAttach 之后,所以不要在构造器里就使用注入实例。
另外,如果你用了 Jetpack Compose,记得给 Activity/Fragment 加 @AndroidEntryPoint,
然后在 Composable 函数里用 hiltViewModel() 获取 ViewModel 实例。
@Inject —— 注入的“执行者”
这是最直接的注解,它有两个身份:
- 构造器注入:告诉 Hilt “这个类的实例可以这样创建”,Hilt 会自动调用该构造器,并传入所需的依赖。
- 字段注入:告诉 Hilt “我需要这个依赖,把它塞到这个字段里”。
- 方法注入(不常用):Hilt 在初始化完成后会自动调用该方法,并把参数传进去。
我一般推荐优先用构造器注入,这样字段可以写成 final,更安全。
字段注入虽然方便,但容易忘记对象是否已经初始化。
// 构造器注入
public class MyRepository {
private final ApiService apiService;
@Inject
public MyRepository(ApiService apiService) {
this.apiService = apiService;
}
}
// 字段注入(只能在 @AndroidEntryPoint 的组件中使用)
@Inject
ApiService apiService;
注意:
字段注入的类必须处于 Hilt 管理的组件树内。
换句话说,能在 @AndroidEntryPoint 的 Activity 里直接字段注入,
但不能在一个普通 Java 类里写 @Inject 字段——除非那个类也是由 Hilt 创建的。
这三者是如何协作的?
我用一张图来说明它们的层级关系:
三个注解的作用对比
| 注解 | 标注位置 | 主要作用 | 注意事项 |
|---|---|---|---|
@HiltAndroidApp |
Application 类 | 生成全局组件,触发 Hilt 初始化 | 只能用于 app module;library 不能加 |
@AndroidEntryPoint |
Activity / Fragment / Service / View / BroadcastReceiver | 启用该组件的字段和方法注入 | 若组件有父级依赖(如 Fragment 依赖 Activity),父级也必须标注 |
@Inject |
构造器 / 字段 / 方法 | 声明依赖的来源(构造器)或需求(字段) | 字段注入只能在 @AndroidEntryPoint 组件中使用;构造器注入需要所有参数可提供 |
避坑提醒:
我曾经在一个多 module 项目里,为了简化依赖管理,在 Application 类中加了一个 @Inject 字段。
结果 Hilt 初始化时这段代码还没准备好,直接抛 NullPointerException。
后来才明白:@Inject 字段必须在组件完成注入后才能访问,不要想当然地放在 onCreate() 最开头用。
——这也是初始化延迟问题的另一个源头,下一章我们会实战复现。
小结
三个注解分工明确:
- @HiltAndroidApp 搭好舞台
- @AndroidEntryPoint 点亮聚光灯
- @Inject 演员登台
记住这个顺序,再遇到注入失败或延迟问题,你就知道该往哪查了。