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

參考自慕課網視頻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章