首先看一下效果:
使用方式:
fileUrl.setText("https://kotlinlang.org/docs/kotlin-docs.pdf")
fileName.setText("Kotlin-Docs.pdf")
downloadButton.setOnClickListener {
DownLoadLaunch.create(this, fileUrl.text.toString(),
fileName.text.toString(), object : OnStateListener {
override fun start() {
Toast.makeText(context, "开始下载", Toast.LENGTH_LONG).show()
}
override fun process(value: Int) {
downloadButton.text = "Downloading $value"
}
override fun error(throwable: Throwable) {
Toast.makeText(context, "下载出错:${throwable.message}", Toast.LENGTH_LONG).show()
downloadButton.text = "DownLoad"
}
override fun donal(file: File) {
downloadButton.text = "下载成功"
downloadPath.setText(file.absolutePath)
Toast.makeText(context, "下载完成:" + file.path, Toast.LENGTH_LONG).show()
}
})
}
只需要指定 url 和 文件的名字,在调用一个方法,就可以实现文件的下载。并且当前活动销毁时,就会停止下载
下面看一下使用方法:
1,导入依赖:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "androidx.core:core-ktx:1.2.0"
//协程基础库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
//协程 Android 库,提供 UI 调度器
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
//liveData + ViewModel + lifecycle
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.0-alpha01"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha01"
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.3.0-alpha01"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha01"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha01"
2,创建 DownLoadManager
object DownLoadManager {
//创建文件
private val downloadDirectory by lazy {
File(ContextTools.context.filesDir, "download").also { it.mkdirs() }
}
//状态
sealed class DownloadStatus {
object None : DownloadStatus()
class Progress(val value: Int) : DownloadStatus()
class Error(val throwable: Throwable) : DownloadStatus()
class Donel(val file: File) : DownloadStatus()
}
//下载
fun download(url: String, fileName: String): Flow<DownloadStatus> {
val file = File(downloadDirectory, fileName)
return flow {
val request = Request.Builder().url(url).get().build()
val response = OkHttpClient.Builder().build()
.newCall(request).execute()
if (response.isSuccessful) {
response.body!!.let { body ->
//总大小
val total = body.contentLength()
//当前值
var emittedProcess = 0L
file.outputStream().use { output ->
body.byteStream().use { input ->
input.copyTo(output) { bytesCopied ->
//计算百分比
val progress = bytesCopied * 100 / total
//当前的值大于上一次的就进行通知
if (progress - emittedProcess > 1) {
//发射,外部的 collect 会执行
emit(DownloadStatus.Progress(progress.toInt()))
emittedProcess = progress
}
}
}
}
//下载完成
emit(DownloadStatus.Donel(file))
}
} else {
throw Exception(response.message)
}
}.catch {
file.delete()
emit(DownloadStatus.Error(it))
//保留最新的值
}.conflate()
}
}
//重写了一些copyTo 方法,增加了一个参数,用来回调下载进度
inline fun InputStream.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE, progress: (Long)-> Unit): Long {
var bytesCopied: Long = 0
val buffer = ByteArray(bufferSize)
var bytes = read(buffer)
while (bytes >= 0) {
out.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = read(buffer)
progress(bytesCopied)
}
return bytesCopied
}
上面主要使用了 flow 进行下载,不了解的可以先百度一下
3,创建 DownloadModel ,用来进行下载
class DownloadModel : ViewModel() {
val downloadStateLiveData =
MutableLiveData<DownLoadManager.DownloadStatus>(DownLoadManager.DownloadStatus.None)
suspend fun download(url: String, fileName: String) {
DownLoadManager.download(url, fileName)
.flowOn(Dispatchers.IO)
.collect {
//发送数据
downloadStateLiveData.value = it
}
}
}
4,创建 DownloadLaunch,入口类,我们调用这个类的方法进行下载
interface OnStateListener {
fun start()
fun process(value: Int)
fun error(throwable: Throwable)
fun donal(file: File)
}
object DownLoadLaunch {
private val mDownloadModel: DownloadModel = DownloadModel()
fun create(
owner: LifecycleOwner,
url: String,
fileName: String,
stateListener: OnStateListener
) {
//这里的 Lambda 会被多次调用,当 liveData 发送消息后 lambda会得到执行
mDownloadModel.downloadStateLiveData.observe(owner) { status ->
when (status) {
DownLoadManager.DownloadStatus.None -> {
//启动协程
owner.lifecycleScope.launch {
stateListener.start()
//下载
mDownloadModel.download(url, fileName)
}
}
is DownLoadManager.DownloadStatus.Progress -> {
stateListener.process(status.value)
}
is DownLoadManager.DownloadStatus.Error -> {
stateListener.error(status.throwable)
}
is DownLoadManager.DownloadStatus.Donel -> {
stateListener.donal(status.file)
}
}
}
}
}
5,进行使用
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/fileUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="文件路径"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:textColor="#000000"
android:textSize="25sp" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/fileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:layout_marginTop="20dp"
android:paddingRight="20dp"
android:hint="文件名字"
android:textColor="#000000"
android:textSize="25sp"
/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/downloadButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="DownLoad"
android:layout_marginTop="20dp"
android:textAllCaps="false" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/downloadPath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:textColor="#000000"
android:layout_marginTop="20dp"
android:textSize="25sp"
android:textStyle="bold" />
</androidx.appcompat.widget.LinearLayoutCompat>
class DownLoadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_down_load)
init()
}
@SuppressLint("SetTextI18n")
private fun init() {
fileUrl.setText("https://kotlinlang.org/docs/kotlin-docs.pdf")
fileName.setText("Kotlin-Docs.pdf")
downloadButton.setOnClickListener {
DownLoadLaunch.create(this, fileUrl.text.toString(),
fileName.text.toString(), object : OnStateListener {
override fun start() {
Toast.makeText(context, "开始下载", Toast.LENGTH_LONG).show()
}
override fun process(value: Int) {
downloadButton.text = "Downloading $value"
}
override fun error(throwable: Throwable) {
Toast.makeText(context, "下载出错:${throwable.message}", Toast.LENGTH_LONG).show()
downloadButton.text = "DownLoad"
}
override fun donal(file: File) {
downloadButton.text = "下载成功"
downloadPath.setText(file.absolutePath)
Toast.makeText(context, "下载完成:" + file.path, Toast.LENGTH_LONG).show()
}
})
}
}
}