如何解决如何确保Kotlin协程以正确的顺序完成?
我正在尝试建立一个允许应用程序下载我提供的json文件的库,然后根据其内容从网络下载图像。到目前为止,我已经与Kotlin Coroutines以及Ktor一起实现了该功能,但是我遇到的一个问题是我无法理解该做什么。
这是我用来定义每个图像的数据类:
data class ListImage(val name: String,val url: String)
用户调用init
函数,该函数将下载新的json文件。下载该文件后,应用程序需要使用getImages
下载该文件定义的许多图像。然后使用数据类和适配器填充列表。
这是我用来提取文件的代码:
fun init(context: Context,url: String): Boolean {
return runBlocking {
return@runBlocking fetchJsonData(context,url)
}
}
private suspend fun fetchJsonData(context: Context,url: String): Boolean {
return runBlocking {
val client: HttpClient(OkHttp) {
install(JsonFeature) {}
}
val data = async {
client.get<String>(url)
}
try {
val json = data.await()
withContext(Dispatchers.IO) {
context
.openFileOutput("imageFile.json",Context.MODE_PRIVATE)
.use { it.write(json.toByteArray()) }
}
} catch (e: Exception) {
return@runBlocking false
}
}
}
这有效并获取本地写入的文件。然后,我必须根据文件的内容获取图像。
suspend fun getImages(context: Context) {
val client = HttpClient(OkHttp)
// Gets the image list from the json file
val imageList = getImageList(context)
for (image in imageList) {
val imageName = image.name
val imageUrl = image.url
runBlocking {
client.downloadFile(context,imageName,imageUrl)
.collect { download ->
if (download == Downloader.Success) {
Log.e("SDK Image Downloader","Successfully downloaded $imageName.")
} else {
Log.i("SDK Image Downloader","Failed to download $imageName.")
}
}
}
}
}
private suspend fun HttpClient
.downloadFile(context: Context,fileName: String,url: String): Flow<Downloader> {
return flow {
val response = this@downloadFile.request<HttpResponse> {
url(url)
method = HttpMethod.Get
}
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data,offset,data.size)
offset += currentRead
} while (currentRead > 0)
if (response.status.isSuccess()) {
withContext(Dispatchers.IO) {
val dataPath =
"${context.filesDir.absolutePath}${File.separator}${fileName}"
File(dataPath).writeBytes(data)
}
emit(Downloader.Success)
} else {
emit(Downloader.Error("Error downloading image $fileName"))
}
}
}
如果该文件已经在设备上,并且我没有尝试重新下载它,那么它也可以工作。问题是当我第一次运行该应用程序时,我尝试先获取文件,然后获取图像。这是一个我如何称呼它的例子:
lateinit var loaded: Deferred<Boolean>
lateinit var imagesLoaded: Deferred<Unit>
@InternalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val context: Context = this
loaded = GlobalScope.async(Dispatchers.Default) {
init(context)
}
GlobalScope.launch { loaded.await() }
imagesLoaded = GlobalScope.async(Dispatchers.Default) {
getDeviceImages(context)
}
GlobalScope.launch { imagesLoaded.await() }
configureImageList(getImageList(context))
}
fun configureImageList(imageList: MutableList<Image>) {
val imageListAdapter = ImageListAdapter(this,imageList)
with(image_list) {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
itemAnimator = null
adapter = imageListAdapter
}
}
这崩溃了。因此,出现的两种情况是:
-
我按原样运行此代码:在应用程序因
java.io.IOException: unexpected end of stream on the url
崩溃之前,已下载文件,并下载了约75%的图像。因此,似乎在文件完全写入之前就开始下载图像。 -
我在没有图像代码的情况下运行了一次应用程序。已下载文件。我注释掉了文件下载代码,而取消注释了图像下载代码。图像已下载,该应用程序可以根据需要运行。这向我表明,如果第一个协程实际上在第二个协程开始之前就完成了,那就行得通。
我已经用尽可能多的方式编写和重写了此代码,但是我不能在成功完成文件写入和图像下载的过程中使它运行。
试图使这些协程连续完成,我在做错什么?
解决方法
在绊倒这个question之后,我才弄清楚了。看来我的HttpClient
对象正在共享一个连接而不是创建一个新的对象,并且当服务器关闭该连接时,它导致运行中的操作意外结束。因此解决方案是:
val client = HttpClient(OkHttp) {
defaultRequest {
header("Connection","close")
}
}
我将此添加到了每个Ktor客户端调用中,现在可以正常使用了。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。