diff --git a/app/src/main/java/com/img/rabbit/utils/DownLoadUtils.kt b/app/src/main/java/com/img/rabbit/utils/DownLoadUtils.kt deleted file mode 100644 index 97eb6e7..0000000 --- a/app/src/main/java/com/img/rabbit/utils/DownLoadUtils.kt +++ /dev/null @@ -1,385 +0,0 @@ -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() - private var cancelledList = mutableListOf() - - 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?): 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): 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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/img/rabbit/utils/DownUtils.kt b/app/src/main/java/com/img/rabbit/utils/DownUtils.kt new file mode 100644 index 0000000..0b2b95a --- /dev/null +++ b/app/src/main/java/com/img/rabbit/utils/DownUtils.kt @@ -0,0 +1,92 @@ +package com.img.rabbit.utils + +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody +import okio.Buffer +import okio.BufferedSource +import okio.ForwardingSource +import okio.buffer +import okio.sink +import java.io.File +import java.io.IOException + +object FileDownloadUtil { + private val client = OkHttpClient.Builder().build() + + fun download(url: String, saveFile: File, listener: DownloadListener) { + val request = Request.Builder().url(url).build() + + // 拦截器处理进度 + val progressClient = client.newBuilder() + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .body(ProgressResponseBody(originalResponse.body, listener)) + .build() + } + .build() + + progressClient.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + listener.onError(e) + } + + override fun onResponse(call: Call, response: Response) { + if (!response.isSuccessful) { + listener.onError(IOException("Unexpected code $response")) + return + } + + try { + val totalSize = response.body.contentLength() + listener.onStart(totalSize) + + response.body.source().use { source -> + saveFile.sink().buffer().use { sink -> + sink.writeAll(source) + } + } + listener.onSuccess(saveFile) + } catch (e: Exception) { + listener.onError(e) + } + } + }) + } +} + +// 进度回调接口 +interface DownloadListener { + fun onStart(totalSize: Long) // 开始下载,返回文件总大小 + fun onProgress(progress: Int, current: Long) // 进度(0-100),当前已下载字节 + fun onSuccess(file: File) // 下载成功 + fun onError(e: Exception) // 下载失败 +} + +// 自定义 ResponseBody 用于拦截进度 +class ProgressResponseBody( + private val responseBody: ResponseBody, + private val listener: DownloadListener +) : ResponseBody() { + override fun contentType() = responseBody.contentType() + override fun contentLength() = responseBody.contentLength() + + override fun source(): BufferedSource { + return object : ForwardingSource(responseBody.source()) { + var totalBytesRead = 0L + override fun read(sink: Buffer, byteCount: Long): Long { + val bytesRead = super.read(sink, byteCount) + totalBytesRead += if (bytesRead != -1L) bytesRead else 0 + val progress = if (contentLength() > 0) (totalBytesRead * 100 / contentLength()).toInt() else 0 + // 回调进度 + listener.onProgress(progress, totalBytesRead) + return bytesRead + } + }.buffer() + } +} + diff --git a/app/src/main/java/com/img/rabbit/utils/UniAppUtils.kt b/app/src/main/java/com/img/rabbit/utils/UniAppUtils.kt index 84be444..44d4d19 100644 --- a/app/src/main/java/com/img/rabbit/utils/UniAppUtils.kt +++ b/app/src/main/java/com/img/rabbit/utils/UniAppUtils.kt @@ -307,7 +307,7 @@ object UniAppUtils { oldFile.delete() } - onProgress(UniMpUpdate.DOWNLOAD_LOADING, null, 0.01f) + onProgress(UniMpUpdate.DOWNLOAD_LOADING, null, 0.001f) scope.launch { val isAvailable = isFileDownloadable(uniVersion.url) diff --git a/app/src/main/java/com/img/rabbit/utils/UpdateUtils.kt b/app/src/main/java/com/img/rabbit/utils/UpdateUtils.kt index e26f2f4..b71b425 100644 --- a/app/src/main/java/com/img/rabbit/utils/UpdateUtils.kt +++ b/app/src/main/java/com/img/rabbit/utils/UpdateUtils.kt @@ -16,25 +16,27 @@ object UpdateUtils { @SuppressLint("SetTextI18n") fun download(scope: CoroutineScope, url: String, filePath: String, fileName: String, onProgress:(progress:Int)-> Unit, onFinish:(isSuccess: Boolean, filePath: String?)-> Unit) { scope.launch(Dispatchers.IO) { - var totalProgress = 0L - DownLoadUtils.getInstance() - .setReadTImeOut(10L) - .setDeleteWhenException(false) - .initUrl(url, null) - .setFilePath(filePath) - .setFileName(fileName) - .setActionCallBack( - { totalProgress = it }, - { - val percent = it.toDouble() / totalProgress.toDouble() * 100 - val curProgress = percent.toInt() - onProgress(curProgress) - }, - { - onFinish(true, it.absolutePath) - }, { - onFinish(false, null) - }).down() + val destination = File(filePath, fileName) + if(destination.exists()){ + destination.delete() + } + FileDownloadUtil.download(url, destination, object : DownloadListener { + override fun onStart(totalSize: Long) { + onProgress(0) + } + + override fun onProgress(progress: Int, current: Long) { + onProgress(progress) + } + + override fun onSuccess(file: File) { + onFinish(true, file.absolutePath) + } + + override fun onError(e: Exception) { + onFinish(false, null) + } + }) } } diff --git a/app/src/main/java/com/img/rabbit/viewmodel/CutoutViewModel.kt b/app/src/main/java/com/img/rabbit/viewmodel/CutoutViewModel.kt index 78cfacb..1911d72 100644 --- a/app/src/main/java/com/img/rabbit/viewmodel/CutoutViewModel.kt +++ b/app/src/main/java/com/img/rabbit/viewmodel/CutoutViewModel.kt @@ -6,12 +6,16 @@ import android.net.Uri import android.os.Build import android.util.Log import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewModelScope import com.img.rabbit.bean.local.ErrorBean import com.img.rabbit.provider.api.ApiManager -import com.img.rabbit.utils.DownLoadUtils +import com.img.rabbit.utils.DownloadListener +import com.img.rabbit.utils.FileDownloadUtil import com.img.rabbit.utils.FileUtils import com.img.rabbit.utils.ImageUtils import com.img.rabbit.utils.PhotoCutter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.toRequestBody @@ -99,34 +103,27 @@ class CutoutViewModel : BaseViewModel() { if(targetFile.exists()){ targetFile.delete() } - DownLoadUtils.getInstance() - .initUrl(url, null) - .setFilePath(targetPath) - .setFileName(targetFileName) - .setDownCallBack(object : DownLoadUtils.DownCallBack { - override fun success(file: File) { + viewModelScope.launch(Dispatchers.IO) { + FileDownloadUtil.download(url, targetFile, object : DownloadListener { + override fun onStart(totalSize: Long) { + } + + override fun onProgress(progress: Int, current: Long) { + // 如果需要进度可以在这里处理 + } + + override fun onSuccess(file: File) { // 3. 下载成功后,将文件解析为 Bitmap 并回调给 UI val bitmap = android.graphics.BitmapFactory.decodeFile(file.absolutePath) onResult(true, bitmap) } - override fun fail(str: String) { - onResult(false, null) - } - - override fun progress(position: Long) { - // 如果需要进度可以在这里处理 - } - - override fun total(total: Long) { - // 获取总大小 - } - - override fun cancel() { + override fun onError(e: Exception) { onResult(false, null) } }) - .down() + + } }catch (e: Exception){ e.printStackTrace() onResult(false, null)