В настоящее время у меня есть сервер gRP C, который отправляет фрагменты видеофайла. Мое приложение android, написанное на Kotlin, использует сопрограммы для обновления пользовательского интерфейса (в Dispatchers.MAIN) и для обработки однонаправленного потока чанков (в Dispatchers.IO). Например:
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-запросы, и поэтому я не могу использовать мою заглушку gRP C (я могу ошибаться, пожалуйста, сообщите мне, если так). Я также проверил WorkManager, и вот та же проблема, я не знаю, является ли это правильным способом обработки этого случая.
Итак, здесь есть два вопроса:
- Есть ли способ обрабатывать запросы gRP C в чистом виде, что означает, что я могу теперь, когда он запускается, заканчивается, не удается и что я могу отменить должным образом?
- Если нет, есть ли лучший способ использовать сопрограммы для этого?
РЕДАКТИРОВАТЬ
Для тех, кто заинтересован, я думаю, что я придумал фиктивный алгоритм загрузки при обновлении индикатора выполнения (открытый для улучшений):
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 !")
}
}
}
}
}
Для получения дополнительной информации, пожалуйста, проверьте: Введение в сопрограммы
РЕДАКТИРОВАТЬ 2
Как предложено в комментариях, мы могли бы фактически использовать потоки для обработки этого, и это будет дать что-то вроде:
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)
}
}
}
}
}