Hilt入门:什么是Hilt?为什么需要依赖注入?
大家好,我是你们的老朋友,一个在Android坑里摸爬滚打多年的工程师。今天咱们来聊聊Hilt。
先别急着写代码,我问你个问题:你以前写Android项目时,有没有遇到过这种场景——一个类里new了一大堆对象,然后想换一个实现,得改好几个文件?我当年就被这问题折腾得够呛。说白了,就是依赖关系太混乱了。
依赖注入:到底在解决什么问题?
先说一个简单的例子。假设你有个UserRepository,它需要ApiService才能干活。
class UserRepository {
private val api = ApiService() // 直接在构造函数里new出来
fun getUser() = api.fetchUser()
}
这样写有什么问题?你要是想把ApiService换成MockApiService做测试,就得改UserRepository的源码。一个项目里类似的地方多了,改起来真的会崩溃。
依赖注入(Dependency Injection,简称DI)就是来解决这个问题的。它的核心思想很简单:对象自己不负责创建依赖,而是由外部传入。
class UserRepository(private val api: ApiService) {
fun getUser() = api.fetchUser()
}
你看看,现在UserRepository不再管ApiService怎么来的,它只管用。谁负责创建?交给一个叫“依赖注入容器”的东西。这就是DI的本质。
依赖注入的好处:
- 降低类与类之间的耦合度
- 方便单元测试(可以轻松替换成 mock 对象)
- 代码更容易维护和扩展
- 生命周期管理更清晰
我个人习惯把DI比作“工厂+仓库”。你把需要的零件(依赖)告诉工厂,工厂从仓库里拿出来给你。你不需要知道零件怎么生产、怎么存放。
Hilt 是什么?
Hilt 是 Google 基于 Dagger 构建的 Android 依赖注入库。它简化了 Dagger 的配置,让DI在Android项目里变得很“顺滑”。
你可能会问:为什么不用原生的 Dagger?嗯,Dagger 虽然强大,但配置起来真的挺繁琐。要在Application里写一大堆Component,还要处理Module、Scope……我当年第一次用 Dagger,光编译就折腾了半天。
Hilt 把这些都封装好了。它自动生成代码,你只需要加几个注解就行。说白了,Hilt 是 Dagger 的“Android 专属版本”,而且是官方推荐的。
Hilt 的核心角色
咱们先看看 Hilt 里几个关键东西:
| 角色 | 注解/类 | 作用 |
|---|---|---|
| 入口点 | @HiltAndroidApp | 标记 Application 类,Hilt 从这里启动 |
| 注入点 | @AndroidEntryPoint | 标记 Activity、Fragment、View、Service 等,告诉你 Hilt 在这里注入依赖 |
| 提供者 | @Module + @Provides | 告诉 Hilt 如何创建那些你自己不能直接构造的对象(比如第三方库的实例) |
| 绑定 | @Binds | 用于将接口绑定到具体实现 |
| 生命周期 | @Singleton、@ActivityScoped 等 | 控制对象的创建和缓存范围 |
我举个例子你就明白了。假设我们有个MainActivity,要用UserRepository。
// 1. Application 上添加 @HiltAndroidApp
@HiltAndroidApp
class MyApp : Application()
// 2. Activity 上添加 @AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// 3. 用 @Inject 告诉 Hilt 帮我注入
@Inject lateinit var repository: UserRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// repository 已经可以用了
repository.getUser()
}
}
// 4. UserRepository 自己也要告诉 Hilt 如何创建
class UserRepository @Inject constructor(
private val api: ApiService
) { ... }
// 5. ApiService 如果是第三方库,用 Module 提供
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideApiService(): ApiService = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
.create(ApiService::class.java)
}
看到了吗?你不需要手动创建任何容器,Hilt 自动帮你搞定了。只需要加注解,剩下的交给编译期生成代码。
小建议:刚开始用 Hilt 时,别一步到位写很多 Module。先让最简单的注入跑通,再逐步加复杂依赖。我在项目中就吃过一次亏,一股脑把所有依赖写进 Module,结果编译报错根本不知道哪里的问题。
为什么我们要学 Hilt 初始化延迟问题?
好,现在你知道了 Hilt 的基本概念。那为什么专门要讲“初始化延迟”呢?
你想想看,Hilt 在应用启动时需要做不少事情:扫描注解、生成代码、构建对象图、创建单例……这一切都在应用进程启动后、第一个 Activity 创建之前完成。如果依赖太多、或者某些初始化很慢(比如 Room 数据库、网络框架、图片加载库),就会导致应用启动变慢。
我做过一个项目,里面有个第三方 SDK 的初始化需要读取本地大文件,每次启动都要卡两三秒。用户反馈“黑屏时间太长”,后来我用 Hilt 的 @Lazy 和 @Provider 做懒加载,启动时间降到了300毫秒。
所以,理解 Hilt 初始化机制,才能做出更快的应用。这就是咱们这门课的核心。
依赖注入 vs 服务定位器
顺便提一句,你可能听过另一种模式叫“服务定位器”(Service Locator)。它的做法是定义一个全局的单例容器,哪里需要就从容器里取。比如:
object ServiceLocator {
val api: ApiService by lazy { Retrofit.Builder().build().create(...) }
}
// 使用时:
val api = ServiceLocator.api
这种方式也可以解耦,但有一个大问题:你无法通过构造函数看出一个类依赖了哪些东西。你只能在运行时看日志才能知道。而且测试时很难替换实现。相比之下,依赖注入更透明、更可控。
注意:不要把 Hilt 当成“万能钥匙”。如果你的项目很简单,只有几个 Activity,完全没必要引入 Hilt。过度设计会得不偿失。我见过有人用 Hilt 写一个 Hello World,结果编译时间多了30秒——纯属自找麻烦。
知识结构总览
下面这张图概括了本章的核心逻辑。你可以把它当作我们整个课程的地图。
从图里你可以看到:我们从一个具体问题(直接new导致的耦合)出发,引出解决思路(外部传入依赖),然后介绍Hilt这个工具,再深入到Hilt的核心注解,最后点明我们要攻克的难点——初始化延迟。整条线非常清楚。
小结
- 依赖注入是一种设计思想:依赖从外部传入,而不是内部创建。
- Hilt是Google为Android打造的DI框架,基于Dagger,简化了配置。
- Hilt通过注解(
@HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module等)自动生成依赖注入代码。 - 初始化延迟是Hilt在生产环境中的常见痛点,也是我们后续章节要解决的核心问题。
动手建议:现在就可以试试创建一个新项目,加入Hilt依赖,写一个最简单的@HiltAndroidApp + @AndroidEntryPoint例子,看看能不能跑起来。遇到编译问题很正常,多在StackOverflow上搜一搜,这是成长最快的路。