> 技术文档 > OkHttp 与 Kotlin 协程完美结合:构建高效的异步网络请求

OkHttp 与 Kotlin 协程完美结合:构建高效的异步网络请求


前言

在现代 Android 开发中,Kotlin 协程已成为处理异步操作的首选方案。将 OkHttp 与协程结合使用,可以创建简洁、高效且易于维护的网络请求架构。本文将深入探讨如何将这两者完美结合,从基础使用到高级技巧,帮助你构建更健壮的应用程序。

一、为什么选择 OkHttp + 协程组合?

1. 传统回调 vs 协程

特性 回调方式 协程方式 代码可读性 嵌套回调,难以维护 线性顺序,逻辑清晰 错误处理 分散在各回调中 集中 try-catch 处理 线程切换 需要手动管理 自动管理,简洁优雅 取消机制 需要手动维护 结构化并发自动取消 学习曲线 较低但难以精通 中等但概念统一

2. 协程核心优势

  • 轻量级线程:比线程更高效,可创建数千个协程

  • 结构化并发:自动管理生命周期和取消操作

  • 挂起函数:用同步方式写异步代码

  • 与 Jetpack 组件深度集成:ViewModel、Lifecycle 等

二、基础集成与配置

1. 添加依赖

// OkHttpimplementation \'com.squareup.okhttp3:okhttp:4.10.0\'// 协程核心implementation \'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4\'implementation \'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4\'

2. 创建 OkHttpClient 实例

val okHttpClient = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build()

三、基本使用模式

1. 简单 GET 请求

