Kotlin+Jetpack 实现一键式下载

项目地址,欢迎 star
首先看一下效果:

在这里插入图片描述
使用方式:

 	    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()
                }
            })
        }
    }
}

参考自慕课网视频

原创文章 119 获赞 77 访问量 3万+

猜你喜欢

转载自blog.csdn.net/baidu_40389775/article/details/104911075