Android网络请求实战:OkHttp与Retrofit黄金组合

1. 项目概述

在Android开发中,网络请求是每个应用都绕不开的核心功能。OkHttp和Retrofit作为Square公司推出的黄金搭档,已经成为现代Android网络请求的事实标准。OkHttp提供了高效的底层HTTP客户端实现,而Retrofit则在此基础上构建了类型安全的RESTful API封装层。

我仍然记得第一次接触Retrofit时的惊艳感——原来网络请求可以如此优雅!通过简单的接口定义就能完成复杂的网络操作,再也不用面对冗长的HttpURLConnection代码。本文将带你从零开始掌握这对黄金组合的使用技巧,包括基础配置、高级功能以及我在实际项目中积累的实战经验。

2. 环境准备与基础配置

2.1 依赖引入

首先在模块的build.gradle文件中添加必要依赖。建议使用最新稳定版本(截至2023年7月):

dependencies { // OkHttp核心库 implementation 'com.squareup.okhttp3:okhttp:4.10.0' // Retrofit核心库 implementation 'com.squareup.retrofit2:retrofit:2.9.0' // Gson转换器 implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 日志拦截器(调试用) implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0' }

注意:Retrofit 2.6.0+开始需要Java 8或Android API 21+。如果你的项目需要支持更低版本,可以考虑使用Retrofit 2.5.0。

2.2 基础配置实例

创建Retrofit实例是使用的第一步,典型的配置如下:

val okHttpClient = OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } }) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build() val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build()

这里有几个关键点需要注意:

  1. 日志拦截器只在调试模式启用,避免生产环境泄露敏感信息
  2. 超时设置应根据实际业务需求调整,电商类应用可能需要更短的超时
  3. baseUrl必须以斜杠结尾,否则会抛出IllegalArgumentException

3. API接口定义与使用

3.1 基础请求定义

Retrofit的核心魅力在于通过接口定义网络请求。以下是一个完整的示例:

interface UserService { @GET("users/{id}") suspend fun getUser(@Path("id") userId: String): Response<User> @POST("users") suspend fun createUser(@Body user: User): Response<CreateUserResponse> @GET("users") suspend fun listUsers( @Query("page") page: Int, @Query("size") size: Int = 20 ): Response<List<User>> @Multipart @POST("avatar") suspend fun uploadAvatar( @Part file: MultipartBody.Part, @Part("description") description: RequestBody ): Response<UploadResult> }

3.2 注解详解

Retrofit提供了丰富的注解来控制请求行为:

  • 方法注解:定义HTTP方法(@GET, @POST, @PUT等)
  • 参数注解
    • @Path - 替换URL中的路径参数
    • @Query - 添加查询参数
    • @Body - 请求体,会自动使用配置的Converter转换
    • @Header - 添加请求头
    • @Field - 表单字段(需与@FormUrlEncoded配合使用)

3.3 响应处理

Retrofit支持多种响应处理方式:

// 方式1:直接返回实体类(自动解析) @GET("users/{id}") suspend fun getUser(@Path("id") userId: String): User // 方式2:返回Response对象(包含响应元信息) @GET("users/{id}") suspend fun getUser(@Path("id") userId: String): Response<User> // 方式3:返回原始ResponseBody @GET("users/{id}") suspend fun getUser(@Path("id") userId: String): ResponseBody

实际项目中推荐使用Response包装,可以获取状态码、头部等信息,便于错误处理。

4. 高级功能与实战技巧

4.1 自定义拦截器

OkHttp的拦截器机制非常强大,可以实现各种定制功能:

class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().newBuilder() .apply { tokenProvider()?.let { token -> addHeader("Authorization", "Bearer $token") } } .build() return chain.proceed(request) } } // 使用方式 OkHttpClient.Builder() .addInterceptor(AuthInterceptor { userManager.getToken() }) .build()

4.2 文件上传下载

文件上传需要使用Multipart:

