【问题标题】:async coroutine with volley带有凌空抽射的异步协程
【发布时间】:2021-07-14 16:07:30
【问题描述】:

我正在尝试将我的宠物应用程序转换为使用协程而不是回调。 我已经完成了一半,但我看不到如何绕过这个函数中的回调。有没有办法使用异步来摆脱回调或者我爬错了树?

这是我目前所拥有的:

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

class PokeClient {
    fun getPokemonData(context: Context, successCallBack: (Pokemon) -> Unit, pokemonName: String) = runBlocking {
    val queue = Volley.newRequestQueue(context)
    val request = url.plus(pokemonName)

    var deferredResult = async {
        val stringRequest = StringRequest(Request.Method.GET, request, Response.Listener<String> { response ->
            val jObj = JSONObject(response)
            val imgUrl = jObj
                .getJSONObject("sprites")
                .getJSONObject("other")
                .getJSONObject("official-artwork")
                .getString("front_default")

            val inputStream = URL(imgUrl).openStream()
            successCallBack(Pokemon(name = jObj.getString("name"), image = BitmapFactory.decodeStream(inputStream)))
        }, Response.ErrorListener {
            val toast = Toast.makeText(context, "error talking to professor Oak!", Toast.LENGTH_SHORT)
            toast.show()
        })

        queue.add(stringRequest)
        }

        deferredResult.await()
    }
}

有什么想法吗?

谢谢你, 安卓新手

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    本质上您需要将带有回调代码块的网络调用转换为可以从任何协程调用的挂起函数,这可以使用suspendCoroutine函数来完成,它基本上为您提供了一个延续对象,可用于在您的案例中从响应回调中返回数据

    suspend fun getPokemon() = suspendCoroutine<Pokemon> { cont ->
        val queue = Volley.newRequestQueue(this)
        val url = "https://pokeapi.co/api/v2/pokemon/"
    
        val stringRequest = StringRequest(Request.Method.GET, url,
            Response.Listener<Pokemon> { response ->   
                val jObj = JSONObject(response)
                val imgUrl = jObj.getJSONObject("sprites")
                    .getJSONObject("other")
                    .getJSONObject("official-artwork")
                    .getString("front_default")
    
                val inputStream = URL(imgUrl).openStream()
                /* call continuation.resume and pass your object */
                cont.resume(Pokemon(name = jObj.getString("name"), image = BitmapFactory.decodeStream(inputStream)))      
            },
            Response.ErrorListener { 
                /* if network call fails then post appropriate error */
                cont.resumeWithException(YourExceptoin) 
            })
        queue.add(stringRequest)
    }
    

    现在您可以从协程中调用此function 并获得Pokemon,如下所示

    runBlocking{
      try { val pokeMon = getPokemon() }
      catch(e: Exception) { Log.d(TAG, "Cant get pokemon") }
    }
    

    注意:可以使用runBlocking 仅用于学习和探索,否则不是一个好主意,请使用launchasync

    编辑:如评论中所述,如果您需要支持取消(您应该支持结构化并发),也可以使用 suspendCancellableCoroutine

    【讨论】:

    • 使用suspendCancellableCoroutine,您还可以支持取消,因此如果协程被取消,请求不会不必要地继续。
    • @mightyWOZ 感谢您的优质回复。我的代码看起来好多了。但是,您说不要使用 runBlocking。如何在我的同步 MainActivity.kt 中避免这种情况?当前调用此 getPokemon() 函数。
    • 我在其他地方了解到 runBlocking 充当了桥梁,但它必须在某个地方发生?我错了吗?
    • 尽快忘掉桥接的东西,只记得一件事,runBlocking 永远不应该在生产代码中使用,它打败了整体协程的目的,因为它阻塞了调用线程
    • Don't use GlobalScope either. 使用lifecycleScope.launch。但最好在 ViewModel 中执行此操作并使用viewModelScope.launch,这样您的请求就不必在每次屏幕旋转时重新启动。协程可以调用您的挂起函数并将结果放在 LiveData 或 SharedFlow 中以供 Activity 观察。
    猜你喜欢
    • 2015-10-30
    • 1970-01-01
    • 2018-01-07
    • 1970-01-01
    • 2018-07-09
    • 1970-01-01
    • 2021-08-29
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多