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集成的完整流程:
11.7 避坑指南
最后,分享几个我踩过的坑:
- OkHttpClient重复创建:如果不加@Singleton,每次注入都会new一个OkHttpClient,连接池就白费了
- baseUrl末尾必须加/:Retrofit要求baseUrl以/结尾,否则会报IllegalArgumentException
- 拦截器顺序:先添加的拦截器先执行。如果你在拦截器里做了重试逻辑,记得放在最前面
- ConverterFactory顺序:如果有多个ConverterFactory,先添加的优先级更高
个人建议:在开发阶段,日志拦截器的level设为BODY。上线前记得改成HEADERS或者直接去掉,不然日志里会打印出用户的敏感信息。
好了,关于Hilt与Retrofit的集成,核心就是这些。说白了就是三步:定义Module、提供依赖、注入使用。但每一步都有细节需要注意,希望今天的分享能帮你少走弯路。