// 上传文件 val filePart = MultipartBody.Part.createFormData( "file", file.name, file.asRequestBody("image/*".toMediaType()) ) val descriptionPart = "avatar image".toRequestBody("text/plain".toMediaType()) userService.uploadAvatar(filePart, descriptionPart) // 下载文件 @Streaming @GET("files/{id}") suspend fun downloadFile(@Path("id") fileId: String): Response<ResponseBody>

注意:大文件下载必须使用@Streaming注解,避免一次性加载到内存。

4.3 自定义Converter

当需要处理特殊数据格式时,可以自定义Converter:

class XmlConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array<Annotation>, retrofit: Retrofit ): Converter<ResponseBody, *> { return Converter { value -> parseXml(value.string(), type) } } // 实现parseXml方法... } // 使用方式 Retrofit.Builder() .addConverterFactory(XmlConverterFactory()) // ...

5. 常见问题与解决方案

5.1 网络请求失败处理

建议统一封装错误处理逻辑:

suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): Result<T> { return try { val response = apiCall() if (response.isSuccessful) { Result.success(response.body()!!) } else { Result.failure(parseError(response.errorBody())) } } catch (e: Exception) { Result.failure(mapException(e)) } } // 使用示例 val result = safeApiCall { userService.getUser("123") } when (result) { is Result.Success -> { /* 处理成功 */ } is Result.Failure -> { /* 处理失败 */ } }

5.2 多BaseUrl支持

在微服务架构中,可能需要访问不同域名的API:

val retrofit = Retrofit.Builder() .baseUrl("http://placeholder.com/") // 必须设置但不会实际使用 .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() // 动态设置BaseUrl的Interceptor class BaseUrlInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val newUrl = originalRequest.url.newBuilder() .host(getHostForRequest(originalRequest)) .build() val newRequest = originalRequest.newBuilder() .url(newUrl) .build() return chain.proceed(newRequest) } private fun getHostForRequest(request: Request): String { // 根据请求特征返回对应的host } }

5.3 缓存策略配置

合理配置缓存可以显著提升用户体验:

val cacheSize = 10 * 1024 * 1024 // 10MB val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize.toLong()) val okHttpClient = OkHttpClient.Builder() .cache(cache) .addInterceptor(CacheInterceptor()) .addNetworkInterceptor(OnlineCacheInterceptor()) .build() class CacheInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var request = chain.request() if (!NetworkUtil.isNetworkAvailable(context)) { request = request.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=3600") .build() } return chain.proceed(request) } }

6. 性能优化建议

6.1 连接池优化

OkHttp默认使用连接池复用连接,但可以进一步优化:

