王学岗协程(六)————Flow与文件下载的应用

文件下载是flow最经典的应用。
在这里插入图片描述

应用很简单,就是点击下载,更新进度条。

核心类

package com.dongnaoedu.flowpractice.download

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.IOException
//导入我们自己写的copyTo,覆盖系统的
import com.dongnaoedu.flowpractice.utils.copyTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOn

/**
 *单例模式(object)就可以,
 */
object DownloadManager {
    //返回一个Flow,里面保存下载的进度,异常,完成,错误等信息,所以Flow需要指定泛型类型
    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) {//响应成功
                //body是个可空类型,我们使用!!强制执行,如果body为空,就报空指针异常。
                response.body()!!.let { body ->
                    val total = body.contentLength()
                    //文件读写(while循环),使用use函数
                    file.outputStream().use { output ->
                        val input = body.byteStream()
                        var emittedProgress = 0L
                        //copyTo是我们自己写的一个扩展函数
                        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 {
                //response没有响应成功,就抛个异常
                throw IOException(response.toString())
            }
            //flow的catch操作符,把上游的异常可以catch到,
        }.catch {
            file.delete()
            //把catch到的异常it发送出去
            emit(DownloadStatus.Error(it))
            //指定调度器,IO操作放到IO调度器上
        }.flowOn(Dispatchers.IO)

    }

}

bean类

package com.dongnaoedu.flowpractice.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()
    //下载完成,file:文件放到哪里了
    data class Done(val file: File) : DownloadStatus()

}

扩展函数

package com.dongnaoedu.flowpractice.utils

import java.io.InputStream
import java.io.OutputStream
//边读边写
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
}

在Fragment中使用

package com.dongnaoedu.flowpractice.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.dongnaoedu.flowpractice.R
import com.dongnaoedu.flowpractice.databinding.FragmentDownloadBinding
import com.dongnaoedu.flowpractice.databinding.FragmentHomeBinding
import com.dongnaoedu.flowpractice.download.DownloadManager
import com.dongnaoedu.flowpractice.download.DownloadStatus
import kotlinx.coroutines.flow.collect
import java.io.File


class DownloadFragment : Fragment() {

    val URL = "http://192.168.1.4:8080/kotlinstudyserver/pic.JPG"

    private val mBinding: FragmentDownloadBinding by lazy {
        FragmentDownloadBinding.inflate(layoutInflater)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return mBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //启动协程,lifecycleScope与Activity/Fragment生命周期绑定,fragment移除的时候,协程就会取消
        lifecycleScope.launchWhenCreated {
            //context可能为空,使用apply操作符
            context?.apply {
                //本地文件
                val file = File(getExternalFilesDir(null)?.path, "pic.JPG")
                //collect会引发下载,collect是挂起函数,必须写在协程里面
                DownloadManager.download(URL, file).collect { status ->
                    //根据不同的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 {
                                progressBar.progress = 100
                                tvProgress.text = "100%"
                            }
                            Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show()
                        }
                        else -> {
                            Log.d("ning", "下载失败.")
                        }
                    }
                }
            }
        }

    }

}

界面布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    tools:context=".fragment.DownloadFragment">


    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:max="100" />

    <TextView
        android:id="@+id/tv_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/progressBar"
        android:layout_centerHorizontal="true"
        android:textSize="20sp"
        tools:text="10%" />
</RelativeLayout>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值