2. Hilt依赖注入基础:@Inject、@Module、@Provides

好,咱们正式开始动手。这章是Hilt的基石,说白了就是搞懂三样东西:@Inject、@Module 和 @Provides。

你想想看,Hilt本质上是个编译期注解处理器。它帮我们生成代码,在运行时把依赖“喂”给需要的对象。那么问题来了,它怎么知道该喂什么、怎么喂?

嗯,答案就在这三个注解里。我刚开始用Dagger的时候,也被这三兄弟搞晕过。今天咱们就捋清楚它们各自管什么、怎么搭配用。

核心概念一句话总结:

  • @Inject 告诉Hilt:“嘿,这个东西需要你帮忙创建,或者它能创建别人需要的东西。”
  • @Module 是“工具箱”或“说明书”,用来告诉Hilt那些它自己搞不定的依赖该怎么造。
  • @Provides 是“说明书”里的具体条目,就是“造这个东西的步骤”。
Hilt依赖注入核心注解关系图 依赖注入核心注解关系 @Inject 字段/构造注入 构件对象图 @Module @Provides 接口/第三方绑定 自定义创建逻辑 💡 搭配原则:优先用 @Inject 构造注入;第三方或接口用 @Module + @Provides;需要区分同类型用 @Qualifier。

2.1 @Inject:告诉Hilt“我要什么,我能造什么”

@Inject 主要有两个用法,你别把它想复杂了。

用法一:构造方法注入

把你的类的构造方法加上 @Inject。Hilt看到后就会知道:“哦,这个类我能帮你new出来。”我自己特别喜欢这种方式,因为它最直观。

class AnalyticsService @Inject constructor(
    private val remoteDataSource: RemoteDataSource,
    private val localCache: LocalCache
) {
    fun trackEvent(eventName: String) {
        // ...
    }
}

我的习惯:能用构造注入的,我绝不用字段注入。原因很简单——构造注入让依赖关系一目了然,单元测试也好mock。

用法二:字段注入

用在Activity、Fragment这类由系统创建实例的类上。你不能改它们的构造方法,所以只能注入到字段。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var analyticsService: AnalyticsService
    @Inject lateinit var userManager: UserManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 此时 analyticsService 和 userManager 已经可用
    }
}

注意!字段注入的变量必须是 lateinit var,而且注解写在字段上。我曾经见过有人把 @Inject 写在 getter 上……嗯,编译倒是能过,但运行时会崩得很惨。

2.2 @Module:给Hilt配说明书

那问题来了——如果依赖是接口、抽象类、或者第三方的类(比如 OkHttp),我总不能去改它们的构造方法吧?

这时候就需要 @Module 出场了。它是一个容器,专门放那些“如何创建复杂依赖”的方法。

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

看到没?@Module 负责打包,@InstallIn 告诉Hilt“这个模块在哪个组件生效”。

关键点:模块里的方法用 @Provides 标注,返回什么类型,Hilt就知道它能提供什么依赖。

2.3 @Provides:手把手教Hilt造对象

@Provides 是 @Module 内部的“生产方法”。说白了,就是“要造这个对象,请按这个方法走”。

我简单列一下常用的几种场景,你可直接套用:

场景 示例 说明
创建接口实例 @Provides fun provideRepo(impl: RepoImpl): Repo 返回接口类型,Hilt自动注入实现类
创建第三方库对象 @Provides fun provideOkHttp(): OkHttpClient 框架之外的类必须由Module提供
创建带配置的对象 @Provides fun provideGson(): Gson 可以配置日期格式等选项

2.4 限定符:解决“同一个类型多个实例”

我在项目中遇到过最头疼的问题——两个 String 类型的依赖。一个代表API baseUrl,一个代表数据库名称。Hilt一看,都是 String,它就懵了。

解决方案?用 @Qualifier(限定符)。咱自己造一个注解来区分。

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BaseUrl

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DatabaseName

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @BaseUrl
    @Provides
    @Singleton
    fun provideBaseUrl(): String = "https://api.example.com"

    @DatabaseName
    @Provides
    @Singleton
    fun provideDatabaseName(): String = "user_database"
}

// 使用
class UserRepository @Inject constructor(
    @BaseUrl private val baseUrl: String,
    @DatabaseName private val dbName: String
) { ... }

避坑指南:我曾经在团队里看到有人把 @Qualifier 和 @Named 混用。我个人建议统一用自定义 @Qualifier,因为 @Named 是字符串比较,重构时容易改漏。自定义注解安全得多。

2.5 Provider:懒加载与工厂模式

有时候你不想立刻创建对象,或者想每次都拿个新的。可以用 Provider。把依赖声明成 Provider<T>,每次调 .get() 就生产一个实例。

class LoginViewModel @Inject constructor(
    private val userManagerProvider: Provider<UserManager>
) {
    fun login() {
        // 每次点击登录,都获得一个新的 UserManager 实例
        val userManager = userManagerProvider.get()
        userManager.performLogin()
    }
}

注意,如果 UserManager 本身标记了 @Singleton,那 Provider.get() 返回的始终是同一个单例。这个细节很关键,我之前排查过一个bug,就是没搞清单例和Provider的协作规则。

2.6 本章小结

到这,Hilt依赖注入的三个核心注解你应该心里有数了:

  • @Inject:我自己的类,用它标记构造;系统创建的类,用它标记字段。
  • @Module + @Provides:第三方的、接口的、需要配置的依赖,放这里集中管理。
  • @Qualifier:同类型不同实例时,用自定义注解区分。
  • Provider:需要懒加载或每次获取新实例时使用。

一句话记住:Hilt依赖注入的底层逻辑就是——@Inject 提出需求,@Module + @Provides 给出生产方案,@Qualifier 解决歧义。