kotlin 斷點續(xù)傳 下載功能

改代碼只用于練手,代碼借鑒過網(wǎng)上有人用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 -> {
            "網(wǎng)絡(luò)不可用"
        }
        is SocketTimeoutException -> {
            "請求網(wǎng)絡(luò)超時"
        }
        is HttpException -> {
            convertStatusCode(t)
        }
        is JsonParseException, is ParseException, is JSONException, is JsonIOException -> {
            "數(shù)據(jù)解析錯誤"
        }
        is SocketException -> {
            "網(wǎng)絡(luò)連接斷開或者切換"
        }
        else -> {
            "未知問題"
        }
    }
}

fun convertStatusCode(httpException: HttpException): String {
    return when {
        httpException.code() >= 500 -> {
            "服務(wù)器發(fā)生錯誤"
        }
        httpException.code() == 404 -> {
            "請求地址不存在"
        }
        httpException.code() >= 400 -> {
            "請求被服務(wù)器失敗"
        }
        httpException.code() >= 300 -> {
            "請求被重定向到其他頁面"
        }
        else -> {
            "網(wǎng)絡(luò)問題"
        }
    }

}


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("網(wǎng)絡(luò)錯誤")))
        }
        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.取消下載任務(wù) 沒有回調(diào)妥粟,若要提示 需另外添加
2.斷點續(xù)傳 主要是添加
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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吏够,一起剝皮案震驚了整個濱河市勾给,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅知,老刑警劉巖播急,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喉镰,居然都是意外死亡旅择,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門侣姆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來生真,“玉大人,你說我怎么就攤上這事捺宗≈埃” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵蚜厉,是天一觀的道長长已。 經(jīng)常有香客問我,道長昼牛,這世上最難降的妖魔是什么术瓮? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮贰健,結(jié)果婚禮上胞四,老公的妹妹穿的比我還像新娘。我一直安慰自己伶椿,他們只是感情好辜伟,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脊另,像睡著了一般导狡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上偎痛,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天旱捧,我揣著相機(jī)與錄音,去河邊找鬼踩麦。 笑死枚赡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的靖榕。 我是一名探鬼主播标锄,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼茁计!你這毒婦竟也來了料皇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤星压,失蹤者是張志新(化名)和其女友劉穎践剂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜膘,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡逊脯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了竣贪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片军洼。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡巩螃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匕争,到底是詐尸還是另有隱情避乏,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布甘桑,位于F島的核電站拍皮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跑杭。R本人自食惡果不足惜铆帽,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望德谅。 院中可真熱鬧爹橱,春花似錦、人聲如沸女阀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浸策。三九已至冯键,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庸汗,已是汗流浹背惫确。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蚯舱,地道東北人改化。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像枉昏,于是被迫代替她去往敵國和親陈肛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容