IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> OkHttp初探2:如何使用OkHttp进行下载封装?带进度条?Kotlin+Flow版本。 -> 正文阅读

[移动开发]OkHttp初探2:如何使用OkHttp进行下载封装?带进度条?Kotlin+Flow版本。

本文接上一篇博文:OkHttp初探:如何使用OkHttp进行Get或Post请求?Kotlin版本。

通用模块封装

这里封装一些通用的代码,先知道一下就可以了。

/**
 * 日志打印
 */
fun log(vararg msg: Any?) {
    val nowTime = SimpleDateFormat("HH:mm:ss:SSS").format(System.currentTimeMillis())
    println("$nowTime [${Thread.currentThread().name}] ${msg.joinToString(" ")}")
}

/**
 * 进度通用回调  不使用flow封装的话 使用这个
 */
internal typealias ProgressBlock = (state: DownloadState) -> Unit

/**
 * 下载状态机
 */
sealed class DownloadState {

    /**
     * 未开始
     */
    object UnStart : DownloadState()

    /**
     * 下载中
     */
    class Progress(var totalNum: Long, var current: Long) : DownloadState()

    /**
     * 下载完成
     */
    class Complete(val file: File?) : DownloadState()

    /**
     * 下载失败
     */
    class Failure(val e: Throwable?) : DownloadState()

    /**
     * 下载失败
     */
    class FileExistsNoDownload(val file: File?) : DownloadState()

}

下载文件,带进度,一般封装

fun downloadFile(url: String, destFileDirName: String, progressBlock: ProgressBlock) {
    //下载状态  默认未开始
    var state: DownloadState = DownloadState.UnStart
    progressBlock(state)

    // TODO: 2021/12/27 file 创建与判断可以封装
    /**
     * file 创建判断  可以封装
     */
    val file = File(destFileDirName)
    val parentFile = file.parentFile
    if (!parentFile.exists()) {
        parentFile.mkdirs()
    }
    if (file.exists()) {
        //文件存在 不需要下载
        state = DownloadState.FileExistsNoDownload(file)
        progressBlock(state)
        return
    } else {
        file.createNewFile()
    }

    //下载
    val okHttpClient = OkHttpClient()
    val request = Request.Builder().url(url).build()
    okHttpClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            state = DownloadState.Failure(e)
            progressBlock(state)
        }

        override fun onResponse(call: Call, response: Response) {
            response.use { res ->
                //完整长度
                var totalLength = 0L
                //写入字节
                val bytes = ByteArray(2048)
                val fileOutputStream = FileOutputStream(file)
                res.body?.also { responseBody ->
                    totalLength = responseBody.contentLength()
                }?.byteStream()?.let { inputStream ->
                    try {
                        var currentProgress = 0L
                        var len = 0
						state = DownloadState.Progress(totalLength, currentProgress)
                        do {
                            if (len != 0) {
                                currentProgress += len
                                fileOutputStream.write(bytes)
                            }
                            //状态改变
                            (state as DownloadState.Progress).current = currentProgress
                            progressBlock(state)
                            len = inputStream.read(bytes, 0, bytes.size)
                        } while (len != -1)
                        //状态改变完成
                        state = DownloadState.Complete(file)
                        progressBlock(state)
                    } catch (e: Exception) {
                        state = DownloadState.Failure(e)
                        progressBlock(state)
                    } finally {
                        inputStream.close()
                        fileOutputStream.close()
                    }
                }
            }
        }

    })

}

使用

    downloadFile(
        "https://dldir1.qq.com/weixin/Windows/WeChatSetup.exe",
        "download/WeChatSetup.exe"
    ) { state: DownloadState ->
        when (val s = state) {
            is DownloadState.Complete -> log("下载完成 文件路径为 ${s.file?.absoluteFile}")
            is DownloadState.Failure -> log("下载失败  ${s.e?.message}")
            is DownloadState.FileExistsNoDownload -> log("已经存在  ${s.file?.absoluteFile}")
            is DownloadState.Progress -> log("下载中  ${(s.current.toFloat() / s.totalNum) * 100}%")
            DownloadState.UnStart -> log("下载未开始")
        }
    }

使用flow封装

对于上述封装使用起来没有问题,但是如果在android上面要把进度显示出来的话,就需要手动切换到UI线程了。不太方便。既然都用kotlin了,那么为什么不解除协程Flow封装呢?

所以,下面基于Flow的封装就来了。直接切换到Main线程,美滋滋。

知识储备:
Kotlin:Flow 全面详细指南,附带源码解析。
Flow : callbackFlow使用心得,避免踩坑!

