Android入门教程 | Kotlin协程入门

Android官方推荐使用协程来处理异步问题。
协程的特点:

  • 轻量:单个线程上可运行多个协程。协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack集成:许多Jetpack库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可用于结构化并发。
示例 首先工程中需要引入 Kotlin 与协程。然后再使用协程发起网络请求。
引入:
Android 工程中引入 Kotlin,参考 Android 项目使用 kotlin
有了Kt后,引入协程
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" // 协程 }

启动协程
不同于 Kotlin 工程直接使用 GlobalScope,这个示例在ViewModel中使用协程。需要使用viewModelScope
下面的 CorVm1 继承了 ViewModel。
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope // 引入 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launchclass CorVm1 : ViewModel() {companion object { const val TAG = "rfDevCorVm1" }fun cor1() { viewModelScope.launch { Log.d(TAG, "不指定dispatcher ${Thread.currentThread()}") } } }

在按钮的点击监听器中调用 cor1() 方法,可以看到协程是在主线程中的。
不指定dispatcher Thread[main,5,main]

由于此协程通过viewModelScope启动,因此在 ViewModel 的作用域内执行。如果 ViewModel 因用户离开屏幕而被销毁,则viewModelScope会自动取消,且所有运行的协程也会被取消。
launch() 方法可以指定运行的线程。可以传入Dispatchers来指定运行的线程。
先简单看一下kotlinx.coroutines包里的 Dispatchers ,它有4个属性:
  • Default,默认
  • Main,Android中指定的是主线程
  • Unconfined,不指定线程
  • IO,指定IO线程
都通过点击事件来启动
// CorVm1.ktfun ioCor() { viewModelScope.launch(Dispatchers.IO) { Log.d(TAG, "IO 协程 ${Thread.currentThread()}") } }fun defaultCor() { viewModelScope.launch(Dispatchers.Default) { Log.d(TAG, "Default 协程 ${Thread.currentThread()}") } }fun mainCor() { viewModelScope.launch(Dispatchers.Main) { Log.d(TAG, "Main 协程 ${Thread.currentThread()}") } }fun unconfinedCor() { viewModelScope.launch(Dispatchers.Unconfined) { Log.d(TAG, "Unconfined 协程 ${Thread.currentThread()}") } }

运行log
IO 协程 Thread[DefaultDispatcher-worker-1,5,main] Main 协程 Thread[main,5,main] Default 协程 Thread[DefaultDispatcher-worker-1,5,main] Unconfined 协程 Thread[main,5,main]

从上面的比较可以看出,如果想利用后台线程,可以考虑Dispatchers.IODefault用的也是DefaultDispatcher-worker-1线程。
模拟网络请求
主线程中不能进行网络请求,我们把请求放到为IO操作预留的线程上执行。一些信息用 MutableLiveData 发出去。
// CorVm1.kt val info1LiveData: MutableLiveData = https://www.it610.com/article/MutableLiveData()private fun reqGet() { info1LiveData.value ="发起请求" viewModelScope.launch(Dispatchers.IO) { val url = URL("https://www.baidu.com/s?wd=abc") try { val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "GET" conn.connectTimeout = 10 * 1000 conn.setRequestProperty("Cache-Control", "max-age=0") conn.doOutput = true val code = conn.responseCode if (code == 200) { val baos = ByteArrayOutputStream() val inputStream: InputStream = conn.inputStream val inputS = ByteArray(1024) var len: Int while (inputStream.read(inputS).also { len = it } > -1) { baos.write(inputS, 0, len) } val content = String(baos.toByteArray()) baos.close() inputStream.close() conn.disconnect() info1LiveData.postValue(content) Log.d(TAG, "net1: $content") } else { info1LiveData.postValue("网络请求出错 $conn") Log.e(TAG, "net1: 网络请求出错 $conn") } } catch (e: Exception) { Log.e(TAG, "reqGet: ", e) } } }

看一下这个网络请求的流程
  1. 从主线程调用reqGet()函数
  2. viewModelScope.launch(Dispatchers.IO)在协程上发出网络请求
  3. 在协程中进行网络操作。把结果发送出去。
kotlin 协程相关知识点 1. 协程基础
  • 你的第一个协程程序
  • 桥接阻塞与非阻塞的世界
  • 等待一个任务
  • 结构化的并发
  • 作用域构建器
  • 提取函数重构
  • ......
2. 取消与超时
  • 取消协程的执行
  • 取消是协作的
  • 使计算代码可取消
  • 在 finally 中释放资源
  • 运行不能取消的代码块
  • 超时
3. 通道
  • 通道基础
  • 关闭与迭代通道
  • 构建通道生产者
  • 管道
  • 使用管道的素数
  • 扇出
  • 扇入
  • 带缓冲的通道
  • 通道是公平的
  • 计时器通道
4. 组合挂起函数
  • 默认顺序调用
  • 使用 async 并发
  • 惰性启动的 async
  • async 风格的函数
  • 使用 async 的结构化并发
【Android入门教程 | Kotlin协程入门】5. 协程上下文与调度器
  • 调度器与线程
  • 非受限调度器 vs 受限调度器
  • 调试协程与线程
  • 在不同线程间跳转
  • 上下文中的任务
  • 子协程
  • 父协程的职责
  • 命名协程以用于调试
  • 组合上下文中的元素
  • 通过显式任务取消
  • 线程局部数据
6. 异常处理
  • 异常的传播
  • CoroutineExceptionHandler
  • 取消与异常
  • 异常聚合
  • 监督
7. select 表达式
  • 在通道中 select
  • 通道关闭时 select
  • Select 以发送
  • Select 延迟值
  • 在延迟值通道上切换
8. 共享的可变状态与并发
  • 问题
  • volatile 无济于事
  • 线程安全的数据结构
  • 以细粒度限制线程
  • 以粗粒度限制线程
  • 互斥
  • Actors
【Kotlin入门到精通全系列视频参考】

    推荐阅读