kotlin协程学习小计

我们来把Kotlin协程这件事从头到尾聊透,保证用最通俗的大白话来讲解,让你不仅学会怎么用,还能理解它背后的门道。

可以把协程想象成一个超级轻量级的、可以随时"摸鱼"的任务。线程是公司里的正式员工,虽然能干很多活,但招聘和解雇(创建和销毁)的成本很高。而协程就像是你手头的具体工作(比如写一份PPT),你可以随时暂停去干别的事(被挂起),回头再接着写,而你的工位(线程)在此期间被其他人使用着 。

1. 协程的三大核心支柱

Kotlin协程的设计围绕着三个紧密相关的概念:挂起函数、协程作用域、协程上下文。理解它们,就掌握了协程的基石。

1.1 挂起函数 (suspend fun):魔法发生的地方

这是协程最核心的魔力所在。

  • 是什么? 一个被 suspend 关键字标记的函数。它代表这个函数可能会耗时 ,并且可以在不阻塞当前线程的情况下暂停执行,等结果准备好了再恢复 。
  • 有什么用? 让异步代码"同步化"。不用写一堆回调函数,代码逻辑看起来就像从上到下顺序执行一样。
  • 怎么工作的(原理简析)? 编译器在背后把挂起函数变成了一个"状态机"。每次函数可能暂停时,都会有一个对应的状态。当函数恢复时,会根据状态标签跳到上次执行的地方继续执行 。
kotlin 复制代码
suspend fun fetchUserData(): String {
    println("开始请求网络") // 运行在某个线程
    // withContext 是一个挂起函数,它会将协程切换到 IO 线程池
    // 当前协程在此处被挂起,但外面的线程可以去干别的活
    val result = withContext(Dispatchers.IO) {
        delay(1000) // 模拟网络请求,这是一个挂起函数
        return@withContext "User Data"
    }
    println("网络请求结束,拿到数据:$result") // 恢复执行,可能回到了原来的线程
    return result
}

1.2 协程作用域 (CoroutineScope):协程的"管辖区域"

  • 是什么? 可以理解为一个协程的管理员 。它定义了所有通过它启动的协程的生命周期边界。它最重要的职责是保证结构化并发
  • 结构化并发的好处:
    1. 取消传播:如果取消一个作用域,它内部所有正在运行的协程都会被取消。
    2. 等待完成:一个作用域会等待它内部所有的子协程都完成,自己才算完成。
    3. 异常传播:如果子协程出错了,它会通知其他兄弟协程或父协程。
  • 常见的作用域:
    • GlobalScope全局大喇叭 。生命周期与整个应用程序一样长,非常容易导致协程泄漏,几乎不推荐在实战中使用
    • runBlocking笨重的桥梁 。它会阻塞当前线程 直到内部所有协程执行完毕。通常只用在main函数测试或连接既有阻塞代码,正式业务代码中禁止使用
    • coroutineScope智能的挂起者 。它是一个挂起函数,会创建一个新的作用域,但不会阻塞线程,而是挂起自己,直到内部所有子协程完成。非常适合在挂起函数内进行并行分解任务 。
    • Android专属viewModelScope (在ViewModel中) 和 lifecycleScope (在Activity/Fragment中)。它们会自动绑定生命周期,当页面销毁时,自动取消所有协程,安全无泄漏

1.3 协程上下文 (CoroutineContext):协程的"背包"

  • 是什么? 一个类似于 Map 的数据结构,里面装着协程运行所需的各种配置信息 。这个背包里主要有四大件:
    1. Job (工作证) :协程的唯一标识和控制器。通过 Job,你可以管理协程的生命周期 :启动、取消、判断是否活跃等 。
      • launch 返回一个 Job 对象。
      • async 返回一个 Deferred 对象(Deferred 继承自 Job,多了一个 await() 方法用于获取结果) 。
    2. CoroutineDispatcher (调度器) :决定这个协程在哪条线程或哪个线程池上执行
      • Dispatchers.Main:主线程,用于UI操作。
      • Dispatchers.IO:IO线程池,用于网络、数据库、文件读写。
      • Dispatchers.Default:默认线程池,用于CPU密集型任务,如JSON解析、排序。
      • Dispatchers.Unconfined:不限派,在第一挂起点前运行在当前线程,之后由恢复它的线程决定,慎用
    3. CoroutineName (名字):给协程起个名,方便调试 。
    4. CoroutineExceptionHandler (异常处理器):处理未捕获的异常 。

2. 如何启动协程:两大核心构建器

你需要在一个 CoroutineScope 里,使用构建器来启动协程。

  • launch:点火起飞,不要结果

    • 当你只关心执行任务,而不需要任务返回任何值时使用。它返回一个 Job 对象,你可以用它来取消协程或等待它完成 。
  • async:点火起飞,等待结果

    • 当你需要执行一个任务,并且最终要拿到它的返回值时使用。它返回一个 Deferred 对象,通过调用它的 await() 方法来获取结果。async 常用于并发执行多个独立任务 。
kotlin 复制代码
// 假设这是在一个 ViewModel 或 lifecycleScope 里
viewModelScope.launch {
    // 启动一个不返回结果的协程
    val job = launch(Dispatchers.IO) {
        // 执行一些IO操作,比如写入日志
    }

    // 并发执行两个网络请求
    val userDeferred = async { fetchUser() }  // 自动继承上下文,通常在默认调度器
    val postsDeferred = async { fetchPosts() }

    // 在这里挂起,等待两个结果都准备好
    val user = userDeferred.await()
    val posts = postsDeferred.await()

    // 更新UI (此时已在主线程)
    showUserData(user, posts)
}

