• 写在前面
    下载功能是非常常用的功能,今天我们要通过kotlin协程和retrofit来是实现文件下载的功能.retorfit本身可以将请求结果以InputStream的形式返回,拿到InputStream,我们再将数据写入文件的outputStream,就可以实现文件的下载了.在java时我们也是这么做的.但问题是,我们在IO操作时都是在子线程操作,而下载过程中我还需要在UI显示下载状态,所以在java的方式中我们一般需要通过listener回调的方式来更新UI.我们在上一篇文章中看到,使用kotlin协程和retrofit进行网络请求并没有用回调,而且代码和rxjava一样简洁.下面来通过一个小demo来看看如何使用协程和retrofit进行网络下载.

  • 项目配置
    项目配置参见上篇文章

  • 代码实现
    话不多说先上图

    布局代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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"xmlns:app="http://schemas.android.com/apk/res-auto"><EditTextandroid:id="@+id/et_url"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_constraintTop_toTopOf="parent"android:layout_marginTop="8dp"android:layout_marginStart="8dp"android:layout_marginEnd="8dp"android:text="http://i3.3conline.com/images/piclib/201112/06/batch/1/119786/1323168166282kf5i4y67qh.jpg"tools:ignore="Autofill,LabelFor,TextFields" /><Buttonandroid:id="@+id/btn_down"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/et_url"app:layout_constraintStart_toStartOf="@id/et_url"android:layout_marginTop="8dp"android:text="下载"/><TextViewandroid:id="@+id/tv_download_state"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/btn_down"app:layout_constraintStart_toStartOf="parent"android:text="等待下载"android:layout_marginStart="8dp"android:layout_marginTop="8dp"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    最上面是一个EditText输入框,输入完整的下载url,点击下面的下载按钮就可以执行下载,最下面的文字是显示下载状态的,显示下载状态我为什么用TextView而不用progressbar?因为progressBar可以在子线程更新,但是TextView必须在UI线程更新,这个Android开发的老鸟应该都知道的,为了demo展示线程的切换所以我用了TextView.
    下面来实现用于网络请求的interface,这里我需要对上篇文章的interface略作改造,由于kotlin的interface比java的interface要强大,我们可以借助伴生对象(companion object)直接在interface中完成retrofit的初始化操作.由于网络请求类在项目中一般只存在一个实例,所以我们以单例的方式完成初始化.关于kotlin的单例我在之前的文章中也讲过,代码如下

    package com.meijian.kotlinnetworkdemoimport okhttp3.ResponseBody
    import retrofit2.Call
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    import retrofit2.http.GET
    import retrofit2.http.Path
    import retrofit2.http.Streaming
    import retrofit2.http.Urlinterface GitHubService {@GET("users/{user}/repos")fun getListRepos(@Path("user") user: String): Call<List<Repo>>//改操作用于下载文件,url传入下载的全路径,Streaming在大文件下载时必须添加,ResponseBody封装下载的流@Streaming@GETfun downloadFile(@Url url:String):Call<ResponseBody>//伴生对象,伴生对象里的方法和属性类似于java的staticcompanion object {//Volatile 注解同java中的 volatile关键字,表示属性更新后在其他线程立即可见@Volatileprivate var instance:GitHubService? = null/*这里的 "?:" 是Elvis表达式,意思是前面的不为null,直接返回前面的,如果前面的为null,执行后面的操作,这里使用了两次,就等价于java单例中的double-check*/fun getInstance(): GitHubService = instance?: synchronized(GitHubService::class.java){instance?:Retrofit.Builder().baseUrl("https://api.github.com/").addConverterFactory(GsonConverterFactory.create()).build().create(GitHubService::class.java).also { instance = it }}}
    }
    

    下载的Activity,和上一篇文章一样,需要实现CoroutineScope,代码如下:

    class DownloadActivity : AppCompatActivity(), CoroutineScope {//job用于控制协程private lateinit var job: Job//继承CoroutineScope必须初始化coroutineContext变量// 这个是标准写法,+其实是plus方法前面表示job,用于控制协程,后面是Dispatchers,指定启动的线程override val coroutineContext: CoroutineContextget() = job + Dispatchers.Mainoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//在onCreate中初始化jobjob = Job()setContentView(R.layout.activity_download)}override fun onDestroy() {job.cancel()super.onDestroy()}
    }
    

    这一部分和上一篇文章一样我就不重复了,不过我要说明的是,实际项目中下载一般不会放在activity中,如果启动下载的任务确实是在acitivity或者fragment,那么在onDestory中本不应该调用job.cancel(),否则在actvity关闭时下载任务也没了,这不合常理,但如果下载是在service中,应该在service关闭时调用job.cancel()
    下面只有最后一步了,点击监听器

    btn_down.setOnClickListener {//这里直接从IO线程中启动launch(Dispatchers.IO) {//切换到主线程操作UIwithContext(Dispatchers.Main) {btn_down.visibility = View.GONE}//切回IO线程创建下载的文件val url = et_url.text.toString()val file =File("${Environment.getExternalStorageDirectory().path}/download/${url.substringAfterLast("/")}")file.createNewFile()//通过GitHubService.getInstance()可以直接拿到GitHubService对象val response = GitHubService.getInstance().downloadFile(url).execute()val body = response.body()if(response.isSuccessful && body!=null){var inStream: InputStream? = nullvar outStream: OutputStream? = null/*注意,在kotlin中没有受检异常,如果这里不写try catch,编译器也是不会报错的,但是我们需要确保流关闭,所以需要在finally进行操作*/try {//以下读写文件的操作和java类似inStream = body.byteStream()outStream = file.outputStream()//文件总长度val contentLength = body.contentLength()//当前已下载长度var currentLength = 0L//缓冲区val buff = ByteArray(1024)var len = inStream.read(buff)var percent = 0while (len != -1) {outStream.write(buff, 0, len)currentLength += len/*不要频繁的调用切换线程,否则某些手机可能因为频繁切换线程导致卡顿,这里加一个限制条件,只有下载百分比更新了才切换线程去更新UI*/if((currentLength * 100/ contentLength).toInt()>percent){percent = (currentLength / contentLength * 100).toInt()//切换到主线程更新UIwithContext(Dispatchers.Main) {tv_download_state.text = "正在下载:$currentLength / $contentLength"}//更新完成UI之后立刻切回IO线程}len = inStream.read(buff)}//下载完成之后,切换到主线程更新UIwithContext(Dispatchers.Main) {tv_download_state.text = "下载完成"btn_down.visibility = View.VISIBLE}} catch (e: Exception) {e.printStackTrace()} finally {inStream?.close()outStream?.close()}}}}
    

    好了,这就完成了,上面代码看起来是不是很复杂,其实未封装的java代码下载更复杂,这里重点在于展示kotlin在同一个协程中如何切换线程,这有点类似于rxjava的线程切换.

    原创文章,转载请注明出处,谢谢

Android中使用Kotlin协程(Coroutines)和Retrofit进行网络请求(二)之文件下载相关推荐

  1. Android中使用Kotlin协程代替RxJava封装网络请求

    现在的Android项目普遍使用Retrofit+RxJava的组合实现网络接口请求与数据的展现.这一功能通过Kotlin语言的协程功能也可以很方便的实现. 相比较而言,RxJava功能过于强大,如果 ...

  2. 在 Android 开发中使用 Kotlin 协程 (一) -- 初识 Kotlin 协程

    前言 最近在研究 Kotlin 协程,发现功能真的超级强大,很有用,而且很好学,如果你正在或计划使用 Kotlin 开发 Android,那么 Kotlin 协程你一定不能错过! 协程是什么? 我们平 ...

  3. android 协程,Android 上的 Kotlin 协程

    协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码.协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念. 在 Android 上,协程有助于 ...

  4. 枯燥的Kotlin协程三部曲(上)——概念启蒙篇

    0x0.引言 Kotlin 1.3 版本开始引入协程 Coroutine,简练的官方文档和网上一堆浅尝辄止的文章让我心里有些没底,不想止步于仅仅知道: ① Android中,Kotlin协程用于解决: ...

  5. Kotlin 协程探索

    文章目录 Kotlin 协程是什么? suspend 是什么? 总结 Kotlin 协程是什么? 本文只是自己经过研究后,对 Kotlin 协程的理解概括,如有偏差,还请斧正. 简要概括: 协程是 K ...

  6. android studio放置在函数上面看_Android中用Kotlin协程和Retrofit进行网络请求和取消请求...

    前面两篇文章介绍了协程的一些基本概念和基本知识,这篇则介绍在Android中如何使用协程配合Retrofit发起网络请求,同时介绍在使用协程时如何优雅的取消已经发起的网络请求. 需要文章中demo完整 ...

  7. pdf 深入理解kotlin协程_协程初探

    Hello,各位朋友,小笨鸟我回来了! 近期学习了Kotlin协程相关的知识,感觉这块技术在项目中的可应用性很大,对项目的开发效率和维护成本有较大的提升.于是就考虑深入研究下相关概念和使用方式,并引入 ...

  8. Kotlin协程-Coroutines-原汁原味一篇就够了系列

    文章目录 Kotlin协程-Coroutines 1. 协程概述 1.1 来自官方的解释:[Coroutines Guide - Kotlin Programming Language](https: ...

  9. 一文快速入门 Kotlin 协程

    一.Kotlin 协程 Kotlin 协程提供了一种全新处理并发的方式,你可以在 Android 平台上使用它来简化异步执行的代码.协程从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生 ...

最新文章

  1. 使用keepalived监控tomcat 达到双机热备
  2. 故宫的“烧脑奇书”又火了!豆瓣9.2分,11种结局,可以玩一年!
  3. 让大数据分析更简单,4步教你玩转MongoDB BI Connector
  4. 04-linux下安装neo4j
  5. html关于计时的函数,关于JavaScript获取时间函数及实现倒计时
  6. 关于 bind 你可能需要了解的知识点以及使用场景
  7. 【计算机网络】第一部分 概述(1) 数据通信和网络绪论
  8. 【100个 Unity小知识点】☀️ | Unity中显示运行时游戏帧率的方法
  9. 东方通TongWeb部署应用中文件不下载而在页面打开
  10. matlab sil,丰田使用高精度发动机模型和SIL+M前置开发发动机控制系统
  11. google谷歌云盘_如何在酒店房间使用Google Chromecast?
  12. Google新人的成长思考
  13. mysql etc my.cnf_Mysql数据库服务器配置文件/etc/my.cnf的详细配置
  14. 6096. 咒语和药水的成功对数
  15. 【历史上的今天】11 月 21 日:第一个阿帕网连接建立;乐视网成立;爱迪生发明留声机
  16. python中if语句的实例_python的if语句里怎样写两个条件
  17. java 简单文件加密
  18. k8s创建service
  19. 计算机专业背景的大学,不要求专业背景的计算机专业!
  20. Qt6实战教程:媒体播放器示例

热门文章

  1. 程序员不止摆这些摊,一起看看真实的程序员是什么样的吧!
  2. python定制手机套餐_利用Python实现高度定制专属RSS
  3. JS学习之数组的长度
  4. Vue中TipTap富文本编辑器的输入框内部分样式无法显示
  5. 用 Python 写一个颜值测试小工具
  6. 在win7虚拟机中无法启用键盘及失效的解决办法(PS/2标准键盘)
  7. 队列的入队、出队基本操作
  8. 可用的游戏代练系统源码
  9. 激励孩子的良言妙语(家长每天都说一说)
  10. “秋老虎”来了:养生必备宝典