Kotlin协程Flow与文件下载(附代码)

文章目录

前言

一、下载的内在逻辑工作

二、运行结果

总结


前言

项目的前期准备工作已经在另一文章完成,主要是使用Jetpack的 Navigation 组件来完成一些UI工作,下面来实现具体的协程Flow与文件下载的工作。

Android Jetpack的 Navigation 组件(学习笔记)-CSDN博客


一、下载的内在逻辑工作

我们需要编写DownloadManager类来实现其内在的逻辑工作,将可能的情况通过emit()发射到Flow(冷流),并在Kotlin我们使用密封类sealed(里面装的东西就是一些不同的状态,比如下载过程中可能的情况,比如正在下载、下载出错或者下载完成的具体操作实现,简单来讲也是一种解耦操作,并且密封类的子类只能在密封类自身所在的文件中声明,更像一种文件的属性了),然后collect来进一步详细处理。

详细过程代码有相关注释

1.下载的内在逻辑代码(DownloadManager)

package com.example.applicationflow.download
import com.example.applicationflow.utils.copyTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import okhttp3.Request
import okhttp3.OkHttpClient
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import java.io.File
import java.io.IOException

//使用 object是 Kotlin 中的单例对象,它们在首次被访问时被延迟初始化,并且在整个应用程序生命周期内只存在一个实例。
object DownloadManager {
    //接受一个 URL 字符串和一个 File 对象作为参数,并返回一个 Flow,该 Flow 会产生 DownloadStatus 对象
    fun download(url: String, file: File): Flow<DownloadStatus>{
        return flow {
            val request = Request.Builder().url(url).get().build()
            val response = OkHttpClient.Builder().build().newCall(request).execute()
            if (response.isSuccessful){
    /*           这里有个技巧,response是可能为空的,一般用Kotlin是?.操作,
                但这样我们就无法模拟空异常情况,所以使用!!来让它可强制为空*/
                response.body!!.let { body ->//body()不能使用了吗?
                    val total =  body.contentLength() //成功了进行文件读写操作
                    file.outputStream().use { output ->
                        val input = body.byteStream()
                        var emittedProcess = 0L//模拟下载进度条
                        input.copyTo(output){ bytesCopied ->//编写一个扩展函数IOE.kt来实现边读边写操作
                            val process = bytesCopied * 100 / total//下载进度条操作
                            if (process - emittedProcess > 5){
                                delay(100)//让下载没那么快
                                emit(DownloadStatus.Progress(process.toInt()))
                                emittedProcess = process
                            }
                        }
                    }
                    }
                emit(DownloadStatus.Done(file))//交给密封类处理
            } else{
                throw IOException(response.toString())//没正常进行抛出异常
            }
        }.catch { //下游用catch来捕获异常情况
            file.delete()
            emit(DownloadStatus.Error(it))//交给密封类处理
        }.flowOn(Dispatchers.IO)//指定调度器
    }
}

2.其中使用了扩展函数 IOExt.kt来实现文件的边读边写

package com.example.applicationflow.utils

import kotlinx.coroutines.flow.MutableStateFlow
import java.io.InputStream
import java.io.OutputStream
val progressFlow = MutableStateFlow(0)

inline fun InputStream.copyTo(output: OutputStream, progress: (Long) -> Unit) {
    val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
    var bytesCopied: Long = 0
    var bytes = read(buffer)

    while (bytes >= 0) {
        output.write(buffer, 0, bytes)
        bytesCopied += bytes
        progress(bytesCopied) // 调用传入的进度更新函数

        bytes = read(buffer)
    }
}

3. 密封类DownloadStatus

package com.example.applicationflow.download

import java.io.File

sealed class DownloadStatus{
    object None : DownloadStatus()
    data class Progress(val value: Int) : DownloadStatus()
    data class Error(val throwable: Throwable) : DownloadStatus()
    data class Done(val file : File) : DownloadStatus()
}

4.回到 DownloadFragment

package com.example.applicationflow.fragment

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.example.applicationflow.R
import com.example.applicationflow.databinding.FragmentDownloadBinding
import com.example.applicationflow.databinding.FragmentHomeBinding
import com.example.applicationflow.download.DownloadManager
import com.example.applicationflow.download.DownloadStatus
import kotlinx.coroutines.flow.collect
import java.io.File

