一、协程协程,康复挂起,让异步像同步
重要的说三遍
协程协程,康复挂起,让异步像同步
协程协程,康复挂起,让异步像同步
协程协程,康复挂起,让异步像同步
经常有这么一种感觉,读完一本小说,内容根本终将都是遗忘,能记住一句话许多时分都实属难得。
那么假如说起协程只能留个一个形象,那么可所以:
协程的中心是挂起(suspend)和康复(resume),最经典的运用便是让几个异步办法能以类似同步的办法排队去履行。
爱与和平,比方与爱
幻想一下,你正在看一部十分引人入胜的电影,可是突然你想上厕所。你莫非会错失电影中精彩的部分吗?当然不会。你会“暂停”电影,上完厕所再“康复”播映。这便是协程的中心概念:挂起与康复。
你能够把协程幻想成一个大厨,他正在烹饪多道菜品。当一个菜品需求等候一段时间(比方煮沸或烘烤)时,大厨不会傻傻地等候,而是转向另一个使命。这便是协程的中心思想:挂起与康复。
-
协程有两个根本操作:挂起(suspend)和康复(resume)。
-
挂起不是停止履行,而是把操控权交还给调用者,直到咱们准备好康复履行。
-
康复便是咱们准备好持续履行时,从上次暂停的当地康复履行。
什么时分,需求用到协程?
协程,轻量级的线程办理工具。 什么时分用到? —— 履行异步使命 和 办理并发操作
-
异步、并发,用协程
-
异步、并发,用协程
-
异步、并发,用协程
-
处理堵塞操作:假如你需求在主线程上履行或许会堵塞的操作,例如网络恳求或数据库查询,那么你应该创立一个协程。在协程中履行这些操作能够防止堵塞主线程,坚持运用的呼应性。
-
并发履行使命:假如你需求一起履行多个使命,并在它们悉数完结后处理成果,你能够创立多个协程,并运用 async 函数或 withContext 函数来并发履行这些使命。
-
完结复杂的并发逻辑:协程供给了一些高档的并发原语,如通道(Channel)和流(Flow),你能够运用它们来完结更复杂的并发逻辑。
-
优化资源运用:协程是一种轻量级的线程,你能够创立很多的协程而不需求忧虑线程资源的开销。假如你需求在运用中履行很多的异步使命,那么运用协程或许会比运用线程更高效。
-
在 Android 开发中,你还能够运用协程来合作 LiveData 或 Flow 完结数据的异步加载和更新,以及合作 Room 数据库库进行异步查询操作等。
以上这些场景都是创立协程的常见状况。运用协程能够让异步代码的写法更挨近同步代码,更加直观易读,一起也让并发操作的操控更为简略。
最经典的场景
最经典的,无非便是,多个网络恳求,一同履行,让这些本来都是异步的代码,像同步相同地履行。
二、 协程的依靠
安卓项目中运用协程,先增加依靠
build.gradle
引进一下
// 协程依靠包
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
// 协程Android支持库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
三、先从最经典的场景说起
假定,有一个页面,需求用Retrofit,恳求2个接口,当且仅当两个接口都恳求成功时,汇总数据回来,更新UI。
其间,一个接口恳求用户详情,一个接口恳求文章数据,一个类用于汇总数据。
界说咱们的数据类和API接口:
- 接口办法被标记为
suspend
,这意味着它们是挂起函数,能够在协程中异步履行
// 用户详细信息数据类
data class UserDetails(/* ... */)
// 用户文章数据类
data class Post(/* ... */)
// 用户详细信息和文章的数据类
data class UserDetailsAndPosts(val userDetails: UserDetails, val userPosts: List<Post>)
// API接口界说
interface ApiService {
@GET("user/{userId}")
suspend fun fetchUserDetails(@Path("userId") userId: String): UserDetails
@GET("posts/{userId}")
suspend fun fetchUserPosts(@Path("userId") userId: String): List<Post>
}
创立Retrofit实例
- 咱们运用了
CoroutineCallAdapterFactory
,它允许咱们在 Retrofit 中运用协程。
val retrofit = Retrofit.Builder()
.baseUrl("https://your-api-url.com/") // API的根底URL
.addConverterFactory(GsonConverterFactory.create()) // 运用Gson转换器
.addCallAdapterFactory(CoroutineCallAdapterFactory()) // 运用协程适配器
.build()
val apiService = retrofit.create(ApiService::class.java) // 创立API服务实例
ViewModel中处理异步恳求
- 运用了
viewModelScope
来发动协程,这意味着假如 ViewModel 被铲除,一切的协程也会被撤销,然后防止了内存走漏。 - 用
async
函数。async
函数会发动一个新的协程并回来一个Deferred
目标,这个目标代表了一个未完结的协程。咱们能够调用Deferred
目标的await
办法来等候协程完结并获取成果。
class UserViewModel(private val apiService: ApiService) : ViewModel() {
val userDetailsAndPostsLiveData = MutableLiveData<UserDetailsAndPosts>()
fun fetchUserDetailsAndPosts(userId: String) {
// 在ViewModel的协程范围内发动协程
viewModelScope.launch {
val userDetailsDeferred = async {
try {
apiService.fetchUserDetails(userId) // 获取用户详细信息
} catch (e: Exception) {
// 处理 fetchUserDetails 的反常
null
}
}
val userPostsDeferred = async {
try {
apiService.fetchUserPosts(userId) // 获取用户文章列表
} catch (e: Exception) {
// 处理 fetchUserPosts 的反常
null
}
}
// 等候两个恳求完结并获取成果
val userDetails = userDetailsDeferred.await()
val userPosts = userPostsDeferred.await()
// 假如两个恳求都成功,创立 UserDetailsAndPosts 实例并更新 LiveData
if(userDetails != null && userPosts != null){
val userDetailsAndPosts = UserDetailsAndPosts(userDetails, userPosts)
userDetailsAndPostsLiveData.value = userDetailsAndPosts
}
}
}
}
在Activity中运用
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// 获取ViewModel实例
viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
//
上面代码,大概看个形象,能够看下下面的分析,也能够直接跳到第四大点。
上面的代码,到底讲了什么
咱们对这个运用 Kotlin 协程的网络恳求比方做一个分析和总结。
-
API接口界说:在这个比方中,咱们首先界说了 API 接口。运用 Retrofit 库,咱们能够经过创立接口并运用注解来界说网络恳求。最重要的是,咱们的接口办法被标记为
suspend
,这意味着它们是挂起函数,能够在协程中异步履行。 -
创立Retrofit实例:咱们创立了 Retrofit 客户端实例,并经过调用
create
办法创立 API 接口的实例。留意咱们运用了CoroutineCallAdapterFactory
,它允许咱们在 Retrofit 中运用协程。 -
在ViewModel中处理异步恳求:咱们在 ViewModel 中界说了一个 LiveData 目标来保存用户详细信息和文章的数据,然后创立了一个处理异步恳求的办法。在这个办法中,咱们运用了
viewModelScope
来发动协程,这意味着假如 ViewModel 被铲除,一切的协程也会被撤销,然后防止了内存走漏。为了并行履行两个网络恳求,咱们运用
async
函数。async
函数会发动一个新的协程并回来一个Deferred
目标,这个目标代表了一个未完结的协程。咱们能够调用Deferred
目标的await
办法来等候协程完结并获取成果。需求留意的是,这些协程都在viewModelScope
的范围内,因此它们的生命周期受 ViewModel 的操控。关于每个网络恳求,咱们运用
try-catch
块来处理或许的反常。假如恳求成功,咱们将成果保存到 LiveData 目标中;假如恳求失利,咱们能够根据需求处理反常。 -
在Activity中运用ViewModel:在 Activity 中,咱们获取 ViewModel 的实例,然后调查 LiveData 的改动来更新 UI。咱们还调用了
fetchUserDetailsAndPosts
办法来发动网络恳求。
这个比方展示了如何运用 Kotlin 协程来处理并行的异步使命,并经过 LiveData 目标将成果回来到 UI。Kotlin 协程供给了一种简洁的办法来办理异步操作,防止了回调阴间,使代码更易于阅览和保护。此外,经过正确运用 viewModelScope
,咱们能够确保在 ViewModel 被铲除时撤销一切的协程,然后防止了内存走漏。
上面这通解说分析,你也能够略过,下面开端正式比较系统地,说一说协程
四、协程,正式开打
四、 协程离不开的4个东西
suspend function : 即挂起函数
CoroutineScope : 即协程效果域
CoroutineContext: 即协程上下文
CoroutineBuilder: 即协程构建器
巴拉巴拉一堆名词。
写个最简略的协程代码,然后圈出来看一下。
了然了然一下
最简略的协程代码
fun log(msg: Any?) = println("ThreadName: [${Thread.currentThread().name}] threadId:[${Thread.currentThread().id}] $msg")
fun main() {
GlobalScope.launch(context = Dispatchers.IO) {
//延时一秒
delay(1000)
log("launch")
}
//主动休眠两秒,防止 JVM 过快退出
Thread.sleep(2000)
log("end")
}
输出:
ThreadName: [DefaultDispatcher-worker-1] threadId:[11] launch
ThreadName: [main] threadId:[1] end
那么,谁是 挂起函数function ?效果域Scope? 上下文Context? 构建器Build?
Emmm,F4的第一次见面,完毕。
五、协程的效果域 CoroutineScope
CoroutineScope效果域这玩意,有必要先讲,不然假如是刚入门的同学,或许会因为没先了解这个东西,整的云里雾里。GlobalScope这个效果域,它不会阻挠 JVM 完毕运转,这意味着!!!!!很或许JVM都完毕了,你的协程还在运转,然后问题就来了,你在代码里边写了一些输出句子,然后你死等,就等不到你的输出句子被打印出来。然后一脸懵逼,二脸懵逼,懵山懵海。尽量别用它
几个效果域
-
GlobalScope
尽量防止运用GlobalScope
-
CoroutineScope
常用 -
MainScope
-
runBlocking
-
viewModelScope
安卓专用,合作viewModel 常用 -
lifecycleScope
安卓专用,合作lifecycle 常用
-
GlobalScope:全局效果域,生命周期和运用相同长。尽管能够在任何当地发动协程,但在实践开发中尽量防止运用,以防止协程无法及时被撤销造成的内存走漏。
-
CoroutineScope:一个通用的协程效果域接口,能够自界说效果域的上下文,比方你能够创立一个
CoroutineScope
的实例并指定其生命周期。 -
MainScope:是 CoroutineScope 的一个实例,它在主线程上创立协程。通常用在 Android UI 线程的操作,需求手动进行撤销操作(OnDestory)以防止内存走漏。
-
runBlocking:这是一个特别的协程效果域,会堵塞当时线程并等候协程履行完毕。通常在测验和主函数中运用,开发中防止运用,因为它会堵塞线程,或许导致运用无呼应。
-
viewModelScope:这是 Android 架构组件供给的预界说效果域,专门为 ViewModel 设计,当 ViewModel 被铲除时,
viewModelScope
中的一切协程也会主动被撤销。在 Android 开发中,这是最常运用的效果域之一。 -
lifecycleScope:同样由 Android 架构组件供给,用于
LifecycleOwner
(如 Activity 或 Fragment)。当LifecycleOwner
的生命周期完毕时,lifecycleScope
中的一切协程会主动撤销。
效果域的开发场景代码
典型的运用场景
GlobalScope: 几乎不必
GlobalScope在 Android 开发中,GlobalScope 的运用场景较少
,首要运用在运用程序等级的后台使命中。例如,或许需求在整个运用程序生命周期内持续监听网络连接状况的改动,这时分就能够运用 GlobalScope。但这需求慎重处理,不然或许会导致内存走漏。
GlobalScope.launch {
// 这儿咱们在全局效果域发动一个新的协程,它会在整个运用程序生命周期内持续运转
// 这个比方中,咱们假定需求在运用程序生命周期内持续监听网络连接状况的改动
while (isActive) { // 在协程仍处于活动状况时持续履行
val isConnected = checkNetworkConnection() // 假定这是一个查看网络连接的挂起函数
if (!isConnected) {
notifyUser() // 假如网络断开,告诉用户
}
delay(5000) // 每 5 秒查看一次网络连接
}
}
CoroutineScope: 常用
CoroutineScope自界说效果域,用于创立一个具有特定生命周期和调度器的协程效果域,例如,能够用于处理一些长期运转的后台使命。
val job = Job()
val dispatcher = Dispatchers.IO
val customScope = CoroutineScope(job + dispatcher)
customScope.launch {
// 在自界说的协程效果域中发动一个新的协程。
// 这个协程的生命周期与 `job` 相关联,当咱们调用 `job.cancel()` 时,这个协程会被撤销。
// 这个协程会在 IO 调度器上运转,这合适履行 IO 密集型的使命,如网络恳求或磁盘读写。
val data = fetchDataFromNetwork() // 假定这是一个挂起函数,用于从网络加载数据
processData(data) // 处理数据
}
MainScope
MainScope:MainScope 在 Android 开发中通常用于在主线程上履行一些短暂的使命
,这些使命需求在用户界面上显现成果
。例如,咱们或许需求从网络上下载一张图片
,然后在主线程上更新用户界面
。不过,需求留意的是,与 viewModelScope 和 lifecycleScope 不同,MainScope
不会主动撤销协程,咱们需求在恰当的时分手动撤销协程
,例如在 Activity 的 onDestroy
办法中,不然或许会导致内存走漏
。
class MainActivity : AppCompatActivity() {
private val mainScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainScope.launch {
// 在主线程上获取数据
val data = fetchDataFromNetwork() // 假定这是一个挂起函数,用于从网络加载数据
// 在主线程上更新 UI
textView.text = data
}
}
override fun onDestroy() {
super.onDestroy()
// 当 Activity 毁掉时,撤销一切的协程以防止内存走漏
mainScope.cancel()
}
}
viewModelScope 常用
viewModelScope:在 ViewModel 中运转协程,当 ViewModel 整理时,一切的协程都会被主动撤销。合适用于触发数据加载等操作。
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
// 在 ViewModel 的效果域中发动一个新的协程
// 当 ViewModel 被整理时,这个协程会被主动撤销
val data = fetchDataFromNetwork() // 假定这是一个挂起函数,用于从网络加载数据
_data.value = data // 更新 LiveData
}
}
}
lifecycleScope 常用
lifecycleScope:在生命周期拥有者(如 Activity 或 Fragment)中运转协程,当生命周期完毕时,一切的协程都会被主动撤销。合适在 UI 操控器中触发数据加载等操作。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
// 在 Activity 的生命周期效果域中发动一个新的协程
// 当 Activity 毁掉时,这个协程会被主动撤销
val data = fetchDataFromNetwork() // 假定这是一个挂起函数,用于从网络加载数据
textView.text = data // 更新 UI
}
}
}
runBlocking
runBlocking:在当时线程堵塞并发动新协程。在 Android 中,runBlocking 首要用于测验中,因为在真实的 Android 设备上,咱们通常防止在主线程上堵塞。
@Test
fun testCoroutine() = runBlocking {
// 在测验中,咱们能够运用 `runBlocking` 在当时线程堵塞并发动新协程
// 这样能够确保在测验办法回来之前,协程内的一切操作都已完结
val data = fetchDataFromNetwork() // 假定这是一个挂起函数,用于从网络加载数据
assertEquals("expected data", data)
}
其实,关于开发开端运用
看到这儿,根本也就够了。
当然,可是,接着说,也是能够的!
六、协程的结构器 CoroutineBuilder
其实吧,这个东西,有点模糊。官方只说了launch和async是CoroutineBuilder。可是关于 runBlocking 和 coroutineScope,它们一起具有效果域和结构器的特性,可是咱们这儿,不把他们当做结构器,只认为 launch 和 async 是结构器。
协程的两个发动办法
-
launch
不关怀回来成果 Job -
async
能够回来成果 Deferred
launch
和 async
的比照
launch
和 async
都是用来创立新协程的结构器。它们的首要差异在于回来值
和意图
。
-
launch
用来发动一个新协程,这个协程不会回来任何成果(而是回来一个Job
目标)。它的首要用途是履行一些并发的操作,这些操作自身便是它们的意图,咱们并不关怀它们的回来值。咱们能够运用这个回来的Job
目标来操控这个协程,比方撤销它。 -
async
用来发动一个新协程,而且这个协程能够回来一个成果。async
回来一个Deferred
目标,这是一个特别的Job
,咱们能够运用await()
办法来获取这个协程的成果。假如这个协程还没有完结,await()
办法会挂起当时协程,直到这个协程完结。
Job
和 Deferred
的比照
Job
和 Deferred
都代表一个协程的生命周期和状况。它们的首要差异在于,Deferred
能够有一个成果。
-
Job
表示一个协程的生命周期。咱们能够调用Job
的cancel()
办法来撤销这个协程。Job
还有一些其他的办法和特点,用来查询协程的状况,比方isActive
、isCompleted
和isCancelled
。 -
Deferred
是Job
的一个子接口,它增加了一个await()
办法,用来获取协程的成果。当你调用await()
办法时,假如这个协程还没有完结,当时协程会被挂起,直到这个协程完结。因此,Deferred
是一个能够有成果的Job
。
launch
和 async
代码演示
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
private val job = Job() // 用来办理协程的生命周期
private val scope = CoroutineScope(Dispatchers.Main + job) // 在主线程上创立协程效果域
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// =========== 以下演示 launch
// 运用 launch 发动一个新协程,这个协程不会回来任何成果。首要用途是履行一些并发的操作
scope.launch {
try {
// 从网络获取用户数据
// 这儿的 fetchUserFromNetwork 是一个 suspend 函数,也便是一个挂起函数,它能够在协程中运转
val user = fetchUserFromNetwork()
// 在数据库中更新用户数据
updateUserInDatabase(user)
} catch (e: Exception) {
// 处理或许发生的反常,例如网络过错或数据库过错
handleException(e)
}
}
// =========== 以下演示 async
// 运用 async 发动一个新协程,而且这个协程能够回来一个成果。
// async 回来一个 Deferred 目标,这是一个特别的 Job,咱们能够运用 await() 办法来获取这个协程的成果。
val deferred: Deferred<User> = scope.async {
fetchUserFromNetwork()
}
// 运用 await 获取协程的成果
// 当你调用 await() 办法时,假如这个协程还没有完结,当时协程会被挂起,直到这个协程完结。
scope.launch {
try {
val user = deferred.await()
updateUserInDatabase(user)
} catch (e: Exception) {
handleException(e)
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // 撤销一切的协程
}
// 下面是一些模仿的函数
// suspend 关键字标明这是一个挂起函数。挂起函数能够在协程中运转,而且能够被挂起,而不会堵塞线程。
private suspend fun fetchUserFromNetwork(): User {
delay(1000) // 模仿网络恳求
return User() // 回来一个用户目标
}
private suspend fun updateUserInDatabase(user: User) {
delay(500) // 模仿数据库操作
}
private fun handleException(e: Exception) {
// 在这儿处理反常
}
}
class User {
// 用户类
}
这备注,能够说是相当详细了吧。
关于协程的结构器,能够了
七、协程的上下文
在 Android 开发中,以下几个协程上下文是比较常用的:
Dispatchers.Main
-
Dispatchers.Main:在主线程上履行协程代码,用于处理 UI 相关的操作,例如更新界面、响运用户交互等。在 Android 中,因为 UI 操作有必要在主线程上履行,因此运用
Dispatchers.Main
是十分常见的。(因为协程在主线程中履行,不应该在该上下文中进行耗时操作,防止堵塞主线程导致运用无呼应。)
Dispatchers.IO
-
Dispatchers.IO:用于履行 I/O 相关的操作,如网络恳求、文件读写等。它运用了一个线程池来运转协程代码,适用于履行长期的堵塞操作。在进行网络恳求、数据库操作或文件读写等 I/O 操作时,运用
Dispatchers.IO
能够防止堵塞主线程,进步运用程序的呼应性。
Dispatchers.Default
-
Dispatchers.Default:用于履行核算密集型操作,如数据处理或核算。它运用了一个线程池来运转协程代码,适用于履行耗费 CPU 资源的使命。当需求进行复杂的核算或处理很多数据时,运用
Dispatchers.Default
能够将这些操作分配到后台线程,防止堵塞主线程。
除了上述常用的协程上下文,还有一些其他的上下文也或许在特定的场景中运用:
Dispatchers.Unconfined
- Dispatchers.Unconfined:这个上下文不受任何特定线程的约束,协程会在康复后康复到任意线程上。它适用于一些无需特定线程约束的操作,但需求留意的是,因为协程在不同的线程之间切换,或许会导致上下文切换的开销。
CoroutineName
- CoroutineName:这个上下文用于为协程指定一个称号,以方便调试和追踪。在复杂的协程流程中,给协程命名能够帮助咱们更好地理解和跟踪协程的履行途径。
在 Android 开发中,常用的协程上下文是根据详细的需求和场景挑选的。首要重视需求在主线程上履行的 UI 操作、I/O 操作和核算密集型操作,并根据需求挑选恰当的协程上下文。
也便是说,日常运用根本都是Dispatchers.Main,Dispatchers.IO,Dispatchers.Default
Dispatchers.Main示例
- 适用场景:用于在主线程上履行协程使命,例如更新用户界面。
- 因为协程在主线程中履行,不应该在该上下文中进行耗时操作,防止堵塞主线程导致运用无呼应。
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 发动一个协程,在主线程中更新UI
CoroutineScope(Dispatchers.Main).launch {
val result = fetchUserData()
updateUI(result)
}
}
private suspend fun fetchUserData(): String {
// 模仿耗时操作
delay(1000)
return "User Data"
}
private fun updateUI(data: String) {
textView.text = data
}
}
Dispatchers.IO:
- 适用场景:用于履行触及 I/O 操作的协程使命,例如网络恳求、文件读写等。
- 这个上下文适用于履行会堵塞线程的 I/O 操作,如网络恳求、文件读写等。它会主动根据需求创立满足的线程池来处理使命。(不要在主线程中履行)
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import kotlin.coroutines.CoroutineContext
class MainActivity : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
job = Job()
launch {
val result = fetchDataFromNetwork()
updateUI(result)
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
// 在 IO 线程中履行网络恳求并读取数据
private suspend fun fetchDataFromNetwork(): String = withContext(Dispatchers.IO) {
val url = URL("https://api.example.com/data")
val connection = url.openConnection()
val inputStream = connection.getInputStream()
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
stringBuilder.append(line)
}
return@withContext stringBuilder.toString()
}
// 更新 UI 的函数
private fun updateUI(data: String) {
val textView = findViewById<TextView>(R.id.textView)
textView.text = data
}
}
Dispatchers.Default:
- 适用场景:用于履行 CPU 密集型的核算使命,例如排序、解析数据等。
- 这个上下文适用于履行耗费 CPU 资源较多的核算使命,如排序、解析数据等。它会根据需求创立满足的线程池来处理使命。
class MainActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
scope.launch(Dispatchers.Default) {
// 在 Default 线程池上履行协程中的核算操作
val result = performCalculation()
withContext(Dispatchers.Main) {
// 切换回主线程更新界面
updateUI(result)
}
}
}
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 撤销协程
}
private suspend fun performCalculation(): Int {
delay(1000) // 模仿核算操作
return 42
}
private fun updateUI(result: Int) {
// 在 UI 线程上更新界面
textView.text = "Result: $result"
}
}
八、协程 康复和挂起
挂起(suspend)和康复(resume)是协程的两个中心操作。
简略来说:
-
挂起suspend
便是暂停当时协程的履行 -
康复resume
便是重新开端协程的履行。
咱们能够把协程幻想成一部正在播映的电影。当你需求脱离一会(比方接个电话),你或许会按下”暂停”按钮,这便是挂起协程。当你准备好持续观看时,你会按下”播映”按钮,这便是康复协程。
-
挂起函数(Suspend function)是Kotlin协程中的一种特别函数,它能够在不堵塞当时线程的状况下暂停协程的履行。Kotlin标准库中的许多函数都是挂起函数,例如
delay()
和yield()
。 -
被
suspend
修饰之后函数就能够被 称之为 挂起函数
谁能够调用挂起函数
在 Kotlin 协程中,挂起函数只能在以下环境中被调用:
- 其他挂起函数:一个挂起函数能够调用其他的挂起函数。这意味着你能够在一个挂起函数中调用另一个挂起函数,而不需求任何特别的语法或关键字。
- 协程构建器:挂起函数能够在协程构建器(如
launch
或async
)中被调用。这是发动一个新的协程并在其间调用挂起函数的常见办法。 - CoroutineScope:在一个 CoroutineScope 的扩展函数中,你能够直接调用挂起函数。
挂起suspend
函数的小比方
看代码吧
咱们前面说过,delay()自身也是一个挂起函数
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("协程开端履行 - ${Thread.currentThread().name}")
delay(1000L) // 这是一个挂起函数
println("协程康复履行 - ${Thread.currentThread().name}")
}
println("主线程持续履行 - ${Thread.currentThread().name}")
}
在上面的代码中,launch
函数发动了一个新的协程,并在内部调用了delay(1000L)
。这个delay
函数便是一个挂起函数,它会暂停协程的履行1秒钟(但不会堵塞线程),然后康复协程的履行。
这段代码的输出将是:
协程开端履行 - main
主线程持续履行 - main
协程康复履行 - main
咱们看到,输出次序是 开端 —— 持续 ——康复。
留意调查,尽管咱们的协程在等候时,主线程并没有被堵塞,而是持续履行了下一行代码。协程的暂停并不会堵塞线程。
再来一个比方
class MainActivity : AppCompatActivity() {
private val coroutineScope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
coroutineScope.launch {
Log.d("协程示例", "onCreate: 协程开端")
try {
val result = withContext(Dispatchers.IO) {
performNetworkRequest()
}
Log.d("协程示例", "onCreate: 收到网络呼应: $result")
// 在这儿处理网络呼应
} catch (e: Exception) {
Log.e("协程示例", "onCreate: 网络恳求失利", e)
// 处理网络恳求失利的状况
}
}
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
suspend fun performNetworkRequest(): String {
Log.d("协程示例", "performNetworkRequest: 开端网络恳求...")
delay(2000) // 模仿网络恳求的推迟
return "网络呼应"
}
}
打印输出:
onCreate: 协程开端
performNetworkRequest: 开端网络恳求...
onCreate: 收到网络呼应: 网络呼应
常见的挂起函数
被 suspend 修饰的函数便是挂起函数,协程也自带一些挂起函数
协程的挂起函数有许多,比方 delay
,join
,await
,withContext
,yield
,withTimeout
等等。这些函数的效果和特点如下:
-
delay
:推迟一段时间再持续履行协程。 -
join
:等候一个协程的完结。 -
await
:等候一个协程的成果。 -
withContext
:在特定的上下文(比方在不同的线程或调度器)中履行代码。 -
yield
:让出CPU的操控权,让其他协程有时机履行。 -
withTimeout
:设置协程的超时时间。
以下是一个比方,结合 Android 的 Activity,演示如何运用这些函数:
import kotlinx.coroutines.*
class MyActivity : AppCompatActivity() {
// 创立一个协程的上下文
private val scope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创立并发动一个协程
scope.launch {
val result = getDataFromNetwork() // 这个函数是一个挂起函数
// 这儿能够更新UI
textView.text = result
}
}
// 从网络获取数据的挂起函数
private suspend fun getDataFromNetwork(): String {
// withContext 能够改动协程的上下文
return withContext(Dispatchers.IO) {
// 假定咱们在这儿运用了一个网络恳求库,比方Retrofit
// 它的函数通常也是挂起函数,能够在协程中直接调用
// 这儿咱们简略模仿一下网络恳求
delay(1000) // 模仿网络推迟
"获取到的数据"
}
}
override fun onDestroy() {
super.onDestroy()
// 当Activity毁掉时,撤销一切的协程以防止内存走漏
scope.cancel()
}
}
单独演示下常用的 withContext
class MainActivity : AppCompatActivity() {
// 创立一个 CoroutineScope,运用 Dispatchers.Main 作为默许的调度器
val scope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 运用 launch 开启一个协程
scope.launch {
Log.d("MainActivity", "开端协程,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
// 运用 withContext 切换到 IO 调度器
withContext(Dispatchers.IO) {
downloadDataFromNetwork() // 模仿耗时操作,比方网络恳求
// 运用 withContext 再次切换到 Main 调度器
withContext(Dispatchers.Main) {
Log.d("MainActivity", "切换回 Main 调度器,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
// 在主线程更新 UI
// textView.text = "更新后的文本"
}
// 运用 withContext 切换到 Default 调度器
withContext(Dispatchers.Default) {
// 耗时核算
Log.d("MainActivity", "切换到 Default 调度器,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
}
}
}
}
// 模仿从网络下载数据的挂起函数
private suspend fun downloadDataFromNetwork(): String {
// withContext 能够改动协程的上下文
return withContext(Dispatchers.IO) {
Log.d("MainActivity", "切换到 IO 调度器,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
Log.d("MainActivity", "开端下载数据,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
delay(1000) // 模仿网络下载推迟
Log.d("MainActivity", "数据下载完结,当时线程: ${Thread.currentThread().name}") // 打印日志,显现当时线程名
"下载的数据" // 回来下载的数据
}
}
override fun onDestroy() {
super.onDestroy()
// 当 Activity 毁掉时,撤销一切协程
scope.cancel()
}
}
输出
开端协程,当时线程: main
切换到 IO 调度器,当时线程: DefaultDispatcher-worker-1
开端下载数据,当时线程: DefaultDispatcher-worker-1
数据下载完结,当时线程: DefaultDispatcher-worker-1
切换回 Main 调度器,当时线程: main
切换到 Default 调度器,当时线程: DefaultDispatcher-worker-1
emm,根本写到这儿的话,日常开发也够用了。不写了太长了这个文章
番外篇 协程到底是不是在单线程里并发?仍是不多线程?
协程或许在单线程,也或许是多线程。详细要取决于Dispatcher
协程自身并不直接与线程对应,它是一种轻量级的线程,能够在单个线程内并发履行,也能够在多个线程之间切换。一个协程能够在一个线程中发动,然后在另一个线程中暂停,然后再在另一个线程中康复。
详细来说,协程的运转取决于它的调度器(Dispatcher)
。调度器决定了协程在哪个线程或哪些线程上履行。比方 Dispatchers.Main
是主线程调度器,Dispatchers.IO
是专门用于磁盘和网络 IO 读写的调度器,Dispatchers.Default
是用于 CPU 密集型使命的调度器,Dispatchers.Unconfined
是一个特别的调度器,它没有特定的线程,会在当时线程当即履行协程,假如协程中有挂起点,它会在康复时持续在其他合适的线程履行。
总的来说,协程既能够在单线程中并发运转,也能够在多线程中运转,取决于你的需求和运用的调度器
。可是,与线程相比,协程更轻量级,能够创立成千上万个而不会对性能造成大的影响。
来比方啊
在单线程内并发运转多个协程
import kotlinx.coroutines.*
fun main() = runBlocking {
// 在主线程发动两个协程
launch {
println("协程1在 ${Thread.currentThread().name} 线程运转")
delay(1000L)
println("协程1在 ${Thread.currentThread().name} 线程完毕")
}
launch {
println("协程2在 ${Thread.currentThread().name} 线程运转")
delay(1000L)
println("协程2在 ${Thread.currentThread().name} 线程完毕")
}
}
这个比方中,咱们在主线程中发动了两个协程,它们会并发履行,但都在同一个线程内。
在多线程之间切换协程:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 在主线程发动协程
launch(Dispatchers.Main) {
println("协程在 ${Thread.currentThread().name} 线程开端")
// 运用 withContext 切换到 IO 线程
withContext(Dispatchers.IO) {
println("协程在 ${Thread.currentThread().name} 线程运转")
}
println("协程在 ${Thread.currentThread().name} 线程完毕")
}
}
这个比方中,咱们首先在主线程中发动了一个协程,然后运用 withContext(Dispatchers.IO)
切换到了 IO 线程,然后又回到了主线程。咱们能够在日志中看到协程在不同的线程之间切换。