Kotlin协程(4)
Kotlin协程(4) Kotlin实践部分 Flow与文件下载应用 DownloadFragment.kt // DownloadFragment 是一个 Fragment 类,用于处理文件下载任务 class DownloadFragment : Fragment() { // 定义下载文件的 URL 地址,这是一个静态的常量 val URL = "https://ts1.cn.mm.bing.net/th/id/R-C.56ab5704680b6574c1b3c0a52643d8b5?rik=P8OAzrJEZS%2biuw&riu=http%3a%2f%2fjourneyz.co%2fwp-content%2fuploads%2f2019%2f09%2fGolden-Gate-Bridge.jpg&ehk=WX9eb2rUUjWBLLrsG2MQZLOMk2wtreV%2bT1Qq1tARk4s%3d&risl=&pid=ImgRaw&r=0" // 延迟初始化 mBinding,这是一个 FragmentDownloadBinding 对象,用于绑定 XML 布局文件 private val mBinding: FragmentDownloadBinding by lazy { FragmentDownloadBinding.inflate(layoutInflater) } // 重写 onCreateView 方法,在 Fragment 创建视图时调用 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // 返回绑定的根视图作为 Fragment 的界面 return mBinding.root } // 重写 onActivityCreated 方法,当与 Fragment 相关的活动的 onCreate 方法完成时调用 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // 使用 lifecycleScope 启动协程,当 Fragment 的生命周期处于 Created 状态时执行 lifecycleScope.launchWhenCreated { // 获取上下文并执行下载操作 context?.apply { // 指定下载文件的存储位置,保存在应用的外部文件目录下 val file = File(getExternalFilesDir(null)?.path, "pic.jpg") // 开始下载文件,并收集下载状态 DownloadManager.download(URL, file).collect { status -> when (status) { // 如果下载过程中有进度更新 is DownloadStatus.Progress -> { mBinding.apply { // 更新进度条和进度文本 progressBar.progress = status.value tvProgress.text = "${status.value}%" } } // 如果下载过程中出现错误 is DownloadStatus.Error -> { Toast.makeText(context, "下载错误", Toast.LENGTH_SHORT).show() } // 如果下载完成 is DownloadStatus.Done -> { mBinding.apply { // 设置进度条到 100%,并更新文本为 100% progressBar.progress = 100 tvProgress.text = "100%" } Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show() } // 处理其他可能的下载状态 else -> { Log.d("Kennem", "下载失败") } } } } } } } DownloadManager.kt object DownloadManager { /** * 下载指定的文件并返回下载状态的流(Flow)。 * * @param url 文件下载的 URL 地址。 * @param file 下载后保存的本地文件。 * @return 下载状态的 Flow,包含下载进度、完成状态或错误信息。 */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) fun download(url: String, file: File): Flow<DownloadStatus> { return flow { // 构建 HTTP 请求对象 val request = Request.Builder().url(url).get().build() // 执行 HTTP 请求,获取响应 val response = OkHttpClient.Builder().build().newCall(request).execute() // 检查响应是否成功 if (response.isSuccessful) { response.body()!!.let { body -> // 获取内容的总长度(字节数) val total = body.contentLength() // 使用输出流将下载的数据写入本地文件 file.outputStream().use { output -> val input = body.byteStream() // 获取输入流 var emittedProgress = 0L // 记录已发出的进度 // 将输入流中的数据复制到输出流,同时跟踪复制的字节数 input.copyTo(output) { bytesCopied -> // 计算当前进度百分比 val progress = bytesCopied * 100 / total // 每当进度比上次更新多5%以上时,发出进度更新 if (progress - emittedProgress > 5) { delay(100) // 模拟网络延迟(可选) emit(DownloadStatus.Progress(progress.toInt())) // 发出进度状态 emittedProgress = progress // 更新已发出的进度 } } } } // 下载完成,发出完成状态 emit(DownloadStatus.Done(file)) } else { // 如果响应失败,抛出异常 throw IOException(response.toString()) } }.catch { // 如果发生错误,删除部分下载的文件并发出错误状态 file.delete() emit(DownloadStatus.Error(it)) }.flowOn(Dispatchers.IO) // 在 IO 线程上运行下载流程 } } DownloadStatus.kt /** * 封闭类,用于表示下载过程中的不同状态。 * 封闭类允许定义一个有限的类型集合,在使用 `when` 表达式时提供全面的类型检查。 */ sealed class DownloadStatus { /** * 表示初始状态,即尚未开始下载。 * 可以用于初始化或重置状态。 */ object None : DownloadStatus() /** * 表示正在进行的下载的进度。 * @param value 一个整数,表示当前的进度百分比(0-100)。 */ data class Progress(val value: Int) : DownloadStatus() /** * 表示下载过程中发生的错误。 * @param throwable 引发失败的异常或错误。 */ data class Error(val throwable: Throwable) : DownloadStatus() /** * 表示下载成功完成。 * @param file 下载完成的文件。 */ data class Done(val file: File) : DownloadStatus() } Flow与Room的应用 ...