Android 使用 OkHttp 和协程下载多个文件

Android download multiple files with OkHttp and coroutine

在我的应用程序中,我从 api 获得了一些图像的一组 URL,并且需要从这些 URL 创建 Bitmap 对象才能在 [=] 中显示图像28=]。我看到 android 文档建议使用协程来执行此类异步任务,但我不确定如何正确执行。

为我的 http 客户端使用 OkHttp,我尝试了以下方法:

GlobalScope.launch {
                    val gson = Gson();
                    val parsedRes = gson.fromJson(
                        response.body?.charStream(),
                        Array<GoodreadsBook>::class.java
                    );
                    // Create the bitmap from the imageUrl
                    for (i in 0 until parsedRes.size) {
                        val bitmap =
                            GlobalScope.async { createBitmapFromUrl(parsedRes[i].best_book.image_url) }
                        parsedRes[i].best_book.imageBitmap = bitmap.await();
                    }
                   searchResults.postValue(parsedRes)
                }

其中 response 是我从 API 返回的内容,searchResults 是保存已解析响应的 LiveData。 此外,这是我从这些网址获取图像的方式:

suspend fun createBitmapFromUrl(url: String): Bitmap? {
    val client = OkHttpClient();
    val req = Request.Builder().url(url).build();
    val res = client.newCall(req).execute();
    return BitmapFactory.decodeStream(res.body?.byteStream())
}

即使每个获取操作都是在单独的协程上完成的,它仍然太慢了。有更好的方法吗?我可以使用任何其他的 http 客户端,如果有一个针对协同程序进行了优化的话,虽然我是 Kotlin 的新手所以我什么都不知道。

首先 createBitmapFromUrl(url: String) 同步执行所有操作,你必须首先阻止它们阻塞协程线程,你可能需要使用 Dispatchers.IO 因为回调不是最协程中惯用的东西。

val client = OkHttpClient()  // preinitialize the client

suspend fun createBitmapFromUrl(url: String): Bitmap? = withContext(Dispatchers.IO) {
    val req = Request.Builder().url(url).build()
    val res = client.newCall(req).execute()
    BitmapFactory.decodeStream(res.body?.byteStream())
}

现在,当您调用 bitmap.await() 时,您只是在说“嘿,等待延迟的 bitmap,一旦它完成,就恢复循环以进行下一次迭代”

因此您可能希望在协程本身中进行赋值以阻止它挂起循环,否则为此创建另一个循环。我会选择第一个选项。

scope.launch {
    val gson = Gson();
    val parsedRes = gson.fromJson(
        response.body?.charStream(),
        Array<GoodreadsBook>::class.java
    );
    // Create the bitmap from the imageUrl
    for (i in 0 until parsedRes.size) {
        launch {
            parsedRes[i].best_book.imageBitmap = createBitmapFromUrl(parsedRes[i].best_book.image_url)
        }
    }
}

使用像下面这样的库,它不使用阻塞 execute 方法,而是从异步 enqueue.

桥接

https://github.com/gildor/kotlin-coroutines-okhttp

suspend fun main() {
    // Do call and await() for result from any suspend function
    val result = client.newCall(request).await()
    println("${result.code()}: ${result.message()}")
}

这基本上是做以下事情

public suspend fun Call.await(): Response {
    return suspendCancellableCoroutine { continuation ->
        enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                continuation.resume(response)
            }

            override fun onFailure(call: Call, e: IOException) {
                if (continuation.isCancelled) return
                continuation.resumeWithException(e)
            }
        })

        continuation.invokeOnCancellation {
            try {
                cancel()
            } catch (ex: Throwable) {
                //Ignore cancel exception
            }
        }
    }
}