suspend fun fetchData(url: String): String { val request = Request.Builder() .url(url) .build() return withContext(Dispatchers.IO) { okHttpClient.newCall(request).execute().use { response -> if (!response.isSuccessful) throw IOException(\"Unexpected code $response\") response.body?.string() ?: throw IOException(\"Empty response\") } }}

2. 结合 Retrofit 使用(推荐)

interface ApiService { @GET(\"users/{id}\") suspend fun getUser(@Path(\"id\") userId: String): User @POST(\"users\") suspend fun createUser(@Body user: User): Response}val retrofit = Retrofit.Builder() .baseUrl(\"https://api.example.com/\") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build()val apiService = retrofit.create(ApiService::class.java)

四、高级应用场景

1. 多个请求顺序执行

viewModelScope.launch { try { // 顺序执行 val user = apiService.getUser(\"123\") val posts = apiService.getPostsByUser(user.id) val comments = apiService.getCommentsForPosts(posts.map { it.id }) _uiState.value = UiState.Success(user, posts, comments) } catch (e: Exception) { _uiState.value = UiState.Error(e) }}

2. 多个请求并行执行

viewModelScope.launch { try { // 并行执行 val deferredUser = async { apiService.getUser(\"123\") } val deferredPosts = async { apiService.getPopularPosts() } val user = deferredUser.await() val posts = deferredPosts.await() _uiState.value = UiState.Success(user, posts) } catch (e: Exception) { _uiState.value = UiState.Error(e) }}

3. 请求重试机制

suspend fun  retryIO( times: Int = 3, initialDelay: Long = 1000, // 1秒 maxDelay: Long = 10000, // 10秒 factor: Double = 2.0, block: suspend () -> T): T { var currentDelay = initialDelay repeat(times - 1) { attempt -> try { return block() } catch (e: IOException) { if (attempt == times - 1) throw e delay(currentDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) } } return block() // 最后一次尝试}// 使用示例viewModelScope.launch { val result = retryIO { apiService.getUser(\"123\") }}

4. 超时控制

viewModelScope.launch { try { val user = withTimeout(5000) { // 5秒超时 apiService.getUser(\"123\") } _uiState.value = UiState.Success(user) } catch (e: TimeoutCancellationException) { _uiState.value = UiState.Error(TimeoutException(\"Request timed out\")) } catch (e: Exception) { _uiState.value = UiState.Error(e) }}

五、线程调度最佳实践

1. 正确的调度器选择

// 网络请求使用 IO 调度器suspend fun fetchData(): Data { return withContext(Dispatchers.IO) { // 执行网络请求 }}// 在主线程更新 UIviewModelScope.launch { val data = fetchData() withContext(Dispatchers.Main) { // 在 Android 中可省略,viewModelScope 默认主线程 updateUI(data) }}

2. 避免主线程阻塞

// 错误示例 - 在主线程执行同步网络请求fun loadData() { viewModelScope.launch(Dispatchers.Main) { // 明确指定主线程更危险 val data = apiService.getData() // 挂起函数,但仍在主线程发起请求 updateUI(data) }}// 正确示例 - 确保网络请求在IO线程suspend fun getData(): Data { return withContext(Dispatchers.IO) { apiService.getData() }}

六、错误处理与状态管理

1. 统一错误处理

sealed class Result { data class Success(val data: T) : Result() data class Error(val exception: Throwable) : Result() object Loading : Result()}suspend fun  safeApiCall(block: suspend () -> T): Result { return try { Result.Success(block()) } catch (e: Exception) { Result.Error(e) }}// 使用示例viewModelScope.launch { _uiState.value = Result.Loading _uiState.value = safeApiCall { apiService.getUser(\"123\") }}

2. 特定错误处理

viewModelScope.launch { try { val response = apiService.getUser(\"123\") if (response.isSuccessful) { _uiState.value = UiState.Success(response.body()!!) } else { when (response.code()) { 401 -> _uiState.value = UiState.Error(AuthException()) 404 -> _uiState.value = UiState.Error(NotFoundException()) else -> _uiState.value = UiState.Error(ApiException(response.message())) } } } catch (e: IOException) { _uiState.value = UiState.Error(NetworkException(e)) } catch (e: Exception) { _uiState.value = UiState.Error(UnexpectedException(e)) }}

七、生命周期管理与取消

1. ViewModel 中的协程

class UserViewModel : ViewModel() { private var currentJob: Job? = null fun loadUser(userId: String) { currentJob?.cancel() // 取消之前的请求 currentJob = viewModelScope.launch { try { _uiState.value = UiState.Loading val user = apiService.getUser(userId) _uiState.value = UiState.Success(user) } catch (e: Exception) { _uiState.value = UiState.Error(e) } } } override fun onCleared() { super.onCleared() currentJob?.cancel() }}

2. Activity/Fragment 中的协程

class UserActivity : AppCompatActivity() { private var loadJob: Job? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loadData() } private fun loadData() { loadJob = lifecycleScope.launch { try { showLoading() val data = apiService.getData() showData(data) } catch (e: Exception) { showError(e) } } } override fun onDestroy() { super.onDestroy() loadJob?.cancel() }}

八、性能优化技巧

1. 请求缓存

val okHttpClient = OkHttpClient.Builder() .cache(Cache(File(context.cacheDir, \"http_cache\"), 10 * 1024 * 1024L)) // 10MB .addNetworkInterceptor { chain -> val response = chain.proceed(chain.request()) val cacheControl = CacheControl.Builder() .maxAge(1, TimeUnit.HOURS) .build() response.newBuilder() .header(\"Cache-Control\", cacheControl.toString()) .build() } .build()

2. 请求合并

suspend fun fetchUserWithPosts(userId: String): UserWithPosts { return coroutineScope { val deferredUser = async { apiService.getUser(userId) } val deferredPosts = async { apiService.getPostsByUser(userId) } UserWithPosts( user = deferredUser.await(), posts = deferredPosts.await() ) }}

3. 限制并发请求数

private val semaphore = Semaphore(5) // 最多5个并发请求suspend fun  withRateLimit(block: suspend () -> T): T { semaphore.acquire() return try { block() } finally { semaphore.release() }}// 使用示例viewModelScope.launch { val result = withRateLimit { apiService.getHeavyResource() }}

九、测试策略

1. 单元测试

@Testfun `getUser should return user when successful`() = runTest { // 准备 val mockResponse = MockResponse() .setBody(\"\"\"{\"id\":\"123\",\"name\":\"John\"}\"\"\") .setResponseCode(200) mockWebServer.enqueue(mockResponse) // 执行 val result = apiService.getUser(\"123\") // 验证 assertEquals(\"123\", result.id) assertEquals(\"John\", result.name)}@Testfun `getUser should throw on network error`() = runTest { // 准备 mockWebServer.shutdown() // 执行 & 验证 assertFailsWith { apiService.getUser(\"123\") }}

2. ViewModel 测试

@Testfun `loadUser should update state to Success`() = runTest { // 准备 val mockUser = User(\"123\", \"John\") coEvery { mockApiService.getUser(\"123\") } returns mockUser // 执行 viewModel.loadUser(\"123\") // 验证 assertEquals( UiState.Success(mockUser), viewModel.uiState.value )}

十、总结与最佳实践

OkHttp 与 Kotlin 协程的结合为 Android 网络编程带来了革命性的改进。通过本文的介绍,我们了解到:

  1. 基本集成:如何将 OkHttp 请求封装为挂起函数

  2. 高级应用:包括并行请求、错误处理、重试机制等场景

  3. 线程调度:合理使用调度器保证性能

  4. 生命周期:结构化并发自动管理协程生命周期

  5. 性能优化:通过缓存、请求合并等技术提升效率

最佳实践建议:

  • 优先使用 Retrofit + 协程适配器:比直接封装 OkHttp 更简洁

  • 合理处理异常:区分网络错误、API错误和业务错误

  • 使用结构化并发:避免全局协程作用域

  • 注意线程安全:确保在正确线程访问UI和共享状态

  • 编写测试用例:特别是错误场景和边界条件

  • 监控协程性能:使用 CoroutineDebugger 等工具分析

通过合理应用这些技术和最佳实践,您可以构建出高效、可靠且易于维护的 Android 网络请求架构,充分发挥协程和 OkHttp 的组合优势。