3. 协程的"花式操作"

3.1 并发与等待

  • join() :等待由 launch 启动的 Job 完成 。
  • await() :等待 async 启动的 Deferred 的结果 。
  • 组合并发 :利用 async 实现并发,再通过 await() 汇聚结果。注意 :不要在 async 后直接跟 await(),那样就变回串行了 。

3.2 协程的取消

取消是协作式的。意思是,协程代码必须自己配合检查是否被取消,否则无法被取消 。

  • 如何让协程可以被取消?
    1. 调用 yield()ensureActive() 来检查状态 。
    2. 检查 isActive 这个布尔值 。
    3. 使用所有 kotlinx.coroutines 包下的挂起函数(如 delaywithContext 等),它们都是可取消的。
  • 释放资源 :在 finally 块中释放资源。但如果 finally 中又调用了挂起函数,需要用 withContext(NonCancellable) { ... } 包裹 。

3.3 异常处理

  • 传播方式launch 将异常直接抛出 给父协程;async 将异常存储在 Deferred 对象里 ,直到 await() 调用时才抛出 。
  • CoroutineExceptionHandler:可以设置全局的"异常兜底方案",但通常作用域内的结构化并发已经能很好地处理异常传播 。
  • supervisorScope主管作用域 。它与 coroutineScope 类似,但最大的区别是:一个子协程失败了,不会影响其他兄弟子协程。非常适合处理多个互相独立的任务 。

4. 协程的高级数据流

4.1 热数据通道:Channel

  • 是什么? 类似于一个阻塞队列 。用于不同协程之间传递数据(Stream)。一个协程发送,一个协程接收 。

4.2 冷数据流:Flow

  • 是什么? 可以理解为异步版本的 Sequence (序列) 。它是一个冷流,即只有当你开始收集 (collect) 它的时候,里面的代码才会执行并发射数据 。
  • 有什么用? 非常适合处理从数据库或网络源源不断返回大量数据的场景,或者接收实时更新。
  • 关键操作符
    • 构建:flow { ... }flowOfasFlow
    • 中间操作:mapfiltercatch(捕获上游异常)。
    • 末端操作:collecttoListfirst

4.3 状态持有者:StateFlowSharedFlow

  • StateFlow状态容器 。它总是保存一个最新的值,并且只有值发生变化时才发射。非常适合替代 LiveData 在ViewModel中持有UI状态 。
  • SharedFlow事件广播器。可以配置缓存策略,用于发送事件,比如一次性提示、导航事件等(但要注意避免事件丢失或重复消费)。

5. 总结与实战示例

让我们用一个完整的Android ViewModel 示例来串起所有知识:

kotlin 复制代码
class MyViewModel : ViewModel() {

    // 1. UI状态,使用 StateFlow 持有,对外暴露不可变的 StateFlow
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    fun loadUserProfile(userId: String) {
        // 2. 在 viewModelScope 中启动协程,自动感知生命周期
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                // 3. 并发获取用户信息和好友列表
                val userProfile = coroutineScope { // 使用 coroutineScope 进行结构化并发
                    val userDeferred = async(Dispatchers.IO) { userRepository.fetchUser(userId) }
                    val friendsDeferred = async(Dispatchers.IO) { friendRepository.fetchFriends(userId) }

                    // 4. 挂起等待两个结果
                    UserProfile(
                        user = userDeferred.await(),
                        friends = friendsDeferred.await()
                    )
                }
                // 5. 更新状态(自动在主线程)
                _uiState.value = UiState.Success(userProfile)
            } catch (e: Exception) {
                // 6. 异常处理
                _uiState.value = UiState.Error(e.message)
            }
        }
    }

    // 密封类表示不同的UI状态
    sealed class UiState {
        object Loading : UiState()
        data class Success(val profile: UserProfile) : UiState()
        data class Error(val message: String?) : UiState()
    }
}

希望这份详解能帮你彻底打通Kotlin协程的任督二脉。从今天起,写异步代码,享受同步代码的丝滑吧!

相关推荐
轩情吖1 小时前
MySQL初识
android·数据库·sql·mysql·adb·存储引擎
Sun_gentle2 小时前
android studio创建flutter项目
android·flutter·android studio
我命由我123452 小时前
在 Android Studio 中,新建 AIDL 文件按钮是灰色
android·ide·android studio·安卓·android jetpack·android-studio·android runtime
音视频牛哥2 小时前
Android平台RTMP/RTSP超低延迟直播播放器开发详解——基于SmartMediaKit深度实践
android·人工智能·计算机视觉·音视频·rtmp播放器·安卓rtmp播放器·rtmp直播播放器
麻瓜生活睁不开眼2 小时前
Android 14 开机自启动第三方 APK 全流程踩坑与最终解决方案(含 RescueParty 避坑)
android·java·深度学习
轩情吖3 小时前
MySQL库的操作
android·数据库·mysql·oracle·字符集·数据库操作·编码集
贤泽3 小时前
Android15 ContentProvider 深度源码分析(上)
android·aosp
张小潇3 小时前
AOSP15 Input专题getevent深入分析
android