如何在Kotlin协程中取消Retrofit发起的文件上传请求?
当然可以用Kotlin协程实现这个需求,而且完全不需要依赖RxJava——你可能只是没注意到Retrofit对协程的原生支持已经帮我们处理了请求取消的核心逻辑😉
核心原理:Retrofit + 协程的自动请求取消
Retrofit从2.6.0版本开始原生支持suspend函数,更重要的是:当协程被取消时,Retrofit会自动调用底层OkHttp请求的cancel()方法。这意味着你不需要手动处理协作式取消的细节——框架已经把协程取消和网络请求取消绑定在一起了,和RxJava里调用Disposable.dispose()的效果完全一致。
具体实现步骤
1. 定义支持协程的Retrofit接口
首先把你的上传接口改成suspend函数形式:
interface ApiService { @Multipart suspend fun uploadImage( @Part file: MultipartBody.Part ): Response<UploadResult> // UploadResult是你的后端返回模型 }
2. 管理上传协程的生命周期
你可以用Job对象来持有上传的协程,就像RxJava里的Disposable一样,在需要取消的时候调用cancel()即可。结合Android的viewModelScope(或者其他自定义CoroutineScope)还能自动处理生命周期,避免内存泄漏:
class UploadViewModel(private val apiService: ApiService) : ViewModel() { private var uploadJob: Job? = null // 开始上传 fun startUpload(file: File) { // 如果已有上传任务,先取消 uploadJob?.cancel() uploadJob = viewModelScope.launch { val requestBody = file.asRequestBody("image/*".toMediaType()) val filePart = MultipartBody.Part.createFormData("file", file.name, requestBody) try { val response = apiService.uploadImage(filePart) if (response.isSuccessful) { // 处理上传成功逻辑 } else { // 处理后端返回的错误 } } catch (e: CancellationException) { // 协程被取消(用户点击了取消按钮),这里可以提示用户上传终止 } catch (e: IOException) { // 处理网络异常(比如断网) } finally { uploadJob = null } } } // 取消上传 fun cancelUpload() { uploadJob?.cancel() uploadJob = null } // ViewModel销毁时自动取消未完成的上传 override fun onCleared() { super.onCleared() uploadJob?.cancel() } }
关于协程协作式取消的误区
你提到的“需要在循环或分块间隙调用挂起函数才能取消”,是针对自定义的耗时挂起任务(比如自己手动分块上传、循环处理数据等)。但在Retrofit的场景下,网络请求的执行是由OkHttp底层处理的,Retrofit已经在suspend函数内部实现了协程取消的协作逻辑——当协程被取消时,它会立即调用OkHttp请求的cancel()方法,终止网络连接,和RxJava的取消行为完全一致。
如果是你自己实现分块上传(比如把大文件分成多个小块逐个上传),那确实需要在每块上传前调用ensureActive()或者yield()来检查协程状态,确保能及时响应取消,但用Retrofit原生的multipart上传完全不需要这一步。
协程 vs RxJava:哪个更适合?
在这个场景下,协程的实现反而更简洁:
- 不需要引入RxJava的依赖,代码更轻量化
- 用
Job管理协程比Disposable更符合Kotlin的语法风格 - 结合
viewModelScope/lifecycleScope可以自动和Android生命周期绑定,减少内存泄漏风险
RxJava当然也能完成任务,但协程的方案更贴合Kotlin生态,代码可读性更高,没有额外的线程调度学习成本(协程的调度器更直观)。
内容的提问来源于stack exchange,提问作者Sergey

