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)
        }
    }
}
相关推荐
157092511341 小时前
Android进阶之光:HTTP协议原理深度解析
android·网络协议·http
终端安全笔记2 小时前
安卓五品牌通道差在哪:监管通道拆解
android
v_34836087623 小时前
Android native端 堆栈打印
android·算法
一笑的小酒馆3 小时前
AndroidKMP之导航和WebView
android
2501_932750266 小时前
Android BottomNavigationView 入门介绍
android
Godikov8 小时前
硬件异步事件下的业务一致性:智能柜领取/归还流程的竞态设计
android
Godikov8 小时前
不用 Netty:Android 手写 TCP 客户端实战——自定义协议帧、粘包处理与可靠重连
android
事圆则缓8 小时前
Java 面向对象如何落到 Android View 体系
android
iPad协议个微协议9 小时前
# 微信自动营销系统如何基于 WechatApi 做事件驱动设计
android·微信·企业微信·个人开发·wechatapi·微信个人号开发
AirDroid_cn10 小时前
隐私沙盒:第三方应用读取剪切板内容如何拦截?
android·gitee