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秒——纯属自找麻烦。

知识结构总览

下面这张图概括了本章的核心逻辑。你可以把它当作我们整个课程的地图。

Hilt初始化延迟问题 知识体系(第1章入门) 依赖注入 (DI) 直接new的问题 外部传入依赖 Hilt (Android DI框架) 注解(@Inject等) 初始化延迟问题 课程目标 • 理解依赖注入核心思想,不再手动new • 掌握Hilt基础用法,能快速搭建DI项目 • 深入学习Hilt初始化机制,找出并解决启动延迟问题

从图里你可以看到:我们从一个具体问题(直接new导致的耦合)出发,引出解决思路(外部传入依赖),然后介绍Hilt这个工具,再深入到Hilt的核心注解,最后点明我们要攻克的难点——初始化延迟。整条线非常清楚。

小结

  • 依赖注入是一种设计思想:依赖从外部传入,而不是内部创建。
  • Hilt是Google为Android打造的DI框架,基于Dagger,简化了配置。
  • Hilt通过注解(@HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module等)自动生成依赖注入代码。
  • 初始化延迟是Hilt在生产环境中的常见痛点,也是我们后续章节要解决的核心问题。

动手建议:现在就可以试试创建一个新项目,加入Hilt依赖,写一个最简单的@HiltAndroidApp + @AndroidEntryPoint例子,看看能不能跑起来。遇到编译问题很正常,多在StackOverflow上搜一搜,这是成长最快的路。