kotlin 断点续传 下载功能

改代码只用于练手,代码借鉴过网上有人用okhttp和kotlin 编写的下载


image.png

大致效果就是页面顶部那块

页面代码
1.xml

  

    


    

2.activity

    var viewModal = ViewModelProvider(
            this,
            ViewModelProvider.AndroidViewModelFactory.getInstance(application)
        ).get(PageModel::class.java)


        val tvSure = findViewById(R.id.tvSure);
        tvSure.setOnClickListener {
            viewModal.downLoad("http://...../apk_release.apk")
        }
        val tvCancel = findViewById(R.id.tvCancel);
        tvCancel.setOnClickListener {
            viewModal.cancel()
        }
        val progressBar = findViewById(R.id.progressBar);

        viewModal.progress.observe(this, Observer {
            progressBar.progress = it.toInt()
        })

        viewModal.downSucc.observe(this, Observer {
            LogUtils.v("下载成功" + it)
            try {
                installNormal(this, it);
            } catch (e: Exception) {
                LogUtils.e(e)
            }


        })
        viewModal.hintMsg.observe(this, Observer {
            LogUtils.v("下载 提示" + it)
        })
        viewModal.downFail.observe(this, Observer {
            if (it){
                LogUtils.v("下载失败")
            }
        })

2.viewModel

package com.melo.app.mvvm.page

import android.content.Context
import android.net.ParseException
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.webkit.MimeTypeMap
import com.blankj.utilcode.util.LogUtils
import com.google.gson.JsonIOException
import com.google.gson.JsonParseException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.isActive
import org.json.JSONException
import retrofit2.HttpException
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.net.*

fun handleResponseError(t: Throwable): String {
    Log.e("handle", "===" + t.message)
    return when (t) {
        is UnknownHostException -> {
            "网络不可用"
        }
        is SocketTimeoutException -> {
            "请求网络超时"
        }
        is HttpException -> {
            convertStatusCode(t)
        }
        is JsonParseException, is ParseException, is JSONException, is JsonIOException -> {
            "数据解析错误"
        }
        is SocketException -> {
            "网络连接断开或者切换"
        }
        else -> {
            "未知问题"
        }
    }
}

fun convertStatusCode(httpException: HttpException): String {
    return when {
        httpException.code() >= 500 -> {
            "服务器发生错误"
        }
        httpException.code() == 404 -> {
            "请求地址不存在"
        }
        httpException.code() >= 400 -> {
            "请求被服务器失败"
        }
        httpException.code() >= 300 -> {
            "请求被重定向到其他页面"
        }
        else -> {
            "网络问题"
        }
    }

}


abstract class IDownLoadBuild {
    open fun getFileName(): String? = null
    open fun getUri(contentType: String): Uri? = null
    open fun getDownLoadFile(): File? = null
    abstract fun getContext(): Context //贪方便的话,返回Application就行
}

sealed class DownLoadStatus {
    class DownLoadProcess(val currentLength: Long, val length: Long, val process: Float) :
        DownLoadStatus()

    class DowLoadError(val t: Throwable) : DownLoadStatus()
    class DowLoadSuccess(val uri: Uri) : DownLoadStatus()
}

class DownLoadBuild(val cxt: Context) : IDownLoadBuild() {
    override fun getContext(): Context = cxt
}

class FileInfo(
    var contentType: String,
    var contentLength: Long
)


