前言
在现代 Android 开发中,Kotlin 协程已成为处理异步操作的首选方案。将 OkHttp 与协程结合使用,可以创建简洁、高效且易于维护的网络请求架构。本文将深入探讨如何将这两者完美结合,从基础使用到高级技巧,帮助你构建更健壮的应用程序。
一、为什么选择 OkHttp + 协程组合?
1. 传统回调 vs 协程
特性 | 回调方式 | 协程方式 |
---|---|---|
代码可读性 | 嵌套回调,难以维护 | 线性顺序,逻辑清晰 |
错误处理 | 分散在各回调中 | 集中 try-catch 处理 |
线程切换 | 需要手动管理 | 自动管理,简洁优雅 |
取消机制 | 需要手动维护 | 结构化并发自动取消 |
学习曲线 | 较低但难以精通 | 中等但概念统一 |
2. 协程核心优势
-
轻量级线程:比线程更高效,可创建数千个协程
-
结构化并发:自动管理生命周期和取消操作
-
挂起函数:用同步方式写异步代码
-
与 Jetpack 组件深度集成:ViewModel、Lifecycle 等
二、基础集成与配置
1. 添加依赖
// OkHttp
implementation '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<Unit>
}
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 <T> 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) {
// 执行网络请求
}
}
// 在主线程更新 UI
viewModelScope.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<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
suspend fun <T> safeApiCall(block: suspend () -> T): Result<T> {
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 <T> withRateLimit(block: suspend () -> T): T {
semaphore.acquire()
return try {
block()
} finally {
semaphore.release()
}
}
// 使用示例
viewModelScope.launch {
val result = withRateLimit {
apiService.getHeavyResource()
}
}
九、测试策略
1. 单元测试
@Test
fun `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)
}
@Test
fun `getUser should throw on network error`() = runTest {
// 准备
mockWebServer.shutdown()
// 执行 & 验证
assertFailsWith<IOException> {
apiService.getUser("123")
}
}
2. ViewModel 测试
@Test
fun `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 网络编程带来了革命性的改进。通过本文的介绍,我们了解到:
-
基本集成:如何将 OkHttp 请求封装为挂起函数
-
高级应用:包括并行请求、错误处理、重试机制等场景
-
线程调度:合理使用调度器保证性能
-
生命周期:结构化并发自动管理协程生命周期
-
性能优化:通过缓存、请求合并等技术提升效率
最佳实践建议:
-
优先使用 Retrofit + 协程适配器:比直接封装 OkHttp 更简洁
-
合理处理异常:区分网络错误、API错误和业务错误
-
使用结构化并发:避免全局协程作用域
-
注意线程安全:确保在正确线程访问UI和共享状态
-
编写测试用例:特别是错误场景和边界条件
-
监控协程性能:使用 CoroutineDebugger 等工具分析
通过合理应用这些技术和最佳实践,您可以构建出高效、可靠且易于维护的 Android 网络请求架构,充分发挥协程和 OkHttp 的组合优势。