实体类
data class DownloadInfo(
val packageName: String,//下载apk时是包名 下载rom时是fileId
val url: String,
val md5: String,
val size: Long,
val packageType: PackageType
)
接口类
interface IDownloadControl {
/**
* 开始下载
*/
fun startDownload(
mAndroidInfos: List<AndroidPackageInfo>,
apkInfos: List<ApkPackageInfo>,
listener: DownloadListener
)
/**
* 检测是否继续下载 用来判断上一次下载是否完成
*/
fun isContinueDownload(): Boolean
/**
* 停止下载任务
*/
fun stop()
}
整个下载类
class DownloadControl : IDownloadControl {
private val TAG = "DownloadControl"
private var mTimer: Timer? = null
private var mCurrentDownloadId: Long? = null//当前下载id
private var mCurrentDownloadPkgName: String? = null//当前下载的pkgName
private var mDownloadListener: DownloadListener? = null
private var mTotalDownloadSize = 0L
private var mCurrentDownloadSize = 0.0//当前下载大小
@Volatile
private var isCancelDownload=false
companion object {
private var mDownloadManager: DownloadManager =
OTA_SDK.application.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
private var mDefaultDestinationPath =
Environment.DIRECTORY_DOWNLOADS//存储地址path 默认/sdcard/Download 根据DownloadManager 下载机制是不能直接外部地址的
private var mDefaultPath = "sdcard/Download/"
private var mDownloadInfoList = mutableListOf<DownloadInfo>()
}
override fun startDownload(
mAndroidInfos: List<AndroidPackageInfo>,
apkInfos: List<ApkPackageInfo>,
listener: DownloadListener
) {
isCancelDownload=false
mDownloadInfoList.clear()
mAndroidInfos.forEach {
val item=DownloadInfo(it.fileId,it.url,it.md5,it.size,PackageType.ROM)
mDownloadInfoList.add(item)
}
apkInfos.forEach {
val item=DownloadInfo(it.packageName,it.url,it.md5,it.size,PackageType.APK)
mDownloadInfoList.add(item)
}
mTotalDownloadSize = getTotalSize()
mDownloadListener = listener
checkFinish()
}
override fun isContinueDownload(): Boolean {
return false
}
/**
* 检测 所有urls 是否都下载完了 下载完成一个移除一个
*/
private fun checkFinish() {
if (mDownloadInfoList.isEmpty()) {
LogUtil.d("checkFinish > urls is empty !", TAG)
mDownloadListener?.success()
return
}
download(mDownloadInfoList[0])
}
/**
* 获取总的 下载量
*/
private fun getTotalSize(): Long {
var totalSize = 0L
mDownloadInfoList.forEach {
totalSize += it.size
}
return totalSize
}
private fun download(info: DownloadInfo) {
mCurrentDownloadPkgName = info.packageName
LogUtil.d("current download package name is $mCurrentDownloadPkgName")
val uri = Uri.parse(info.url)
val fileName = File(uri.path).name
//获取当前已经相同名字的下载的文件
val destFile = DirectoryManager.getDownloadDirFile(fileName)
//下载验证 验证是否已经被下载过
val verifyResult = CommonUtils.verifyFileMd5(info.md5, destFile)
LogUtil.d("downloadRequest > md5 verify $verifyResult", TAG)
if (verifyResult) {
//md5 验证通过
//更新当前下载进度
mCurrentDownloadSize += info.size
val percent = getProgress(mTotalDownloadSize, mCurrentDownloadSize.toLong())
mDownloadListener?.progress(percent)
//移除当前已经下载完成的
mDownloadInfoList.removeAt(0)
//继续检查还有没有需要下载
UpdateDbManager.updatePath(
mCurrentDownloadPkgName!!,
destFile.absolutePath
) {
checkFinish()
}
} else {
//md5 验证不通过 重新下载
downloadRequest(uri, fileName)
}
}
/**
* 下载请求
*/
private fun downloadRequest(uri: Uri, fileName: String) {
LogUtil.d("downloadRequest > start new download", TAG)
val request = DownloadManager.Request(uri)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
request.setDestinationInExternalPublicDir(mDefaultDestinationPath, fileName)
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE)
request.setAllowedOverMetered(true)
request.setAllowedOverRoaming(true)
mCurrentDownloadId = mDownloadManager.enqueue(request)
startTimer()
}
/**
* 定时任务刷新 下载结果
*/
private fun startTimer() {
mTimer?.cancel()
mTimer = Timer()
mTimer?.schedule(object : TimerTask() {
override fun run() {
checkDownloadStatus()
}
}, 0, 500L)
}
/**
* 检测下载进度
*/
private fun checkDownloadStatus() {
if (isCancelDownload){
LogUtil.d("cancel download return",TAG)
return
}
if (mCurrentDownloadId == null) {
LogUtil.d("checkDownloadStatus > download fail , downloadId is null", TAG)
mDownloadListener?.fail("")
return
}
val query = DownloadManager.Query().setFilterById(mCurrentDownloadId!!)
val cursor = mDownloadManager.query(query)
if (cursor.moveToFirst()) {
//下载状态
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
var reason = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
val localUri =
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val itemBytesDownload =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val itemTotalSize =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
cursor.close()
reason = if ("placeholder".equals(reason)) "" else reason
LogUtil.d("checkDownloadStatus > download status:$status, reason:$reason", TAG)
when (status) {
DownloadManager.STATUS_RUNNING -> {
// 下载中
val itemPercent = getProgress(itemTotalSize, itemBytesDownload)
LogUtil.d("itemPercent > $itemPercent $localUri", TAG)
val itemDownload = mCurrentDownloadSize + itemBytesDownload
val percent = getProgress(mTotalDownloadSize, itemDownload.toLong())
mDownloadListener?.progress(percent)
}
DownloadManager.STATUS_PAUSED -> {
// 下载暂停
mDownloadListener?.paused()
}
DownloadManager.STATUS_SUCCESSFUL -> {
// 下载完成
LogUtil.d("checkDownloadStatus > download success $localUri", TAG)
mTimer?.cancel()
//下载完成 判断目标文件是否需要 移动到目标文件
val sourceFile = File(Uri.parse(localUri).path)
val destFile = DirectoryManager.getDownloadDirFile(sourceFile.name)
//复制到目标文件
copyFile(sourceFile, destFile)
val destFileAbsolutePath=destFile.absolutePath
//删除复制前下载的文件 保留复制文件
cleanDefaultDownload()
//下载成功一个移除一个 然后刷新保存LastDownloadInfo
mCurrentDownloadSize += mDownloadInfoList[0].size
mDownloadInfoList.removeAt(0)
UpdateDbManager.updatePath(
mCurrentDownloadPkgName!!,
destFileAbsolutePath
) {
checkFinish()
}
}
DownloadManager.STATUS_FAILED -> {
// 下载失败
LogUtil.d("checkDownloadStatus > download fail $localUri", TAG)
mTimer?.cancel()
mDownloadListener?.fail(reason)
}
// ERROR_UNKNOWN -> {
// //COLUMN_ERROR_CODE的值,当下载完成时出现不适合任何其他错误代码的错误。
// }
// ERROR_FILE_ERROR -> {
// //当出现不适合任何其他错误代码的存储问题时
// }
// ERROR_UNHANDLED_HTTP_CODE->{
// //收到下载管理器无法处理的HTTP代码时COLUMN_REASON的值。
// }
// ERROR_HTTP_DATA_ERROR->{
// //在HTTP级别接收或处理数据时发生错误时
// }
// ERROR_TOO_MANY_REDIRECTS->{
// //重定向过多时COLUMN_REASON的值。
// }
// ERROR_INSUFFICIENT_SPACE->{
// //存储空间不足时COLUMN_REASON的值。通常,这是因为SD卡已满。
// }
// ERROR_DEVICE_NOT_FOUND->{
// //未找到外部存储设备时COLUMN_REASON的值。通常,这是因为未安装SD卡。
// }
// ERROR_CANNOT_RESUME->{
// //COLUMN_REASON的值,此时可能发生了一些暂时性错误,但我们无法恢复下载。
// }
// ERROR_FILE_ALREADY_EXISTS->{
// //请求的目标文件已存在时COLUMN_REASON的值(下载管理器不会覆盖现有文件)。
// }
// PAUSED_WAITING_TO_RETRY -> {
// //COLUMN_REASON的值,当由于发生某些网络错误而暂停下载,并且下载管理器正在等待重试请求时
// }
// PAUSED_WAITING_FOR_NETWORK ->{
// //当下载正在等待网络连接进行时,COLUMN_REASON的值。
// }
// PAUSED_QUEUED_FOR_WIFI ->{
// //当下载超过移动网络上下载的大小限制并且下载管理器正在等待Wi-Fi连接进行时,COLUMN_REASON的值。
// }
// PAUSED_UNKNOWN ->{
// //由于其他原因暂停下载时COLUMN_REASON的值。
// }
else -> {
LogUtil.d("other status $status,$reason", TAG)
}
}
}
}
private fun delete(sourceFile: File){
if (sourceFile.exists()) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Files.delete(sourceFile.toPath())
}
LogUtil.d("File deleted successfully", TAG)
} catch (e: IOException) {
e.printStackTrace()
LogUtil.e("${Log.getStackTraceString(e)}",TAG)
}
} else {
LogUtil.d("File does not exist", TAG)
}
}
/**
* 将文件sourceFile 复制到destFile
*/
private fun copyFile(sourceFile: File, destFile: File) {
val inputStream = FileInputStream(sourceFile)
val outputStream = FileOutputStream(destFile)
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
}
inputStream.close()
outputStream.close()
}
private fun getProgress(totalSize: Long, currentSize: Long): Double {
try {
if (totalSize <= 0) {
LogUtil.d(" getProgress total size is : $totalSize", TAG)
return 0.0
}
var progress = (currentSize / totalSize.toDouble())
progress = if (progress > 1.0) 1.0 else if (progress < 0.0) 0.0 else progress
return BigDecimal(progress).setScale(2, BigDecimal.ROUND_HALF_UP).toDouble()
} catch (e: Exception) {
e.printStackTrace()
return 0.0
}
}
private fun cleanDefaultDownload() {
File(mDefaultPath).apply {
val deleteSuccess = this.deleteRecursively()
LogUtil.d("delete sdcard/Download success $deleteSuccess", TAG)
if (!deleteSuccess) {
ShellCmdUtil.execCommand("rm -r ${this.absolutePath}", true).apply {
LogUtil.d(
" shell cmd delete sdcard/Download file ${this?.first} ${this?.second}",
TAG
)
}
}
}
}
override fun stop() {
isCancelDownload=true
mTimer?.cancel()
mCurrentDownloadId?.let {
try {
//先取消下载
mDownloadManager.remove(it)
}catch (e:Exception){
e.printStackTrace()
LogUtil.e("${Log.getStackTraceString(e)}",TAG)
}
}
//删除复制前下载的文件 保留复制文件
cleanDefaultDownload()
MemoryManager.cleanDownloadPackage()
}
}