fun downloadByHttpUrl(apkUrl: String, build: IDownLoadBuild) = flow {
    try {
        val url1 = URL(apkUrl)
        val con1 = url1.openConnection() as HttpURLConnection
        con1.requestMethod = "GET"
        var fileInfo: FileInfo? = null
        if (con1.responseCode == HttpURLConnection.HTTP_OK) {
            fileInfo = FileInfo(con1.contentType, con1.contentLength.toLong());
        } else {
            emit(DownLoadStatus.DowLoadError(RuntimeException("网络错误")))
        }
        if (fileInfo == null) {
            LogUtils.e("fileInfo  is  null")
            emit(DownLoadStatus.DowLoadError(RuntimeException("下载出错")))
            return@flow
        }
        LogUtils.e("fileInfo  is  ${fileInfo.contentLength}=======${fileInfo.contentType}")

        val url = URL(apkUrl)
        val con = url.openConnection() as HttpURLConnection
        con.requestMethod = "GET"
        con.readTimeout = 30000
        con.connectTimeout = 30000
        con.setRequestProperty("Accept-Encoding", "identity")
        val info = try {
            downLoadBuildToOutputStream(build, fileInfo.contentType)
        } catch (e: Exception) {
            emit(DownLoadStatus.DowLoadError(e))
            DowLoadInfo(null)
            return@flow
        }
        var currentLength: Long = info.file?.length() ?: 0;
        if (currentLength == fileInfo.contentLength) {
            emit(DownLoadStatus.DowLoadSuccess(Uri.fromFile(info.file)))
            return@flow
        }
        con.setRequestProperty("Range", "bytes=$currentLength-${fileInfo.contentLength}")
        LogUtils.e("bytes=$currentLength-${fileInfo.contentLength}")
        LogUtils.e("===CODE==" + con.responseCode)
        if (con.responseCode == HttpURLConnection.HTTP_PARTIAL) {
            val ios = con.inputStream
            val ops = info.ops
            if (ops == null) {
                emit(DownLoadStatus.DowLoadError(RuntimeException("下载出错")))
                return@flow
            }
            //写入文件
            val bufferSize = 1024 * 16
            val buffer = ByteArray(bufferSize)
            val bufferedInputStream = BufferedInputStream(ios, bufferSize)
            var readLength: Int = 0
            try {
                while (bufferedInputStream.read(buffer, 0, bufferSize)
                        .also { readLength = it } != -1
                ) {
                    ops.write(buffer, 0, readLength)
                    currentLength += readLength
                    emit(
                        DownLoadStatus.DownLoadProcess(
                            currentLength,
                            fileInfo.contentLength,
                            currentLength.toFloat() / fileInfo.contentLength.toFloat()
                        )
                    )
                    LogUtils.e("========下载中。。。")
                    if (!currentCoroutineContext().isActive) {
                        emit(DownLoadStatus.DowLoadError(RuntimeException("暂停下载")))
                    }
                }
                bufferedInputStream.close()
                ops.close()
                ios.close()
            } catch (e: java.lang.Exception) {
                emit(DownLoadStatus.DowLoadError(e))
                return@flow
            }
            if (info.uri != null)
                emit(DownLoadStatus.DowLoadSuccess(info.uri))
            else emit(DownLoadStatus.DowLoadSuccess(Uri.fromFile(info.file)))

        } else {
            emit(DownLoadStatus.DowLoadError(RuntimeException("下载出错")))
        }
    } catch (e: Throwable) {
        emit(DownLoadStatus.DowLoadError(e))
    }
}.flowOn(Dispatchers.IO)

private fun downLoadBuildToOutputStream(build: IDownLoadBuild, contentType: String): DowLoadInfo {
    val context = build.getContext()
    val uri = build.getUri(contentType)
    if (build.getDownLoadFile() != null) {
        val file = build.getDownLoadFile()!!
        return DowLoadInfo(FileOutputStream(file), file)
    } else if (uri != null) {
        return DowLoadInfo(context.contentResolver.openOutputStream(uri), uri = uri)
    } else {
        val name = build.getFileName()
        val fileName = if (!name.isNullOrBlank()) name else "meloInfo.${
            MimeTypeMap.getSingleton()
                .getExtensionFromMimeType(contentType)
        }"
        val file = File("${context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)}", fileName)
        return DowLoadInfo(FileOutputStream(file, true), file)
    }
}

private class DowLoadInfo(val ops: OutputStream?, val file: File? = null, val uri: Uri? = null)


注意点:
1.取消下载任务 没有回调,若要提示 需另外添加
2.断点续传 主要是添加
con.setRequestProperty("Range", "bytes={fileInfo.contentLength}")

bytes 开始点 到文件总大小。 也可以 直接用 "bytes=$currentLength-"

3.适配的话需要在配置文件里面添加provider

     
            
        

你可能感兴趣的:(kotlin 断点续传 下载功能)