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)
        }
    }
}
相关推荐
爱笑鱼4 小时前
Binder(八):远端进程死了,BinderProxy 为什么还能收到通知?
android
Android-Flutter4 小时前
Kotlin 冷流与热流详解
android·kotlin
zhangphil5 小时前
Android ContentProvider/ContentResolver读图片,跨进程 IPC慢
android
yueqc15 小时前
Android .so 文件压缩
android·so·包体积
龚礼鹏5 小时前
深入解析 Android Automotive (AAOS) 启动流程与 CarService 核心机制(基于 Android 16 最新源码视角)
android
rosmis6 小时前
agent各指标定义
android·java·开发语言
风样滴男人哟7 小时前
PHP特性之反射类ReflectionClass机制
android·开发语言·php
小孔龙8 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
qizayaoshuap8 小时前
鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
android·华为·harmonyos
朝星8 小时前
Framework筑基之Handler消息机制5(应用层)
android