依赖注入(Hilt)集成:Hilt基础概念、创建网络模块、提供Retrofit与OkHttp实例

好,咱们今天来聊聊 Hilt。说实话,依赖注入这个概念,刚入行那会儿我觉得挺玄乎的。不就是 new 个对象吗?搞那么复杂干嘛?直到我接手了一个有十几个 Activity 和二十多个网络请求类的项目……嗯,那滋味,谁改谁知道。

Hilt 是 Google 官方基于 Dagger 打造的 Android 依赖注入框架。说白了,它帮你管理对象的创建和生命周期。你只管声明「我需要什么」,Hilt 负责「怎么给你」。我个人习惯把 Hilt 看作是 Android 版的 Spring Boot——虽然不完全一样,但思路是相通的。

Hilt 的核心概念

要理解 Hilt,你得先搞懂这几个东西:

  • @HiltAndroidApp:标注在 Application 类上。这是 Hilt 的入口,所有依赖注入的起点。
  • @AndroidEntryPoint:标注在 Activity、Fragment、Service 等组件上。告诉 Hilt:「这个类需要注入依赖」。
  • @Module:标注在一个类上,表示这个类是一个「模块」,负责提供依赖对象。
  • @Provides:标注在 Module 内部的方法上,告诉 Hilt:「这个方法可以创建某个类型的对象」。
  • @Singleton:标注在提供方法上,表示这个对象在整个应用中是单例。

你想想看,如果没有 Hilt,你要在 Activity 里用 Retrofit,得自己写个单例类,或者传参。有了 Hilt,你只需要在 Activity 里写 @Inject lateinit var retrofit: Retrofit,剩下的交给框架。

核心思想:Hilt 把「创建对象」和「使用对象」分开了。你只关心怎么用,不关心怎么创建。

创建 NetworkModule

好,理论说完了,咱们直接上手。创建一个网络模块,专门提供 Retrofit 和 OkHttp 的实例。

我在项目中遇到过一种情况:团队里有人把 OkHttp 配置散落在各个文件里,有的设了超时,有的没设,最后排查问题的时候简直想哭。所以我的建议是——所有网络相关的依赖,统一放在一个 Module 里

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()
    }

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

这里有几个细节要注意:

  • @InstallIn(SingletonComponent::class) 表示这个模块的依赖在整个应用生命周期内有效。
  • @Singleton 确保 OkHttpClient 和 Retrofit 只创建一次。你想想看,每次请求都 new 一个 OkHttpClient,那内存和性能都扛不住。
  • provideRetrofit 的参数 okHttpClient: OkHttpClient 是 Hilt 自动注入的。它知道你需要 OkHttpClient,就会从 provideOkHttpClient 方法里拿。

小技巧:如果你有多个 baseUrl,可以创建多个 Retrofit 实例,用 @Named 或者 @Qualifier 来区分。我一般用自定义的 Qualifier 注解,可读性更好。

提供 OkHttp 实例的细节

OkHttp 的配置其实挺讲究的。我曾经遇到过一个线上问题:用户反馈图片加载慢,查了半天发现是 OkHttp 的缓存没配好。所以这里我多说几句。

@Provides
@Singleton
fun provideCache(): Cache {
    return Cache(File(context.cacheDir, "http_cache"), 10L * 1024 * 1024) // 10MB
}

@Provides
@Singleton
fun provideOkHttpClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(Interceptor { chain ->
            val request = chain.request().newBuilder()
                .addHeader("Accept-Language", "zh-CN")
                .build()
            chain.proceed(request)
        })
        .build()
}

看到了吗?我把 Cache 也单独提供了一个方法。这样做的好处是:如果你想换缓存策略,只需要改这一个地方。而且 Hilt 会自动把 Cache 注入到 OkHttpClient 的提供方法里。

注意:OkHttp 的 Interceptor 顺序很重要。如果你同时加了日志拦截器和缓存拦截器,日志拦截器应该放在最外层,否则可能看不到缓存命中的日志。我曾经因为这个排查了半天……嗯,说多了都是泪。

提供 Retrofit 实例的细节

Retrofit 的提供方法相对简单,但有一个坑:ConverterFactory 的添加顺序。如果你同时用了 Gson 和 Moshi,顺序会影响最终使用哪个转换器。

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

我个人习惯把 GsonConverterFactory 放在最前面。为什么?因为 Retrofit 会遍历所有 ConverterFactory,找到第一个能处理该类型的。Gson 比较通用,放前面能覆盖大多数情况。

整体架构图

下面这张图展示了 Hilt 网络模块的核心流程。我画的时候尽量简洁,你看一眼就能明白。

Hilt 网络模块依赖注入流程 @HiltAndroidApp @Module NetworkModule @Provides OkHttpClient @Provides Retrofit @AndroidEntryPoint Activity @AndroidEntryPoint Activity

这张图其实就说明了一件事:Hilt 从 Application 启动,扫描 NetworkModule,然后按需提供 OkHttpClient 和 Retrofit,最终注入到 Activity 中。整个过程你不需要手动 new 任何对象。

避坑指南

最后,我总结几个实际开发中容易踩的坑:

  • Module 没加 @InstallIn:Hilt 不知道这个模块属于哪个组件,编译直接报错。我刚开始用的时候经常忘。
  • 多个 Module 提供同类型对象:Hilt 会报 duplicate binding 错误。解决办法是用 @Named 或自定义 Qualifier。
  • OkHttpClient 没加 @Singleton:每次注入都 new 一个客户端,连接池白配了,性能大打折扣。
  • Retrofit 的 baseUrl 末尾没加 /:这个坑我踩过两次。Retrofit 要求 baseUrl 必须以 / 结尾,否则会抛出 IllegalArgumentException。

我的习惯:每次写完 NetworkModule,我都会先跑一遍单元测试,确保所有提供方法都能正常返回对象。别等到集成测试才发现问题,那时候排查成本就高了。

好了,Hilt 集成网络模块的内容就这些。说白了,就是三步:建 Module、写 Provides、加注解。但每一步都有细节,希望你能在实际项目中慢慢体会。

公众号:蓝海资料掘金营,微信deep3321