Android 根据Url使用Retrofit框架进行文件下载

一、背景

根据后端返回的url下载地址,去执行文件下载,将文件保存到SD卡。这里使用Retrofit网络框架。

二、代码实现

2.1、定义一个DownloadFileService
Kotlin 复制代码
interface DownloadFileService {
    @Streaming
    @GET
    suspend fun downloadFile(@Url fileUrl: String):ResponseBody
}
2.2、定义一个FileDownloadClient
Kotlin 复制代码
private var mDownliadService: DownloadFileService? = null

    fun getUploadFileService() :DownloadFileService{
        if(mDownliadService==null){
            val okHttpClient = OkHttpClient.Builder()
                .callTimeout(0, TimeUnit.SECONDS)//0 代表不考虑请求的超时 这里的超时是指整个请求过程的超时,如果设置太短,任务还没执行完,会以超时结束任务,这里尤其要注意
                .connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)// 重连
                .followRedirects(false)// 重定向
                //.addInterceptor(RequestInterceptor(authorization,requestId,offset,uploadType))
                //.addInterceptor(RemoveContentLengthInterceptor())
                //.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                .build()

            val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl(DeviceUtil.getInstance().getServerIp())
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build()
            mDownliadService= retrofit.create(DownloadFileService::class.java)
       }

        return mDownliadService!!
    }
2.3、定义一个DownloadCallback
Kotlin 复制代码
interface DownloadCallback {
    fun onSuccess(srcApkTarPath:String)
    fun onProgress(progress: Int)
    fun onFailure(msg:String?)
}
2.4、定义一个FileDownloadManager,执行文件下载
Kotlin 复制代码
class FileDownloadManager {

    private val TAG = "FileDownloadManager_"

    companion object {
        private var singleInstance: FileDownloadManager? = null
            get() {
                // 懒汉模式
                if (null == field) {
                    field = FileDownloadManager()
                }
                return field
            }

        @Synchronized // 添加注解,线程同步,线程安全
        fun getInstance(): FileDownloadManager {
            return singleInstance!! // 表示非空时执行
        }
    }


    fun doDownloadFile(
        appName:String,
        packageName: String,
        downUrl: String,
        fileSize: Long,
        fileMd5:String,
        downloadCallback: DownloadCallback
    ) {

        try {
            val coroutineScope = CoroutineScope(Dispatchers.Default)
            coroutineScope.launch(Dispatchers.IO) {
                val response = FileDownloadClient.getUploadFileService().downloadFile(downUrl)
                val length = response.contentLength()
                XLogUtil.d("${TAG}doDownloadFile result appName:$appName,,,packageName:$packageName,,,length:$length,,,fileSize:$fileSize,,,downUrl:$downUrl")

                if (length > 0) {
                    val folder = File(ConstantUtil.DOWNLOAD_FOLDER_PATH)
                    if (!folder.exists()) {
                        folder.mkdirs()
                    }

                    // 使用输入流保存响应体到文件
                    val inputStream = response.byteStream()
                    val fileName = "$packageName.tar"

                    var fileTargetPath =
                        ConstantUtil.DOWNLOAD_FOLDER_PATH + File.separator + fileName

                    val outputStream = FileOutputStream(fileTargetPath)

                    val buf = ByteArray(1024)
                    var downLoadFileSize = 0
                    var lastProgress = 0
                    do {
                        val numread = inputStream.read(buf)
                        if (numread == -1) {
                            break
                        }
                        outputStream.write(buf, 0, numread)
                        downLoadFileSize += numread

                        val progressValue =
                            ((downLoadFileSize * 100f) / length).toInt()

                          //相同的进度值只回调一次
                        if (progressValue > lastProgress) {
                            lastProgress = progressValue
                            downloadCallback?.apply {
                                onProgress(progressValue)
                            }
                        }
                    } while (true)


                    outputStream.flush()
                    // 关闭文件输出流和输入流
                    outputStream.close()
                    //inputStream.close()
                    val localFileMd5=FileUtil.getInstance().getFileMd5(fileTargetPath)
                    if(localFileMd5==fileMd5){
                        downloadCallback?.onSuccess(fileTargetPath)
                    }else{
                        downloadCallback?.onFailure("文件 md5 不一致,localFileMd5:$localFileMd5,,,fileMd5:$fileMd5")
                    }
                }else{
                    downloadCallback?.onFailure("读取文件len=0")
                }
            }
        } catch (e: Exception) {
            XLogUtil.e("${TAG}doDownloadFile Exception appName:$appName,,,packageName:$packageName,,,Exception:${e.message},,,fileSize:$fileSize,,,downUrl:$downUrl")
            downloadCallback?.onFailure(e.message)
        }
    }
}
相关推荐
c***21293 小时前
Springboot3学习(5、Druid使用及配置)
android·学习
修炼者3 小时前
【Android 进阶】别再强转 Context 了!手把手教你优雅解耦 View 与 Activity
android·android studio
x***01064 小时前
SpringSecurity+jwt实现权限认证功能
android·前端·后端
程序员江同学4 小时前
线下活动|2025 Kotlin 中文开发者大会北京分会场
android·kotlin
李坤林5 小时前
Android Vulkan 开启VK_GOOGLE_DISPLAY_TIMING 后,一个vsync 会释放两个imageBuffer现象分析
android·vulkan
Jerry5 小时前
Compose 状态思维
android
k***45996 小时前
MySQL----case的用法
android·数据库·mysql
r***86987 小时前
Plugin ‘mysql_native_password‘ is not loaded`
android·数据库·mysql
v***59837 小时前
MySQL-mysql zip安装包配置教程
android·mysql·adb
不用89k8 小时前
Android无法区分USB摄像头是哪一个
android