美文网首页
kotlin 断点续传 下载功能

kotlin 断点续传 下载功能

作者: 清汤白面 | 来源:发表于2021-06-22 17:58 被阅读0次

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


image.png

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

页面代码
1.xml

  <TextView
        android:id="@+id/tvSure"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:padding="15dp"
        android:text="下载"
        android:textColor="#fff"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tvCancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:padding="15dp"
        android:text="取消下载"
        android:textColor="#fff"
        android:textSize="16sp" />


    <ProgressBar
        android:id="@+id/progressBar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingVertical="10dp"
        android:progress="0" />

2.activity

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


        val tvSure = findViewById<TextView>(R.id.tvSure);
        tvSure.setOnClickListener {
            viewModal.downLoad("http://...../apk_release.apk")
        }
        val tvCancel = findViewById<TextView>(R.id.tvCancel);
        tvCancel.setOnClickListener {
            viewModal.cancel()
        }
        val progressBar = findViewById<ProgressBar>(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=currentLength-{fileInfo.contentLength}")

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

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

     <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                tools:replace="android:resource" />
        </provider>

相关文章

  • kotlin 断点续传 下载功能

    改代码只用于练手,代码借鉴过网上有人用okhttp和kotlin 编写的下载 大致效果就是页面顶部那块 页面代码1...

  • iOS断点续传

    基于iOS10、realm封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)。下载...

  • 造轮子 - RxDownload

    基于RxJava打造的下载工具, 支持多线程下载和断点续传, 智能判断是否支持断点续传等功能 标签(空格分隔): ...

  • Android断点下载小结

    前言 断点续传是一个很传统的话题;现在但凡包含下载功能的软件,大部分都会有断点续传的功能;因此对于断点续传的实现,...

  • 自定义下载器

    功能: 支持多任务同时下载 支持断点续传 文件结构: AYFileTool AYDownLoader AYDown...

  • 简易下载器-Downloader

    功能简介 使用RxJava和Okhttp实现的简单下载器,支持: 1.普通下载 2.断点续传 3.下载进度回调 以...

  • 安卓下载任务管理

    下载页面UI设计参照 网易云音乐 下载功能 多任务并行下载 断点续传(需服务器支持) 项目地址:https://g...

  • iOS 下载功能(断点续传)

    我们来看看如何自己简单的实现一个断点续传的功能,思路如下: 思路步骤:1.文件的存放 1.1 如果正在下载,放...

  • Android下载文件(一)下载进度&断点续传

    索引 Android下载文件(一)下载进度&断点续传 Android下载文件(二)多线程并发&断点续传(待续) A...

  • android 多线程断点续传下载 一

    文章出处  想做一个下载功能,当然理想的功能要支持多任务同时下载,断点续传的功能,我想一步一步来,首先困难摆在了多...

网友评论

      本文标题:kotlin 断点续传 下载功能

      本文链接:https://www.haomeiwen.com/subject/idsmyltx.html