首页 > 解决方案 > Android 使用 OkHttp 和协程下载多个文件

问题描述

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

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

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())
}

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

标签: androidkotlinokhttpkotlin-coroutines

解决方案


首先,所有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)
        }
    }
}

推荐阅读