改代码只用于练手,代码借鉴过网上有人用okhttp和kotlin 编写的下载
大致效果就是页面顶部那块
页面代码
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={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>