/**
 * 使用Flow改造文件下载
 * callbackFlow  可以保证线程的安全  底层是channel
 */
fun downloadFileUseFlow(url: String, destFileDirName: String) = callbackFlow<DownloadState> {
    var state: DownloadState = DownloadState.UnStart
    send(state)

    //获取文件对象
    val file = File(destFileDirName).also { file ->
        val parentFile = file.parentFile
        if (!parentFile.exists()) {
            parentFile.mkdirs()
        }
        if (file.exists()) {
            state = DownloadState.FileExistsNoDownload(file)
            send(state)
            //流关闭,返回
            close()
            return@callbackFlow
        } else {
            file.createNewFile()
        }
    }
    //下载
    val okHttpClient = OkHttpClient().newBuilder()
        .dispatcher(dispatcher)
        .writeTimeout(30, TimeUnit.MINUTES)
        .readTimeout(30, TimeUnit.MINUTES)
        .build()
    val request = Request.Builder()
        .url(url)
        .build()
    okHttpClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            //更新状态
            state = DownloadState.Failure(e)
            this@callbackFlow.trySendBlocking(state)
            close()
        }

        override fun onResponse(call: Call, response: Response) {
            //下载
            val body = response.body
            if (response.isSuccessful && body != null) {
                //完整长度
                val totalNum: Long = body.contentLength()
                //当前下载的长度
                var currentProgress: Long = 0L
                var len = 0

                response.use {
                    //等效于   FileOutputStream(file)  输出流
                    val outputStream = file.outputStream()
                    //输入流
                    val byteStream = body.byteStream()
                    try {
                        val bates = ByteArray(2048)

                        //设置状态对象拉出来,避免循环一直创建对象
                        state = DownloadState.Progress(totalNum, currentProgress)
                        //循环读写
                        do {
                            if (len != 0) {
                                currentProgress += len
                                outputStream.write(bates)
                            }
                            //更新进度
                            (state as DownloadState.Progress).current = currentProgress
                            this@callbackFlow.trySendBlocking(state)
                            len = byteStream.read(bates, 0, bates.size)
                        } while (len != -1)
                        //下载完成
                        state = DownloadState.Complete(file)
                        this@callbackFlow.trySendBlocking(state)
                    } catch (e: Exception) {
                        state = DownloadState.Failure(e)
                        this@callbackFlow.trySendBlocking(state)
                    } finally {
                        outputStream.close()
                        byteStream.close()
                        //关闭callbackFlow
                        this@callbackFlow.close()
                    }
                }

            } else {
                //更新状态且关闭
                state = DownloadState.Failure(Exception(response.message))
                this@callbackFlow.trySendBlocking(state)
                close()
            }
        }

    })
    //使用channelFlow 必须使用awaitClose 挂起flow等待channel结束
    awaitClose {
        log("callbackFlow关闭 .")
    }
}
    .buffer(Channel.CONFLATED) //设置 立即使用最新值 buffer里面会调用到fuse函数,继而调用到create函数重新创建channelFlow
    .flowOn(Dispatchers.Default) //直接设置callbackFlow执行在异步线程
    .catch { e ->
        //异常捕获重新发射
        emit(DownloadState.Failure(e))
    }

使用

    //这里使用runBlocking只是为了跑程序,一般和lifecycleScope等合作使用
    runBlocking(Dispatchers.Main) {
        downloadFileUseFlow(
            "https://dldir1.qq.com/weixin/Windows/WeChatSetup.exe",
            "download/WeChatSetup.exe"
        ).onEach { downloadState ->
            when (downloadState) {
                is DownloadState.Complete -> log("下载完成 文件路径为 ${downloadState.file?.absoluteFile}")
                is DownloadState.Failure -> log("下载失败  ${downloadState.e?.message}")
                is DownloadState.FileExistsNoDownload -> log("已经存在  ${downloadState.file?.absoluteFile}")
                is DownloadState.Progress -> log("下载中  ${(downloadState.current.toFloat() / downloadState.totalNum) * 100}%")
                DownloadState.UnStart -> log("下载未开始")
            }
        }.launchIn(this)
            .join()
    }

以上就是博主提供的两种简单的封装方式了。

后面会陆续推出OkHttp高阶使用,以及OkHttp源码分析博客。觉得不错关注博主哈~😎
创作不易,如有帮助一键三连咯🙆?♀?。欢迎技术探头噢!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-04 13:32:50  更:2022-01-04 13:33:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:23:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码