如何使用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
相关产品推荐
相关产品推荐

