Android入门教程 | Kotlin协程入门
Android官方推荐使用协程来处理异步问题。
协程的特点:
- 轻量:单个线程上可运行多个协程。协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
- 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
- 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
- Jetpack集成:许多Jetpack库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可用于结构化并发。
引入:
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.IO
。Default
用的也是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)
}
}
}
看一下这个网络请求的流程
- 从主线程调用
reqGet()
函数 viewModelScope.launch(Dispatchers.IO)
在协程上发出网络请求- 在协程中进行网络操作。把结果发送出去。
- 你的第一个协程程序
- 桥接阻塞与非阻塞的世界
- 等待一个任务
- 结构化的并发
- 作用域构建器
- 提取函数重构
- ......
- 取消协程的执行
- 取消是协作的
- 使计算代码可取消
- 在 finally 中释放资源
- 运行不能取消的代码块
- 超时
- 通道基础
- 关闭与迭代通道
- 构建通道生产者
- 管道
- 使用管道的素数
- 扇出
- 扇入
- 带缓冲的通道
- 通道是公平的
- 计时器通道
- 默认顺序调用
- 使用 async 并发
- 惰性启动的 async
- async 风格的函数
- 使用 async 的结构化并发
- 调度器与线程
- 非受限调度器 vs 受限调度器
- 调试协程与线程
- 在不同线程间跳转
- 上下文中的任务
- 子协程
- 父协程的职责
- 命名协程以用于调试
- 组合上下文中的元素
- 通过显式任务取消
- 线程局部数据
- 异常的传播
- CoroutineExceptionHandler
- 取消与异常
- 异常聚合
- 监督
- 在通道中 select
- 通道关闭时 select
- Select 以发送
- Select 延迟值
- 在延迟值通道上切换
- 问题
- volatile 无济于事
- 线程安全的数据结构
- 以细粒度限制线程
- 以粗粒度限制线程
- 互斥
- Actors
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)
- Android|Android install 多个设备时指定设备