微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 Retrofit 进行队列下载

如何解决使用 Retrofit 进行队列下载

我正在尝试为我的 Android 应用创建一个队列管理器。

在我的应用中,我在 RecyclerView 中显示视频列表。当用户点击任何视频时,我会在设备上下载视频。下载本身运行良好,我什至可以同时下载多个视频并显示每次下载的下载进度。

问题: 我只想同时下载 3 个视频,并将所有其他下载放在队列中。

这是我的 Retrofit 服务生成器类:

object RetrofitInstance {

private val downloadRetrofit by lazy {
    val dispatcher = dispatcher()
    dispatcher.maxRequestsPerHost = 1
    dispatcher.maxRequests = 3

    val client = OkHttpClient
        .Builder()
        .dispatcher(dispatcher)
        .build()

    Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
}

val downloadApi: Endpoints by lazy {
    downloadRetrofit.create(Endpoints::class.java)
}
}

这是我的端点接口类:

interface Endpoints {

@GET
@Streaming
suspend fun downloadFile(@Url fileURL: String): Response<ResponseBody>
}

我使用 Kotlin 协程开始下载:

suspend fun startDownload(url: String,filePath: String) {
    val downloadService = RetrofitInstance.downloadApi.downloadFile(url)
    if (downloadService.isSuccessful) {
        saveFile(downloadService.body(),filePath)
    } else {
        // callback for error
    }
}

我还尝试通过使用 dispatcher(Executors.newFixedThreadPool(1)) 来减少 Retrofit 可以使用的线程数,但这也无济于事。它仍然会同时下载所有文件

任何帮助将不胜感激。谢谢!

编辑

忘了提一件事。我正在为 recyclerView 项目使用自定义视图。这些自定义视图通过直接调用 Download 类来管理自己的下载状态。

解决方法

您可以使用 CoroutineWorker 在后台线程下载视频并处理下载队列。

  1. 创建工作线程
class DownloadVideoWorker(
    private val context: Context,private val params: WorkerParameters,private val downloadApi: DownloadApi
) : CoroutineWorker(context,params) {

    override suspend fun doWork(): Result {
        val videos = inputData.getStringArray(VIDEOS)
        //Download videos
        return success()
    }

    companion object {
        const val VIDEOS: String = "VIDEOS"
    
        fun enqueue(videos: Array<String>): LiveData<WorkInfo> {
            val downloadWorker = OneTimeWorkRequestBuilder<DownloadVideoWorker>()
                .setInputData(Data.Builder().putStringArray(VIDEOS,videos).build())
                .build()
            val workManager = WorkManager.getInstance()
            workManager.enqueue(downloadWorker)
            return workManager.getWorkInfoByIdLiveData(downloadWorker.id)
        }
    }
}
  1. 在您的 viewModel 添加函数以从您的 Fragment/Activity 调用 worker
class DownloadViewModel() : ViewModel() {
    private var listOfVideos: Array<String> // Videos urls

    fun downloadVideos(): LiveData<WorkInfo> {
        val videosToDownload = retrieveNextThreeVideos()
        return DownloadVideoWorker.enqueue(videos)
    }
    
    fun retrieveNextThreeVideos(): Array<String> {
        if(listOfVideos.size >= 3) {
            val videosToDownload = listOfVideos.subList(0,3)
            videosToDownload.forEach { listOfVideos.remove(it) }
            return videosToDownload
        }
        return listOfVideos
    }
}

  1. 观察 LiveData 并处理工作结果
    fun downloadVideos() {
        documentsViewModel.downloadVideos().observe(this,Observer {
            when (it.state) {
                WorkInfo.State.SUCCEEDED -> {
                    downloadVideos()
                }
                WorkInfo.State.FAILED -> {
                    // Handle error result
                }
            }
        })
    }

注意:要了解有关 Coroutine Worker 的更多信息,请参阅:https://developer.android.com/topic/libraries/architecture/workmanager/advanced/coroutineworker

,

我终于能够实现它,但我仍然不确定这是否是最有效的方法。我使用了 ThreadPool 的单例变量。这是我所做的:

在我的下载类中,我创建了一个 ThreadPoolExecutor 的伴随对象:

companion object {
    private val executor: ThreadPoolExecutor = Executors.newFixedThreadPool(3) as ThreadPoolExecutor
}

然后我在 startDownload 函数中进行了以下更改:

fun startDownloading(url: String,filePath: String) {
    downloadUtilImp.downloadQueued()

    runBlocking {
        downloadJob = launch(executor.asCoroutineDispatcher()) {
            val downloadService = RetrofitInstance.api.downloadFile(url)
            if (downloadService.isSuccessful) saveFile(downloadService.body(),filePath)
            else downloadUtilImp.downloadFailed(downloadService.errorBody().toString())
        }
    }
}

此代码一次仅下载 3 个视频,并将所有其他下载请求排队。

如果有更好的方法,我仍然愿意接受建议。感谢您的帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。