val connectionPool = ConnectionPool( maxIdleConnections = 5, // 最大空闲连接数 keepAliveDuration = 5, // 保持时间(分钟) timeUnit = TimeUnit.MINUTES ) val okHttpClient = OkHttpClient.Builder() .connectionPool(connectionPool) .build()

6.2 请求合并与批处理

对于高频小请求,可以考虑合并:

@POST("batch") suspend fun batchRequests(@Body requests: List<BatchRequestItem>): Response<BatchResponse> // 使用示例 val batchResult = userService.batchRequests(listOf( BatchRequestItem("GET", "/users/1"), BatchRequestItem("GET", "/users/2") ))

6.3 取消请求机制

避免内存泄漏和无效请求:

private val callMap = mutableMapOf<String, Call>() fun fetchUser(userId: String) { val call = userService.getUser(userId) callMap[userId] = call call.enqueue(object : Callback<User> { override fun onResponse(call: Call, response: Response<User>) { callMap.remove(userId) // 处理响应 } override fun onFailure(call: Call, t: Throwable) { callMap.remove(userId) // 处理失败 } }) } fun cancelRequest(userId: String) { callMap[userId]?.cancel() callMap.remove(userId) }

7. 测试策略

7.1 Mock Server测试

使用MockWebServer进行接口测试:

val mockWebServer = MockWebServer() @Before fun setup() { mockWebServer.start() val retrofit = Retrofit.Builder() .baseUrl(mockWebServer.url("/")) .client(OkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .build() service = retrofit.create(UserService::class.java) } @Test fun testGetUser() = runBlocking { mockWebServer.enqueue(MockResponse() .setBody("""{"id":"123","name":"test"}""") .setResponseCode(200)) val response = service.getUser("123") assertThat(response.isSuccessful).isTrue() assertThat(response.body()?.name).isEqualTo("test") } @After fun tearDown() { mockWebServer.shutdown() }

7.2 流量统计

监控网络请求情况:

class MetricsInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val startNs = System.nanoTime() val response = chain.proceed(request) val tookMs = (System.nanoTime() - startNs) / 1_000_000 val contentLength = response.body?.contentLength() ?: -1 // 上报指标 metricsCollector.report( url = request.url.toString(), method = request.method, status = response.code, duration = tookMs, size = contentLength ) return response } }

8. 项目结构建议

对于大型项目,建议采用分层结构:

network/ ├── api/ │ ├── UserService.kt │ ├── ProductService.kt │ └── ... ├── interceptors/ │ ├── AuthInterceptor.kt │ ├── LoggingInterceptor.kt │ └── ... ├── models/ │ ├── requests/ │ └── responses/ ├── converters/ ├── RetrofitClient.kt └── ApiResult.kt

关键文件说明:

  • RetrofitClient.kt:单例管理Retrofit实例
  • ApiResult.kt:统一封装API返回结果
  • interceptors/:存放各类拦截器
  • converters/:自定义转换器实现

9. 兼容性考虑

9.1 多平台支持

Retrofit不仅支持Android,也可用于Java/Kotlin后端项目:

// JVM项目配置示例 val retrofit = Retrofit.Builder() .baseUrl("http://internal.api.com/") .client(OkHttpClient()) .addConverterFactory(JacksonConverterFactory.create()) .build()

9.2 与协程/RxJava配合

Retrofit天然支持协程和RxJava:

// 协程版本 @GET("users") suspend fun getUsers(): List<User> // RxJava版本 @GET("users") fun getUsers(): Observable<List<User>> // Flow版本 @GET("users") fun getUsers(): Flow<List<User>>

10. 安全最佳实践

10.1 证书锁定

防止中间人攻击:

val certificatePinner = CertificatePinner.Builder() .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build() val okHttpClient = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build()

10.2 敏感信息保护

避免日志泄露敏感数据:

class RedactingInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val response = chain.proceed(request) if (!BuildConfig.DEBUG) { return response } val contentType = response.body?.contentType() if (contentType?.subtype == "json") { val bodyString = response.body?.string() ?: return response val redactedBody = redactSensitiveData(bodyString) val newBody = ResponseBody.create(contentType, redactedBody) return response.newBuilder().body(newBody).build() } return response } private fun redactSensitiveData(json: String): String { // 实现敏感字段脱敏逻辑 } }

11. 调试技巧

11.1 使用Stetho调试

Facebook的Stetho工具可以方便地查看网络请求:

implementation 'com.facebook.stetho:stetho:1.6.0' implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
val okHttpClient = OkHttpClient.Builder() .addNetworkInterceptor(StethoInterceptor()) .build()

11.2 请求标记

为每个请求添加唯一标识便于追踪:

class RequestIdInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().newBuilder() .addHeader("X-Request-ID", UUID.randomUUID().toString()) .build() return chain.proceed(request) } }

12. 未来演进

随着Kotlin多平台(KMP)的发展,Retrofit也在不断进化。目前已经可以通过Kotlin的expect/actual机制实现多平台共享网络层代码。同时,HTTP/3的支持也在路线图中,未来在移动端将能获得更快的网络性能。

在实际项目中,我建议保持对OkHttp和Retrofit新版本的关注,但不要盲目升级。每次升级前应在测试环境充分验证,特别是注意ProGuard规则的变化。