使用 Retrofit 队列下载

Queue download using Retrofit

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

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

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

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

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)
}
}

这是我的端点接口 class:

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 class.

来管理自己的下载状态

您可以使用 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
  2. 调用 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 并处理 worker 结果
    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 的单例变量。这是我所做的:

在我的下载 class 中,我创建了 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 个视频,并对所有其他下载请求进行排队。

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