在Android上使用gRPC处理文件下载 [英] Handling file download with gRPC on Android
问题描述
我目前有一个gRPC服务器,该服务器正在发送视频文件的块.我用Kotlin编写的android应用程序将协程用于UI更新(在Dispatchers.MAIN上)和用于处理单向数据块流(在Dispatchers.IO上).如下所示:
I currently have a gRPC server which is sending chunks of a video file. My android application written in Kotlin uses coroutines for UI updates (on Dispatchers.MAIN) and for handling a unidirectional stream of chunks (on Dispatchers.IO). Like the following:
GlobalScope.launch(Dispatchers.Main) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.DOWNLOADING) // MAKE PROGRESS BAR VISIBLE
GlobalScope.launch(Dispatchers.IO) {
stub.downloadVideo(request).forEach {
file.appendBytes(
it.data.toByteArray()
)
}
}.join()
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.FINISHED) // MAKE PROGRESS BAR DISAPPEAR
} catch (exception: Exception) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.ERROR) // MAKE PROGRESS BAR DISAPPEAR
screenNavigator.showError(exception) // SHOW DIALOG
}
}
这很好用,但是我想知道是否没有一种更清洁"的方式来处理下载.我已经知道DownloadManager,但是我觉得它只接受HTTP查询,因此我不能使用我的gRPC存根(我可能是错的,请告诉我是不是).我还检查了WorkManager,这是一个相同的问题,我不知道这是否是处理该案件的正确方法.
This works pretty well but I wonder if there is not a 'cleaner' way to handle downloads. I already know about DownloadManager but I feel like it only accepts HTTP queries and so I can't use my gRPC stub (I might be wrong, please tell me if so). I also checked WorkManager, and here is the same problem I do not know if this is the proper way of handling that case.
因此,这里有两个问题:
So, there are two questions here:
- 是否有一种以干净的方式处理gRPC查询的方法,这意味着我现在可以在启动,完成,失败以及可以正确取消它的时候了?
- 如果没有,是否有更好的方法来使用协程?
对于那些感兴趣的人,我相信我想出了一个虚拟算法,可以在更新进度条时进行下载(即兴创作):
For those interested, I believe I came up with a dummy algorithm for downloading while updating the progress bar (open to improvments):
suspend fun downloadVideo(callback: suspend (currentBytesRead: Int) -> Unit) {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
callback(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope { //NOTE: The scope is the current Fragment
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
downloadVideo { currentBytes ->
withContext(Dispatchers.Main) {
updateLoadingBar(currentBytes)
if (job.isCancelled)
println("cancelled !")
}
}
}
}
}
有关更多信息,请检查:介绍协程
For more info, please check: Introduction to coroutines
正如评论中所建议的,我们实际上可以使用Flows来处理此问题,并且它会给出类似的内容:
As proposed in comments we could actually use Flows to handle this and it would give something like:
suspend fun foo(): Flow<Int> = flow {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
emit(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
foo()
.onCompletion { cause -> println("Flow completed with $cause") }
.catch { e -> println("Caught $e") }
.collect { current ->
if (job.isCancelled)
return@collect
updateLoadingBar(current)
}
}
}
}
}
推荐答案
gRPC可能有很多事情,因此在这方面您的问题尚不清楚.最重要的是,它可以完全异步并基于回调,这意味着可以将其转换为可以在主线程上收集的Flow
.但是,文件写入受阻.
gRPC can be many things so in that respect your question is unclear. Most importantly, it can be fully async and callback-based, which would mean it can be turned into a Flow
that you can collect on the main thread. File writing, however, is blocking.
您的代码似乎在后台启动下载后立即发送了FINISHED
信号.您应该将launch(IO)
替换为withContext(IO)
.
Your code seems to send the FINISHED
signal right away, as soon as it has launched the download in the background. You should probably replace launch(IO)
with withContext(IO)
.
这篇关于在Android上使用gRPC处理文件下载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!