class DownloadFragment : Fragment() {
    val url = "https://img-blog.csdnimg.cn/8b31f564c1364e6ca595daf55af9fade.png"
    private val mBinding: FragmentDownloadBinding by lazy{
        FragmentDownloadBinding.inflate(layoutInflater)
    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return mBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //使用flow下游的collect需要使用协程的环境
        lifecycleScope.launchWhenCreated {
            context?.apply {//getExternalFilesDir是用来下载到Andriod本地文件的
                val file = File(getExternalFilesDir(null)?.path, "pic.JPG")
                DownloadManager.download(url, file).collect{ status -> //flow下游的操作
                    when (status) {//密封类的好处,使用更加便捷,根据收集的status流来调动下面的函数
                        is DownloadStatus.Progress -> {
                            mBinding.apply {
                                progressBar3.progress = status.value
                                tvProress.text = "${status.value}"
                            }
                        }
                        is DownloadStatus.Error -> {
                            Toast.makeText(context, "下载错误", Toast.LENGTH_SHORT).show()
                        }
                        is DownloadStatus.Done -> {
                            mBinding.apply {
                                progressBar3.progress = 100
                                tvProress.text = "100%"
                            }
                            Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show()
                        }

                        else -> {
                            Log.d("fenghua", "下载失败")
                        }
                    }
                }
            }
        }
    }
}

4. 相关目录如下

所需配置信息(基础上补加): 

plugins {
    id 'kotlin-kapt'
}
implementation 'androidx.databinding:databinding-runtime:8.0.2'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation 'com.squareup.okhttp3:okhttp:4.9.3'

清单文件信息(与网络有关补加):

<uses-permission android:name="android.permission.INTERNET"/>
android:networkSecurityConfig="@xml/network_security_config"

 

二、运行结果


总结

有不懂Flow特性的可以看一下我前面发的文章,这次主要是跟别人学习(动脑学院)实战Flow的下载应用,还有就是一些Android网络配置的相关知识.

协程中下载应用引入Flow有什么好处:

1.异步性能优化: 使用协程的 Flow 可以让网络下载操作在后台线程中执行,而不会阻塞主线程。这样可以避免在主线程中执行网络操作导致的UI卡顿,提高应用的响应性和性能。

:协程的基本特性(异步处理)
2.简化异步代码: Flow 提供了一种简洁的方式来处理异步数据流,使用起来比传统的回调方式更加清晰和简单。你可以使用 collect 操作符来订阅 Flow,并在其中处理下载的数据,而不需要手动管理线程和回调。

:就是延迟执行 emit() 相关操作直到调用 collect() 的时候才启动的理解,那你都可以准备collect()了,说明你的前期准备已经完成,所以可以emit()了,假如你前期工作没准备好就emit(),其中就很容易出现问题。这是不是逻辑更严谨一些,更利于保护内存数据等。

3.流式处理: Flow 本身就是一种数据流,它支持流式处理数据。你可以使用 Flow 的操作符来对下载的数据进行各种转换、过滤、合并等操作,以满足不同的需求,比如数据转换、数据过滤、数据合并等。
4.取消和异常处理: Flow 提供了对取消和异常处理的支持。通过协程的取消机制,你可以方便地取消下载操作,而不需要手动管理线程。此外,Flow 还提供了异常处理的机制,可以方便地处理下载过程中可能出现的异常情况。

:详细看代码的异常处理的细节
5.背压支持: Flow 支持背压,可以有效地控制数据流的速率,防止数据流过快导致内存溢出或应用崩溃的情况发生。你可以使用 Flow 的背压操作符来控制数据流的速率,保证数据的稳定流动。

:这点非常好用,我在想一些下载限流(比如百度网盘)是不是这样处理的
6.降低了耦合度

:详细看代码,更多在分工合作的形式完成代码。

这些解释都比较全面,大家仔细看前面的代码感悟一些,特别是协程Flow的认识,不然仔细展开又要几个钟都讲不完,有不懂的可以在评论区留言,大家相互学习。

  • 43
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Imagine8877

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值