Retrofit Service

@Streaming
@GET
fun downloadFile(@Url fileUrl: String): Call<ResponseBody>

这里不能用suspend关键字

  • @Streaming 注解: 这个注解告知 Retrofit 不应将整个响应主体加载到内存中。它用于处理大文件或者流式数据,允许以流的方式逐段处理响应数据而不是一次性加载全部到内存中。它通常在下载大文件或者处理视频流等情况下使用。
  • suspend 关键字: 这是 Kotlin 协程的一部分,用于标记函数可以挂起执行。它允许在协程中使用挂起函数,使得异步操作更加方便和易于处理。

因为它们的作用和机制不同,@Streaming 注解主要是为了告知 Retrofit 应该以流的方式处理响应数据,而 suspend 关键字用于标记可以挂起执行的函数。@Streaming 注解主要作用于 Retrofit 处理响应体的方式,而 suspend 关键字主要用于在协程中处理异步操作。

在 Retrofit 中,@Streaming 注解通常与 Call<ResponseBody> 结合使用,因为它们允许以流式方式处理响应体。而 suspend 关键字用于将 Retrofit 方法转换为可以在协程中使用的挂起函数,但由于其异步性质,与 @Streaming 注解在技术上并不兼容。

Retrofit创建

fun create(): FileDownloadService {
val okHttpClient = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl("http://218.77.59.2:6203/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
return retrofit.create(FileDownloadService::class.java)
}

ViewModel中使用

fun download() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = withContext(Dispatchers.IO) {
fileDownloadService.downloadFile(MainActivity.DOWNLOAD_URL).execute()
}

if (response.isSuccessful) {
val body = response.body()
if (body != null) {
val url = response.raw().request().url().toString()
val fileName = getFileNameFromUrl(url)
saveFile(body, fileName)
}
} else {
// 文件下载失败处理
LogUtils.d("文件处理失败: ${response.isSuccessful}}")
}
}catch (e: Exception) {
e.printStackTrace()
}
}
}

private fun saveFile(body: ResponseBody,fileName: String?) {
var inputStream: InputStream? = null
var outputStream: OutputStream? = null

try {
val fileReader = ByteArray(4096)
val fileSize = body.contentLength()
val fileSizeDownloaded: Long = 0
inputStream = body.byteStream()
val file = File(application.filesDir,fileName?:"a.apk")
LogUtils.d("保存文件位置: ${file.absolutePath}")
outputStream = FileOutputStream(file) // 替换成你想要保存文件的路径


while (true) {
val read = inputStream.read(fileReader)
if (read == -1) {
break
}
outputStream.write(fileReader, 0, read)
}
outputStream.flush()

val fileMD5 = FileUtils.getFileMD5ToString(file)
LogUtils.d("保存成功,${fileMD5}")
} catch (e: IOException) {
e.printStackTrace()
} finally {
inputStream?.close()
outputStream?.close()
}
}

private fun getFileNameFromUrl(url: String): String? {
var fileName: String? = null
val lastPathSegment = Uri.parse(url).lastPathSegment
if (!lastPathSegment.isNullOrEmpty()) {
fileName = lastPathSegment
}
return fileName
}