一、背景
根据后端返回的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)
}
}
}