385 lines
11 KiB
Kotlin
385 lines
11 KiB
Kotlin
package com.img.rabbit.utils
|
|
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.util.Log
|
|
import okhttp3.*
|
|
import okhttp3.Headers.Companion.toHeaders
|
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
import java.io.*
|
|
import java.net.URL
|
|
import java.util.concurrent.CancellationException
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
|
|
class DownLoadUtils private constructor() {
|
|
|
|
companion object {
|
|
private const val TAG = "DownLoadUtils"
|
|
private val downLoadHttpUtils: DownLoadUtils by lazy {
|
|
DownLoadUtils()
|
|
}
|
|
|
|
@JvmStatic
|
|
@Synchronized
|
|
fun getInstance(): DownLoadUtils {
|
|
return downLoadHttpUtils
|
|
}
|
|
}
|
|
|
|
private var downloadSizeInfo = mutableMapOf<String, Long>()
|
|
private var cancelledList = mutableListOf<String>()
|
|
|
|
private var buffSize = 4096//建议设置为2048
|
|
fun setBuffSize(size: Int): DownLoadUtils {
|
|
this.buffSize = size
|
|
return this
|
|
}
|
|
|
|
private var interceptor: Interceptor? = null
|
|
fun setInterceptor(interceptor: Interceptor?): DownLoadUtils {
|
|
this.interceptor = interceptor
|
|
return this
|
|
}
|
|
|
|
private var readTimeOut = 30L
|
|
fun setReadTImeOut(read: Long): DownLoadUtils {
|
|
this.readTimeOut = read
|
|
return this
|
|
}
|
|
|
|
private var writeTimeout = 30L
|
|
fun setWriteTimeOut(write: Long): DownLoadUtils {
|
|
this.writeTimeout = write
|
|
return this
|
|
}
|
|
|
|
private var connectTimeout = 30L
|
|
fun setConnectTimeOut(connect: Long): DownLoadUtils {
|
|
this.connectTimeout = connect
|
|
return this
|
|
}
|
|
|
|
var filePath = ""
|
|
fun setFilePath(path: String): DownLoadUtils {
|
|
this.filePath = path
|
|
return this
|
|
}
|
|
|
|
private var fileName = ""
|
|
fun setFileName(name: String): DownLoadUtils {
|
|
this.fileName = name
|
|
return this
|
|
}
|
|
|
|
private var deleteWhenException = true
|
|
fun setDeleteWhenException(dele: Boolean): DownLoadUtils {
|
|
this.deleteWhenException = dele
|
|
return this
|
|
}
|
|
|
|
private val requestBuilder: Request.Builder = Request.Builder()
|
|
private var urlBuilder: HttpUrl.Builder? = null
|
|
|
|
private val okHttpClient = lazy {
|
|
OkHttpClient.Builder()
|
|
.readTimeout(readTimeOut, TimeUnit.SECONDS)
|
|
.writeTimeout(writeTimeout, TimeUnit.SECONDS)
|
|
.connectTimeout(connectTimeout, TimeUnit.SECONDS)
|
|
.addInterceptor(interceptor ?: LoggingInterceptor())
|
|
.build()
|
|
}
|
|
private var actionGetTotal: (total: Long) -> Unit? = { _ -> }
|
|
private var actionProgress: (position: Long) -> Unit? = { _ -> }
|
|
private var actionSuccess: (file: File) -> Unit? = { _ -> }
|
|
private var actionCancel: () -> Unit? = { }
|
|
private var actionFail: (msg: String) -> Unit? = { _ -> }
|
|
|
|
fun setActionCallBack(
|
|
actionGetTotal: (total: Long) -> Unit,
|
|
actionProgress: (position: Long) -> Unit,
|
|
actionSuccess: (file: File) -> Unit,
|
|
actionFail: (msg: String) -> Unit,
|
|
): DownLoadUtils {
|
|
this.actionGetTotal = actionGetTotal
|
|
this.actionProgress = actionProgress
|
|
this.actionSuccess = actionSuccess
|
|
this.actionFail = actionFail
|
|
|
|
return this
|
|
}
|
|
|
|
private var downCallBack: DownCallBack? = null
|
|
fun setDownCallBack(callBack: DownCallBack): DownLoadUtils {
|
|
this.downCallBack = callBack
|
|
return this
|
|
}
|
|
|
|
fun initUrl(url: String, params: Map<String, String>?): DownLoadUtils {
|
|
urlBuilder = url.toHttpUrlOrNull()?.newBuilder()
|
|
if (params.isNullOrEmpty()) {
|
|
return this
|
|
} else {
|
|
for ((k, v) in params) {
|
|
checkName(k)
|
|
urlBuilder?.setQueryParameter(k, v)
|
|
}
|
|
}
|
|
return this
|
|
}
|
|
|
|
fun addHeader(map: Map<String, String>): DownLoadUtils {
|
|
requestBuilder.headers(map.toHeaders())
|
|
return this
|
|
}
|
|
|
|
private fun checkName(name: String) {
|
|
require(name.isNotEmpty()) { "name is empty" }
|
|
}
|
|
|
|
fun down() {
|
|
if (urlBuilder == null) {
|
|
throw IllegalStateException("url not init")
|
|
} else {
|
|
doDown()
|
|
}
|
|
}
|
|
|
|
private fun doDown() {
|
|
val startTime = System.currentTimeMillis()
|
|
Log.i(TAG, "startTime=$startTime")
|
|
val url = urlBuilder?.build()
|
|
if (url == null) {
|
|
doException("url is null")
|
|
return
|
|
}
|
|
if (isDowning(filePath + fileName)) {
|
|
return
|
|
}
|
|
cancelledList.remove(fileName)
|
|
|
|
val currentLen = downloadSizeInfo[fileName] ?: 0L
|
|
if (isCanContinueDownload(url.toUrl(), currentLen)) {
|
|
requestBuilder.removeHeader("RANGE")
|
|
requestBuilder.addHeader("RANGE", "bytes=${currentLen}-")
|
|
}
|
|
val request = requestBuilder.url(url).tag(filePath + fileName).build()
|
|
|
|
var `is`: InputStream? = null
|
|
var raf: RandomAccessFile? = null
|
|
var file: File? = null
|
|
try {
|
|
val response = okHttpClient.value.newCall(request).execute()
|
|
val total = response.body?.contentLength() ?: 0
|
|
doGetTotal(currentLen + total)
|
|
|
|
val buf = ByteArray(buffSize)
|
|
var len: Int
|
|
|
|
file = if (fileName.isEmpty()) {
|
|
File(filePath)
|
|
} else {
|
|
val fileDir = File(filePath)
|
|
if (!fileDir.exists() || !fileDir.isDirectory) {
|
|
fileDir.mkdirs()
|
|
}
|
|
File(filePath, fileName)
|
|
}
|
|
|
|
`is` = response.body?.byteStream() ?: FileInputStream("")
|
|
raf = RandomAccessFile(file, "rw")
|
|
raf.seek(currentLen)
|
|
|
|
var sum: Long = currentLen
|
|
while (`is`.read(buf).also { len = it } != -1) {
|
|
if (isCancelled()) throw CancellationException()
|
|
raf.write(buf, 0, len)
|
|
sum += len.toLong()
|
|
downloadSizeInfo[fileName] = sum
|
|
Log.e(TAG, "download progress : $sum")
|
|
doProgress(sum)
|
|
}
|
|
Log.e(TAG, "download success")
|
|
|
|
if (!file.exists()) {
|
|
throw FileNotFoundException("file create err,not exists")
|
|
} else {
|
|
doSuccess(file)
|
|
}
|
|
Log.e(TAG, "totalTime=" + (System.currentTimeMillis() - startTime))
|
|
} catch (e: Exception) {
|
|
if (deleteWhenException && file?.exists() == true) file.delete()
|
|
Log.e(TAG, "download failed : " + e.message)
|
|
doException(e.message.toString())
|
|
} finally {
|
|
try {
|
|
`is`?.close()
|
|
} catch (e: IOException) {
|
|
e.printStackTrace()
|
|
}
|
|
try {
|
|
raf?.close()
|
|
} catch (e: IOException) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun isCanContinueDownload(url: URL, start: Long): Boolean { //是否支持断点下载
|
|
val requestBuilder = Request.Builder()
|
|
requestBuilder.addHeader("RANGE", "bytes=$start-")
|
|
requestBuilder.addHeader("Connection", "close")
|
|
val request: Request = requestBuilder.url(url).head().build()
|
|
val response: Response = okHttpClient.value.newCall(request).execute()
|
|
return if (response.isSuccessful) {
|
|
if (response.code == 206) { //支持
|
|
response.close()
|
|
true
|
|
} else { //不支持
|
|
response.close()
|
|
false
|
|
}
|
|
} else {
|
|
response.close()
|
|
false
|
|
}
|
|
}
|
|
|
|
private fun isDowning(tag: String): Boolean {
|
|
for (call in okHttpClient.value.dispatcher.runningCalls()) {
|
|
if (call.request().tag() == tag) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private fun isCancelled(): Boolean {
|
|
return cancelledList.contains(fileName)
|
|
}
|
|
|
|
fun cancel() {
|
|
cancel(filePath + fileName)
|
|
}
|
|
|
|
fun cancel(tag: String) {
|
|
cancelledList.add(fileName)
|
|
if (okHttpClient.value.dispatcher.runningCalls().isNotEmpty()) {
|
|
for (call in okHttpClient.value.dispatcher.runningCalls()) {
|
|
if (call.request().tag() == tag) {
|
|
call.cancel()
|
|
}
|
|
}
|
|
}
|
|
doCancel()
|
|
}
|
|
|
|
private fun doException(err: String) {
|
|
runOnUiThread {
|
|
if (downCallBack == null) {
|
|
actionFail.invoke(err)
|
|
} else {
|
|
downCallBack?.fail(err)
|
|
}
|
|
mainThread = null
|
|
}
|
|
}
|
|
|
|
private fun doSuccess(file: File?) {
|
|
runOnUiThread {
|
|
if (file == null) {
|
|
doException("file not exit")
|
|
} else {
|
|
if (downCallBack == null) {
|
|
actionSuccess.invoke(file)
|
|
} else {
|
|
downCallBack?.success(file)
|
|
}
|
|
}
|
|
mainThread = null
|
|
}
|
|
}
|
|
|
|
private fun doGetTotal(total: Long) {
|
|
runOnUiThread {
|
|
if (downCallBack == null) {
|
|
actionGetTotal.invoke(total)
|
|
} else {
|
|
downCallBack?.total(total)
|
|
}
|
|
}
|
|
mainThread = null
|
|
}
|
|
|
|
private fun doProgress(progress: Long) {
|
|
runOnUiThread {
|
|
if (downCallBack == null) {
|
|
actionProgress.invoke(progress)
|
|
} else {
|
|
downCallBack?.progress(progress)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun doCancel() {
|
|
runOnUiThread {
|
|
if (downCallBack == null) {
|
|
actionCancel.invoke()
|
|
} else {
|
|
downCallBack?.cancel()
|
|
}
|
|
}
|
|
mainThread = null
|
|
}
|
|
|
|
private var mainThread: Handler? = null
|
|
private fun runOnUiThread(action: () -> Unit) {
|
|
if (Looper.myLooper() != Looper.getMainLooper()) { // If we finish marking off of the main thread, we need to
|
|
// actually do it on the main thread to ensure correct ordering.
|
|
if (mainThread == null) {
|
|
mainThread = Handler(Looper.getMainLooper())
|
|
}
|
|
mainThread?.post {
|
|
action.invoke()
|
|
}
|
|
return
|
|
}
|
|
action.invoke()
|
|
}
|
|
|
|
|
|
class LoggingInterceptor : Interceptor {
|
|
@Throws(IOException::class)
|
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
val request: Request = chain.request()
|
|
val startTime = System.nanoTime()
|
|
Log.d(
|
|
TAG, String.format(
|
|
"Sending request %s on %s%n%s",
|
|
request.url, chain.connection(), request.headers
|
|
)
|
|
)
|
|
val response: Response = chain.proceed(request)
|
|
val endTime = System.nanoTime()
|
|
Log.d(
|
|
TAG, String.format(
|
|
"Received response for %s in %.1fms%n%s",
|
|
response.request.url, (endTime - startTime) / 1e6, response.headers
|
|
)
|
|
)
|
|
return response
|
|
}
|
|
|
|
companion object {
|
|
private const val TAG = "LoggingInterceptor"
|
|
}
|
|
}
|
|
|
|
interface DownCallBack {
|
|
fun success(file: File)
|
|
fun fail(str: String)
|
|
fun progress(position: Long)
|
|
fun total(total: Long)
|
|
fun cancel()
|
|
}
|
|
} |