【问题标题】:Android download multiple files with OkHttp and coroutineAndroid 使用 OkHttp 和协程下载多个文件
【发布时间】:2020-10-26 15:23:27
【问题描述】:

在我的应用程序中,我从 api 获得了一组指向某些图像的 url,并且需要从这些 url 中创建 Bitmap 对象才能在 UI 中显示图像。我看到 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())
}

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

【问题讨论】:

  • 首先您应该确定缓慢的原因,这很可能在这段代码之外。是服务器吗?您的互联网连接?
  • bitmap.await() 你已经在循环中等待,这将暂停循环直到延迟完成。
  • 考虑使用 Picasso 或 Glide 或 Coil,他们会为您下载并缓存图像。
  • @Thomas,我在模拟器中运行我的应用程序,因此可能会导致网络速度变慢。运行分析器表明,实际上网络请求本身需要一些时间,但我的实现运行速度比 Animesh Sahu 提出的要慢

标签: android kotlin okhttp kotlin-coroutines


【解决方案1】:

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

【讨论】:

  • 我无法从 withContext 块返回值
  • @AdrianPascu 啊抱歉,只是删除 return 关键字,lambda 中的最后一条语句会自动返回 :) 修复它。否则,您可以使用 return@withContext &lt;return value&gt; 之类的合格返回
  • 哦,是的,这是一个 lambda。仍然不习惯这种块语法。我试试看
  • 我不得不编辑我的帖子,所以如果你能再看一遍,那就太好了。用我的方法这无关紧要,但是在 for 循环之后,在更新每个元素之后,我修改了一个包含整个数组的 LiveData 对象。使用您现在建议的方法,更新 LiveData 是不可能的,因为数组中的更改不会立即反映。有没有办法也实现这个?其他语言可以让你链接异步任务,但我在 Kotlin 中找不到类似的东西。
  • @AdrianPascu 在作用域中启动的外部协程将返回一个作业,该作业将等待其所有子进程完成。您可以创建另一个协程,然后执行job.join()(挂起调用),当所有值都设置在数组中时,该协程将恢复。然后您可以更新您的实时数据。
【解决方案2】:

使用如下库,它不使用阻塞 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
            }
        }
    }
}

【讨论】:

  • 这个看profiler好像快了一点,但是区别不明显
  • 当然。但是使用线程更有效。执行解决方案没有问题。
猜你喜欢
  • 1970-01-01
  • 2016-09-12
  • 2022-01-10
  • 2017-05-27
  • 2015-07-08
  • 1970-01-01
  • 2019-11-20
  • 2021-04-07
  • 2016-12-21
相关资源
最近更新 更多