改代碼只用于練手,代碼借鑒過網(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={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>