在Android上使用gRPC处理文件下载 [英] Handling file download with gRPC on Android

查看:257
本文介绍了在Android上使用gRPC处理文件下载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前有一个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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