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)
        }
    }
}
相关推荐
BLUcoding7 分钟前
Android 生命周期详解
android
Swift社区8 分钟前
鸿蒙 vs iOS / Android:谁更适合 AI?
android·ios·harmonyos
冬奇Lab28 分钟前
硬件加速与 OMX/Codec2:解密编解码器的底层世界
android·音视频开发·视频编码
亘元有量-流量变现44 分钟前
ASO优化全流程实操指南:从基础到迭代,精准提升App曝光与转化
android·ios·harmonyos·aso优化·方糖试玩
私人珍藏库1 小时前
【Android】GameNative 0.9.0 [特殊字符] 手机畅玩Steam游戏
android·游戏·智能手机·app·工具·软件·多功能
诸神黄昏EX2 小时前
Android Safety 系列专题【篇七:Android AVF机制】
android
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.2 小时前
MySQL 主从架构中的使用技巧及优化
android·mysql·架构
羊小蜜.2 小时前
Mysql 11: 存储过程全解——从创建到使用
android·数据库·mysql·存储过程
zh_xuan2 小时前
Android compose和传统view混用
android
大黄说说2 小时前
MySQL索引失效的常见场景有哪些?如何通过EXPLAIN分析查询性能?
android·adb