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
}
}
}
}
在我的应用程序中,我从 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
}
}
}
}