【发布时间】:2020-12-14 17:47:07
【问题描述】:
我正在尝试构建一个库,允许应用程序下载我提供的 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
}
}
这分崩离析。所以出现的两种情况是:
-
我按原样运行此代码:文件已下载,大约 75% 的图像在应用程序因
java.io.IOException: unexpected end of stream on the url而崩溃之前已下载。所以看起来图像在文件完全写入之前就开始下载了。 -
我在没有图像代码的情况下运行了一次应用程序。文件已下载。我注释掉文件下载代码,取消注释掉图片下载代码。图像已下载,该应用程序按我的意愿运行。这向我表明,如果第一个协程实际上在第二个协程开始之前完成,它将起作用。
我已经尽可能多地编写和重写了这段代码,但我无法让它运行而不发生文件写入和图像下载都成功完成。
试图让这些协程连续完成我做错了什么?
【问题讨论】:
标签: kotlin kotlin-coroutines ktor