一、协程协程,康复挂起,让异步像同步

重要的说三遍

  • 协程协程,康复挂起,让异步像同步
  • 协程协程,康复挂起,让异步像同步
  • 协程协程,康复挂起,让异步像同步

经常有这么一种感觉,读完一本小说,内容根本终将都是遗忘,能记住一句话许多时分都实属难得。

那么假如说起协程只能留个一个形象,那么可所以:

协程的中心是挂起(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 协程的网络恳求比方做一个分析和总结。

  1. API接口界说:在这个比方中,咱们首先界说了 API 接口。运用 Retrofit 库,咱们能够经过创立接口并运用注解来界说网络恳求。最重要的是,咱们的接口办法被标记为 suspend,这意味着它们是挂起函数,能够在协程中异步履行。

  2. 创立Retrofit实例:咱们创立了 Retrofit 客户端实例,并经过调用 create 办法创立 API 接口的实例。留意咱们运用了 CoroutineCallAdapterFactory,它允许咱们在 Retrofit 中运用协程。

  3. 在ViewModel中处理异步恳求:咱们在 ViewModel 中界说了一个 LiveData 目标来保存用户详细信息和文章的数据,然后创立了一个处理异步恳求的办法。在这个办法中,咱们运用了 viewModelScope 来发动协程,这意味着假如 ViewModel 被铲除,一切的协程也会被撤销,然后防止了内存走漏。

    为了并行履行两个网络恳求,咱们运用 async 函数。async 函数会发动一个新的协程并回来一个 Deferred 目标,这个目标代表了一个未完结的协程。咱们能够调用 Deferred 目标的 await 办法来等候协程完结并获取成果。需求留意的是,这些协程都在 viewModelScope 的范围内,因此它们的生命周期受 ViewModel 的操控。

    关于每个网络恳求,咱们运用 try-catch 块来处理或许的反常。假如恳求成功,咱们将成果保存到 LiveData 目标中;假如恳求失利,咱们能够根据需求处理反常。

  4. 在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?

什么是TM的kotlin协程?就是靠 恢复和挂起,像同步一样写异步代码

Emmm,F4的第一次见面,完毕。


五、协程的效果域 CoroutineScope

CoroutineScope效果域这玩意,有必要先讲,不然假如是刚入门的同学,或许会因为没先了解这个东西,整的云里雾里。GlobalScope这个效果域,它不会阻挠 JVM 完毕运转,这意味着!!!!!很或许JVM都完毕了,你的协程还在运转,然后问题就来了,你在代码里边写了一些输出句子,然后你死等,就等不到你的输出句子被打印出来。然后一脸懵逼,二脸懵逼,懵山懵海。尽量别用它

几个效果域

  • GlobalScope 尽量防止运用 GlobalScope

  • CoroutineScope 常用

  • MainScope

  • runBlocking

  • viewModelScope 安卓专用,合作viewModel 常用

  • lifecycleScope 安卓专用,合作lifecycle 常用


  1. GlobalScope:全局效果域,生命周期和运用相同长。尽管能够在任何当地发动协程,但在实践开发中尽量防止运用,以防止协程无法及时被撤销造成的内存走漏。

  2. CoroutineScope:一个通用的协程效果域接口,能够自界说效果域的上下文,比方你能够创立一个 CoroutineScope 的实例并指定其生命周期。

  3. MainScope:是 CoroutineScope 的一个实例,它在主线程上创立协程。通常用在 Android UI 线程的操作,需求手动进行撤销操作(OnDestory)以防止内存走漏。

  4. runBlocking:这是一个特别的协程效果域,会堵塞当时线程并等候协程履行完毕。通常在测验和主函数中运用,开发中防止运用,因为它会堵塞线程,或许导致运用无呼应。

  5. viewModelScope:这是 Android 架构组件供给的预界说效果域,专门为 ViewModel 设计,当 ViewModel 被铲除时,viewModelScope 中的一切协程也会主动被撤销。在 Android 开发中,这是最常运用的效果域之一。

  6. 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

launchasync 的比照

launchasync 都是用来创立新协程的结构器。它们的首要差异在于回来值意图

  • launch 用来发动一个新协程,这个协程不会回来任何成果(而是回来一个 Job 目标)。它的首要用途是履行一些并发的操作,这些操作自身便是它们的意图,咱们并不关怀它们的回来值。咱们能够运用这个回来的 Job 目标来操控这个协程,比方撤销它。

  • async 用来发动一个新协程,而且这个协程能够回来一个成果。async 回来一个 Deferred 目标,这是一个特别的 Job,咱们能够运用 await() 办法来获取这个协程的成果。假如这个协程还没有完结,await() 办法会挂起当时协程,直到这个协程完结。


JobDeferred 的比照

JobDeferred 都代表一个协程的生命周期和状况。它们的首要差异在于,Deferred 能够有一个成果。

  • Job 表示一个协程的生命周期。咱们能够调用 Jobcancel() 办法来撤销这个协程。Job 还有一些其他的办法和特点,用来查询协程的状况,比方 isActiveisCompletedisCancelled
  • DeferredJob 的一个子接口,它增加了一个 await() 办法,用来获取协程的成果。当你调用 await() 办法时,假如这个协程还没有完结,当时协程会被挂起,直到这个协程完结。因此,Deferred 是一个能够有成果的 Job

launchasync 代码演示

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

  1. Dispatchers.Main:在主线程上履行协程代码,用于处理 UI 相关的操作,例如更新界面、响运用户交互等。在 Android 中,因为 UI 操作有必要在主线程上履行,因此运用 Dispatchers.Main 是十分常见的。(因为协程在主线程中履行,不应该在该上下文中进行耗时操作,防止堵塞主线程导致运用无呼应。)

Dispatchers.IO

  1. Dispatchers.IO:用于履行 I/O 相关的操作,如网络恳求、文件读写等。它运用了一个线程池来运转协程代码,适用于履行长期的堵塞操作。在进行网络恳求、数据库操作或文件读写等 I/O 操作时,运用 Dispatchers.IO 能够防止堵塞主线程,进步运用程序的呼应性。

Dispatchers.Default

  1. Dispatchers.Default:用于履行核算密集型操作,如数据处理或核算。它运用了一个线程池来运转协程代码,适用于履行耗费 CPU 资源的使命。当需求进行复杂的核算或处理很多数据时,运用 Dispatchers.Default 能够将这些操作分配到后台线程,防止堵塞主线程。

除了上述常用的协程上下文,还有一些其他的上下文也或许在特定的场景中运用:

Dispatchers.Unconfined

  1. Dispatchers.Unconfined:这个上下文不受任何特定线程的约束,协程会在康复后康复到任意线程上。它适用于一些无需特定线程约束的操作,但需求留意的是,因为协程在不同的线程之间切换,或许会导致上下文切换的开销。

CoroutineName

  1. CoroutineName:这个上下文用于为协程指定一个称号,以方便调试和追踪。在复杂的协程流程中,给协程命名能够帮助咱们更好地理解和跟踪协程的履行途径。

在 Android 开发中,常用的协程上下文是根据详细的需求和场景挑选的。首要重视需求在主线程上履行的 UI 操作、I/O 操作和核算密集型操作,并根据需求挑选恰当的协程上下文。

也便是说,日常运用根本都是Dispatchers.MainDispatchers.IODispatchers.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 协程中,挂起函数只能在以下环境中被调用:

  • 其他挂起函数:一个挂起函数能够调用其他的挂起函数。这意味着你能够在一个挂起函数中调用另一个挂起函数,而不需求任何特别的语法或关键字。
  • 协程构建器:挂起函数能够在协程构建器(如 launchasync)中被调用。这是发动一个新的协程并在其间调用挂起函数的常见办法。
  • 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 修饰的函数便是挂起函数,协程也自带一些挂起函数

协程的挂起函数有许多,比方 delayjoinawaitwithContextyieldwithTimeout 等等。这些函数的效果和特点如下:

  • 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 线程,然后又回到了主线程。咱们能够在日志中看到协程在不同的线程之间切换。