11. Hilt与Retrofit:网络模块的封装

网络请求,几乎是每个Android应用都绕不开的坎。Retrofit作为最流行的HTTP客户端,搭配Hilt来做依赖注入,可以说是天作之合。今天我们就来聊聊,怎么把这一套组合拳打得漂亮。

11.1 为什么需要封装网络模块?

你想想看,一个App里可能有几十个接口。如果每个ViewModel都自己去new一个Retrofit实例,那代码就乱成一锅粥了。更别提OkHttpClient的配置——超时时间、拦截器、日志打印,这些全局配置应该只写一次。

我个人习惯把网络模块单独抽出来,用Hilt的Module来管理。这样做的好处很明显:

  • 统一配置:所有网络相关的东西都在一个地方
  • 易于测试:可以轻松替换为Mock数据
  • 生命周期可控:单例管理,避免内存泄漏

核心思路:用Hilt的@Module和@Provides来提供OkHttpClient和Retrofit实例,再用@Singleton确保全局只有一个实例。

11.2 基础依赖配置

首先,在build.gradle里加上必要的依赖。嗯,这里要注意版本号的一致性,我踩过这个坑。

// 项目级 build.gradle
plugins {
    id 'com.google.dagger.hilt.android' version '2.48' apply false
}

// 模块级 build.gradle
plugins {
    id 'dagger.hilt.android.plugin'
}

dependencies {
    // Hilt
    implementation 'com.google.dagger.hilt.android:2.48'
    kapt 'com.google.dagger.hilt.android.compiler:2.48'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

    // OkHttp
    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
}

注意:Hilt的版本和AGP版本有兼容性要求。我曾经因为版本不匹配,编译报错折腾了半天。建议去官方文档查一下兼容性矩阵。

11.3 创建网络模块

接下来,我们创建一个NetworkModule。这个类用@Module注解标记,告诉Hilt这是一个依赖提供者。

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

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        val loggingInterceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            .addInterceptor { chain ->
                val request = chain.request().newBuilder()
                    .addHeader("Content-Type", "application/json")
                    .addHeader("Accept", "application/json")
                    .build()
                chain.proceed(request)
            }
            .build()
    }

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

    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

这里有几个关键点:

  • @InstallIn(SingletonComponent::class):表示这个模块的依赖在整个应用生命周期内都可用
  • @Singleton:确保OkHttpClient和Retrofit是单例
  • provideRetrofit方法接收OkHttpClient参数:Hilt会自动注入,不需要手动创建

11.4 OkHttpClient的注入细节

OkHttpClient的配置,说白了就是网络请求的「基础设施」。我在项目中遇到过一个问题:多个拦截器的执行顺序搞反了,导致认证token一直没加上去。

这里我建议把拦截器分成两类:

类型 作用 示例
应用拦截器 处理请求/响应的业务逻辑 添加Header、重试机制
网络拦截器 处理网络层面的逻辑 日志打印、重定向

如果你需要动态添加Header(比如token),可以这样写:

class AuthInterceptor @Inject constructor(
    private val tokenManager: TokenManager
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val token = tokenManager.getToken()

        val request = if (token != null) {
            originalRequest.newBuilder()
                .header("Authorization", "Bearer $token")
                .build()
        } else {
            originalRequest
        }

        return chain.proceed(request)
    }
}

然后在NetworkModule里注入这个拦截器:

@Provides
@Singleton
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient {
    return OkHttpClient.Builder()
        .addInterceptor(authInterceptor)
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()
}

小技巧:日志拦截器建议放在最后,这样可以看到完整的请求和响应信息,包括添加的Header。

11.5 Retrofit的封装与使用

Retrofit的注入相对简单,但有一个细节值得注意:baseUrl的配置。我个人习惯把baseUrl放在BuildConfig里,方便不同环境切换。

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

    @Provides
    @Singleton
    fun provideBaseUrl(): String {
        return BuildConfig.BASE_URL
    }

    @Provides
    @Singleton
    fun provideRetrofit(
        okHttpClient: OkHttpClient,
        baseUrl: String
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

然后在ViewModel里使用:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService
) : ViewModel() {

    fun fetchData() {
        viewModelScope.launch {
            try {
                val response = apiService.getData()
                // 处理响应
            } catch (e: Exception) {
                // 处理异常
            }
        }
    }
}

11.6 知识体系图

下面这张图展示了Hilt与Retrofit集成的完整流程:

Hilt + Retrofit 网络模块架构图 NetworkModule @Provides @Singleton OkHttpClient 拦截器、超时配置 Retrofit baseUrl、Converter ApiService 接口定义 ViewModel @Inject constructor

11.7 避坑指南

最后,分享几个我踩过的坑:

  • OkHttpClient重复创建:如果不加@Singleton,每次注入都会new一个OkHttpClient,连接池就白费了
  • baseUrl末尾必须加/:Retrofit要求baseUrl以/结尾,否则会报IllegalArgumentException
  • 拦截器顺序:先添加的拦截器先执行。如果你在拦截器里做了重试逻辑,记得放在最前面
  • ConverterFactory顺序:如果有多个ConverterFactory,先添加的优先级更高

个人建议:在开发阶段,日志拦截器的level设为BODY。上线前记得改成HEADERS或者直接去掉,不然日志里会打印出用户的敏感信息。

好了,关于Hilt与Retrofit的集成,核心就是这些。说白了就是三步:定义Module、提供依赖、注入使用。但每一步都有细节需要注意,希望今天的分享能帮你少走弯路。

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