You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Kotlin协程替换Volley网络请求中的Callback回调

核心结论

可以通过协程完全移除回调,你当前的实现思路存在问题,没有正确将回调式API转换为协程的挂起形式,协程的使用方式也不符合Android场景要求。


现有代码的问题

  • 你包裹在async里的Volley请求本身是异步执行的,async块在把请求加入队列之后就立刻结束了,await()根本等不到回调返回的结果,协程在这里完全没起到等待异步结果的作用
  • runBlocking会阻塞当前调用线程,如果你在主线程调用这个方法会直接导致ANR,完全不适合Android业务场景
  • 回调作为参数传入本身就违背了用协程扁平化异步代码的目的

解决方案

用kotlinx.coroutines提供的suspendCancellableCoroutine(可取消的挂起函数构造器)把回调式API包装成标准挂起函数,不需要传入回调,直接返回结果或者抛出异常。

修改后的完整实现

const val BASE_URL = "https://pokeapi.co/api/v2/pokemon/"

class PokeClient {
    // 改为挂起函数,不需要传入回调,直接返回Pokemon对象,异常由调用方统一处理
    suspend fun getPokemonData(context: Context, pokemonName: String): Pokemon = suspendCancellableCoroutine { continuation ->
        val queue = Volley.newRequestQueue(context)
        val requestUrl = BASE_URL + pokemonName

        val stringRequest = StringRequest(
            Request.Method.GET,
            requestUrl,
            { response ->
                // 网络请求成功,解析数据
                try {
                    val jObj = JSONObject(response)
                    val imgUrl = jObj
                        .getJSONObject("sprites")
                        .getJSONObject("other")
                        .getJSONObject("official-artwork")
                        .getString("front_default")
                    
                    // 图片下载是IO阻塞操作,切到IO调度器执行
                    CoroutineScope(Dispatchers.IO).launch {
                        val inputStream = URL(imgUrl).openStream()
                        val bitmap = BitmapFactory.decodeStream(inputStream)
                        // 恢复挂起,返回解析完成的Pokemon对象
                        continuation.resume(Pokemon(name = jObj.getString("name"), image = bitmap))
                    }
                } catch (e: Exception) {
                    // 数据解析出错,抛出异常
                    continuation.resumeWithException(e)
                }
            },
            { error ->
                // 网络请求失败,抛出异常
                continuation.resumeWithException(error)
            }
        )

        queue.add(stringRequest)
        
        // 协程取消时同步取消Volley请求,避免内存泄漏
        continuation.invokeOnCancellation {
            stringRequest.cancel()
        }
    }
}

调用示例(ViewModel场景)

fun fetchPokemon(name: String) {
    viewModelScope.launch {
        try {
            // 直接同步写法拿到异步结果,完全不需要回调
            val pokemon = pokeClient.getPokemonData(getApplication(), name)
            pokemonLiveData.postValue(pokemon)
        } catch (e: Exception) {
            // 统一处理所有异常:请求失败、解析失败、图片下载失败都可以在这里捕获
            Toast.makeText(getApplication(), "error talking to professor Oak!", Toast.LENGTH_SHORT).show()
        }
    }
}

内容的提问来源于stack exchange,提问作者FailedUnitTest

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 08:18:02