Android 上的 Kotlin 协程(Coroutines)
官网介绍:
https://developer.android.com/kotlin/coroutines
一、协程的概念
协程(Coroutines)是一种并发设计模式,可以在 Android 平台上使用它来简化异步执行的代码。
协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。
在 Android 上,协程有助于管理长时间运行的任务.
二、协程的特点
协程是在 Android 上进行 异步编程 的推荐解决方案。值得关注的特点包括:
(1)轻量:可以在单个线程上运行多个协程,
因为协程支持挂起,不会使正在运行协程的线程阻塞。
挂起比阻塞 节省内存 ,且支持多个 并行操作。
(2)内存泄漏更少:使用结构化并发机制 在 一个作用域 内执行 多项操作。
(3)内置取消支持:取消操作 会自动 在运行中的 整个协程层次结构 内传播。
(4)Jetpack 集成:许多 Jetpack 库 都包含提供全面 协程支持 的 扩展。
某些库还提供自己的协程作用域,可供用于结构化并发。
三、示例
1. 示例概览
实现:发出网络请求 并 将结果返回到 主线程,然后应用可以在 主线程上 向用户 显示结果。
具体而言,ViewModel 架构组件 会在 主线程 上调用 代码库层,以触发 网络请求。
会介绍多种使用 协程 确保 主线程畅通的解决方案:
withContext()函数 转移线程 , 并 结合 suspend( 挂起)关键字 强制从协程里 调用.
viewModelScope.launch 启动新协程
ViewModel 包含一组可直接 与协程配合 使用的 KTX 扩展。这些扩展是 lifecycle-viewmodel-ktx 库.
2. 依赖项信息
应用的 build.gradle
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}
3. 具体类
3.1 结果类
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
3.2 存储库类,触发 网络请求.
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
}
分析:
(1) 使用 withContext()函数将 协程的执行操作 移至其他线程( I/O 线程)
(2) suspend 关键字进行标记函数,强制从协程内调用它.
3.3 登录ViewModel, 在主线程调用 存储库 类.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
分析:
(1) 应用从主线程上的 View 层调用 login() 函数。
(2) launch 在主线程上创建新协程,然后协程开始执行。
(3) 在协程内,调用 loginRepository.makeLoginRequest()
现在会挂起协程的进一步执行操作,
直至 makeLoginRequest() 中的 withContext 块结束运行。
(4) withContext 块结束运行后,
login() 中的协程在主线程上恢复执行操作,
并返回网络请求的结果。
(5) 处理 Repository 层可能抛出的异常, 使用的是 try-catch 块
4. 一些总结:
(1) viewModelScope.launch 里面调用 使用suspend 声明 且 使用了withConext()(转移到其它调度程序的某个线程的)函数.
(2) viewModelScope 与界面生命周期绑定(默认在主调度程序 Dispatchers.Main),
因此,当用户离开屏幕时,此协程启动的操作将自动取消。
(3) 当协程调用标记为 suspend 的函数时,
它不会像常规函数调用一样在函数返回之前进行阻塞,而是挂起执行,
直到结果就绪为止,然后从上次停止的位置恢复并使用返回的结果。
当它挂起并等待结果时,它会取消阻塞正在运行它的线程,以便其他函数或协程可以运行。
(Kotlin编译器会把suspend 声明的函数,转换成callback形式的代码)
(4) 重要提示:suspend 关键字不指定运行代码的线程。挂起函数可以在后台线程或主线程上运行。
(5) 一个协程里可以运行多个线程
(6) suspend function 的伪代码介绍
suspend function.JPG
(6) Coroutine 的基本内容(Android 官方介绍)
coroutine 内容.JPG
-- End --