当前位置: 首页 > news >正文

Android Kotlin 协程详解

一、协程概述

1.1 协程的定义与优势

协程是 Kotlin 中处理异步操作的核心特性,它轻量、高效,允许以同步方式编写异步代码,避免回调地狱,提升代码可读性和可维护性。

核心优势:
  • 轻量级:单个应用可创建数千个协程,内存占用极低
  • 非阻塞:通过挂起函数实现线程切换,避免阻塞主线程
  • 结构化并发:自动管理协程生命周期,防止内存泄漏
  • 无缝集成:与 Android 生命周期完美结合,替代传统 AsyncTask、Handler 等

1.2 协程与线程对比

特性

协程

线程

内存占用

约 1KB / 协程

约 1MB / 线程

启动速度

纳秒级

毫秒级

并发能力

数千个

数百个

资源消耗

极低

(数据来源:Kotlin 官方文档及测试数据)

二、协程基础

2.1 协程的启动方式

2.1.1 launch(无返回值)

// 在主线程启动协程

viewModelScope.launch(Dispatchers.Main) {

    // 执行UI操作

}

// 在后台线程执行耗时任务

viewModelScope.launch(Dispatchers.IO) {

    val data = repository.fetchData()

    withContext(Dispatchers.Main) {

        textView.text = data

    }

}

2.1.2 async(带返回值)

val deferred = viewModelScope.async(Dispatchers.IO) {

    repository.fetchUserData()

}

// 等待结果并在主线程处理

viewModelScope.launch(Dispatchers.Main) {

    val user = deferred.await()

    updateUI(user)

}

在 Kotlin 协程中,async函数的核心特性是异步启动但支持同步获取结果,它的执行机制可以拆解为两部分:

  • 异步启动特性async会立即启动一个协程,该协程在指定的调度器上异步执行
  • 可等待性:通过await()方法可以阻塞当前协程,直到async启动的协程完成并返回结果
从启动方式看:绝对的异步特性

async的启动机制与launch一致,都是通过协程调度器实现非阻塞启动:

  1. 不会阻塞调用线程
  2. 会在指定调度器(如 IO/Default)的线程池中分配执行资源
  3. 适合用于并行计算任务(如同时获取多个网络接口数据)
从结果获取看:await () 的同步阻塞特性

Deferred.await()方法具有以下特点:

  1. 会阻塞当前协程(注意:不是阻塞线程!)
  2. 等待期间会释放当前线程资源给其他协程
  3. 本质是 "挂起当前协程直到结果可用",而非传统同步编程的线程阻塞
与同步 / 异步的对比表格

特性

传统同步方法

async+await 组合

纯异步回调

调用线程阻塞

是(线程级阻塞)

否(协程级挂起)

结果获取方式

立即返回

显式 await () 获取

回调函数

并行能力

强(支持 async 并行)

代码可读性

高(接近同步写法)

低(可能出现回调地狱)

2.2 协程作用域

2.2.1 常用作用域
  • viewModelScope:与 ViewModel 生命周期绑定,自动取消
  • lifecycleScope:与 Activity/Fragment 生命周期绑定
  • GlobalScope:全局作用域,需谨慎使用,避免内存泄漏
2.2.2 自定义作用域

class MyRepository(private val scope: CoroutineScope) {

    fun fetchData() {

        scope.launch(Dispatchers.IO) {

            // 执行数据库操作

        }

    }

}

2.3 挂起函数

2.3.1 定义与使用

// 挂起函数必须标记suspend

suspend fun fetchUserData(userId: String): User {

    delay(1000L) // 模拟网络请求

    return User(userId, "John Doe")

}

// 在协程中调用挂起函数

viewModelScope.launch {

    val user = fetchUserData("123")

    updateUI(user)

}

2.3.2 挂起与阻塞的区别
  • 挂起:协程暂停执行,释放线程资源,不阻塞线程
  • 阻塞:线程被占用,无法执行其他任务(如 Thread.sleep)

2.4 调度器(Dispatchers)

2.4.1 常用调度器
  • Dispatchers.Main:主线程,用于 UI 操作
  • Dispatchers.IO:优化 IO 操作(网络、数据库)
  • Dispatchers.Default:CPU 密集型任务
  • Dispatchers.Unconfined:不指定线程,当前线程执行
2.4.2 线程切换

viewModelScope.launch(Dispatchers.IO) {

    // 执行耗时操作

val data = database.loadData()

//从IO线程切换到Main线程

    withContext(Dispatchers.Main) {

        // 更新UI

    }

}

三、协程进阶

3.1 结构化并发

3.1.1 coroutineScope

//所有子协程完成后才会结束

//任一子协程异常会取消整个作用域

suspend fun fetchMultipleData(): CombinedData {

    return coroutineScope {

        val userDeferred = async { fetchUser() }

        val profileDeferred = async { fetchProfile() }

        CombinedData(userDeferred.await(), profileDeferred.await())

    }

}

coroutineScope 是 Kotlin 协程中实现结构化并发的核心 API,其设计目标是解决传统异步编程中任务生命周期管理混乱的问题。它的核心特性包括:

  • 作用域绑定:所有在 coroutineScope 内启动的子协程都与该作用域绑定
  • 完成保证:coroutineScope 会等待所有子协程完成后才返回
  • 异常传播:任一子协程抛出异常会导致整个作用域取消

自动资源释放:确保协程执行完毕后释放相关资源

结构化并发的实现原理

coroutineScope 通过以下机制实现结构化并发:

  • 作用域树结构:每个 coroutineScope 创建一个新的作用域节点,子协程作为子节点
  • 完成回调链:父作用域等待所有子作用域完成后才结束
  • 异常冒泡:子协程异常会向上传播至父作用域

// 作用域树结构示例

suspend fun parentScope() = coroutineScope {

    println("父作用域开始")

    

    // 第一个子作用域

    coroutineScope {

        println("子作用域1开始")

        launch {

            delay(1000)

            println("子作用域1任务完成")

        }

        println("子作用域1等待任务完成")

    }

    

    // 第二个子作用域

    coroutineScope {

        println("子作用域2开始")

        launch {

            delay(500)

            println("子作用域2任务完成")

        }

        println("子作用域2等待任务完成")

    }

    

    println("父作用域所有子任务完成")

}

异常处理机制

coroutineScope 的异常处理遵循以下规则:

  • 子协程抛出异常会导致整个 coroutineScope 取消
  • 异常会被传播给 coroutineScope 的调用者
  • 未处理的异常会导致协程崩溃

suspend fun exceptionHandling() = coroutineScope {

    val job1 = launch {

        delay(1000)

        println("任务1完成")

    }

    

    val job2 = launch {

        delay(500)

        throw IOException("网络错误") // 抛出异常

    }

    

    // 以下代码不会执行,因为job2抛出异常

    job1.join()

    job2.join()

    println("所有任务完成")

}

// 调用方式

try {

    exceptionHandling()

} catch (e: IOException) {

    println("捕获异常: ${e.message}") // 输出:捕获异常: 网络错误

}

与其他作用域的对比

特性

coroutineScope

GlobalScope

viewModelScope

结构化并发

是(强制等待子协程)

是(与生命周期绑定)

异常处理

严格(异常传播)

自动取消(随组件销毁)

生命周期管理

自动(完成即结束)

手动

自动(随 ViewModel 销毁)

适用场景

并行任务聚合

后台长时间运行任务

ViewModel 内异步操作

3.1.2 supervisorScope

supervisorScope是 Kotlin 协程中用于实现非结构化并发的关键构建器,它与coroutineScope共同构成了结构化并发的两大支柱。其核心特性在于:

  • 子协程异常隔离:子协程的异常不会传播到兄弟协程和父协程
  • 灵活的异常处理:每个子协程可独立处理自身异常
  • 非阻塞式异常传播:异常不会立即中断整个作用域的执行

// supervisorScope基本结构

suspend fun supervisorScopeDemo() {

    supervisorScope {

        // 启动多个子协程

        val job1 = launch { /* 任务1 */ }

        val job2 = launch { /* 任务2 */ }

        val job3 = launch { /* 任务3 */ }

        

        // 等待所有子协程完成

        job1.join()

        job2.join()

        job3.join()

    }

}

supervisorScope 与 coroutineScope 的核心差异
1. 异常处理机制对比

特性

coroutineScope

supervisorScope

异常传播

子协程异常会取消整个作用域及所有兄弟协程

子协程异常仅影响自身,不影响其他协程

异常处理责任

父协程负责统一处理所有子协程异常

每个子协程需自行处理自身异常

任务完整性

所有子任务必须全部完成

部分子任务失败不影响其他任务执行

适用场景

强一致性要求的场景(如数据库事务)

独立任务并行执行的场景(如日志收集)

2 执行流程对比示例

// coroutineScope异常传播示例

suspend fun coroutineScopeException() {

    try {

        coroutineScope {

            launch {

                delay(100)

                throw Exception("子协程1异常")

            }

            launch {

                delay(200)

                println("子协程2是否执行?") // 不会执行

            }

        }

    } catch (e: Exception) {

        println("捕获到异常: ${e.message}")

    }

}

// supervisorScope异常隔离示例

suspend fun supervisorScopeException() {

    try {

        supervisorScope {

            launch {

                delay(100)

                throw Exception("子协程1异常")

            }

            launch {

                delay(200)

                println("子协程2正常执行") // 会执行

            }

        }

    } catch (e: Exception) {

        println("捕获到异常: ${e.message}") // 不会捕获到异常

    }

}

supervisorScope 的异常处理机制
子协程异常的传播路径

graph TD

    A[supervisorScope] --> B[子协程1]

    A --> C[子协程2]

    B -->|抛出异常| B1[异常仅在子协程1内传播]

    C -->|正常执行| C1[子协程2不受影响]

子协程自处理异常的最佳实践

suspend fun selfHandleException() {

    supervisorScope {

        // 子协程1:自行处理异常

        launch {

            try {

                // 可能抛出异常的操作

                throw IOException("网络异常")

            } catch (e: IOException) {

                logError("网络请求失败: ${e.message}")

            }

        }

        

        // 子协程2:未处理异常会导致自身取消,但不影响其他协程

        launch {

            // 未处理的异常

            throw IllegalStateException("状态异常")

        }

        

        // 子协程3:正常执行

        launch {

            delay(500)

            println("子协程3完成")

        }

    }

}

3.2 Flow(响应式数据流)

3.2.1 基本使用

// 定义Flow

fun getUpdates(): Flow<Update> = flow {

    while (true) {

        emit(fetchUpdate()) // 发射数据

        delay(1000L)

    }

}

// 收集Flow

lifecycleScope.launch {

    getUpdates()

        .flowOn(Dispatchers.IO)

        .collect { update ->

            // 更新UI

        }

}

3.2.2 操作符
  • map:转换数据
  • filter:过滤数据
  • debounce:防抖处理
  • collectLatest:处理最新数据

3.3 Channel(协程间通信)

3.3.1 基础用法

val channel = Channel<Int>()

// 生产者

launch {

    for (i in 1..5) {

        channel.send(i * i)

    }

    channel.close()

}

// 消费者

launch {

    for (value in channel) {

        println(value)

    }

}

3.3.2 缓冲区策略

// 容量为2,溢出时丢弃最旧数据

val channel = Channel<Int>(2, BufferOverflow.DROP_OLDEST)

四、最佳实践

4.1 推荐做法

  1. 使用生命周期绑定作用域:如 viewModelScope、lifecycleScope
  2. 依赖注入调度器:避免硬编码 Dispatchers
  3. 结构化并发优先:使用 coroutineScope/supervisorScope 管理协程
  4. 处理异常:使用 try-catch 或 CoroutineExceptionHandler
  5. Flow 处理多值异步:替代回调和 LiveData

4.2 避免做法

  1. 滥用 GlobalScope:可能导致内存泄漏
  2. 在主线程执行耗时操作:始终使用 Dispatchers.IO 或 Default
  3. 手动管理协程生命周期:依赖作用域自动取消
  4. 阻塞挂起函数:避免使用 Thread.sleep

4.3 性能优化

  1. 限制并发数量:使用 Semaphore 控制同时运行的协程数
  2. 合理选择调度器:IO 任务用 Dispatchers.IO,CPU 任务用 Default
  3. Flow 背压处理:使用 buffer、conflate 等操作符控制流速

五、示例代码

5.1 ViewModel 中使用协程

class MyViewModel : ViewModel() {

    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)

    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun fetchData() {

        viewModelScope.launch {

            _uiState.value = UiState.Loading

            try {

                val data = withContext(Dispatchers.IO) {

                    repository.fetchData()

                }

                _uiState.value = UiState.Success(data)

            } catch (e: Exception) {

                _uiState.value = UiState.Error(e.message ?: "未知错误")

            }

        }

    }

}

5.2 Flow 与 LiveData 结合

class MyViewModel : ViewModel() {

    val userData: LiveData<User> = repository.getUserData()

        .flowOn(Dispatchers.IO)

        .catch { e -> Log.e("MyViewModel", "获取数据失败", e) }

        .asLiveData(viewModelScope.coroutineContext)

}

5.3 并行执行多个请求

suspend fun fetchMultipleData(): CombinedData {

    return coroutineScope {

        val userDeferred = async(Dispatchers.IO) { userService.getUser() }

        val profileDeferred = async(Dispatchers.IO) { profileService.getProfile() }

        CombinedData(userDeferred.await(), profileDeferred.await())

    }

}

六、测试协程

6.1 使用 TestDispatcher

import kotlinx.coroutines.test.*

@Test

fun testFetchData() = runTest {

    val testDispatcher = StandardTestDispatcher()

    Dispatchers.setMain(testDispatcher)

    val viewModel = MyViewModel(repository = mockRepository)

    viewModel.fetchData()

    // 推进时间让协程执行

    testDispatcher.advanceUntilIdle()

    assertEquals(UiState.Success(expectedData), viewModel.uiState.value)

}

6.2 模拟耗时操作

@Test

fun testNetworkRequest() = runTest {

    val mockRepository = MockRepository()

    val viewModel = MyViewModel(repository = mockRepository)

    

    // 模拟网络请求延迟

    mockRepository.mockDelay(1000L)

    

    viewModel.fetchData()

    advanceTimeBy(1000L) // 推进时间

    

    assertTrue(viewModel.uiState.value is UiState.Success)

}

七、资源推荐

  1. Kotlin 官方文档:协程指南
  2. Android 开发者文档:协程最佳实践
  3. Kotlin 协程 GitHub:kotlinx.coroutines

  4.  

八、总结

Kotlin 协程是 Android 异步编程的革命性工具,通过轻量级、结构化并发和响应式编程,极大提升了代码的可读性和可维护性。掌握协程的基础、进阶技巧和最佳实践,能够显著提高开发效率,减少内存泄漏和线程管理问题。建议结合官方文档和实际项目不断实践,逐步深入理解协程的核心原理和应用场景。

http://www.xdnf.cn/news/951373.html

相关文章:

  • Python 中的加密库:守护数据安全的利刃
  • 8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
  • 拟合问题处理
  • C# dll版本冲突解决方案
  • 运放——单电源供电和双电源供电
  • 商品中心—1.B端建品和C端缓存的技术文档一
  • 消息队列系统设计与实践全解析
  • 规则与人性的天平——由高考迟到事件引发的思考
  • NSS-DAY12
  • 2.2.2 ASPICE的需求分析
  • CopyQ | 在命令中使用正则表达式并实现匹配指定字符串的方法
  • 大话软工笔记—需求分析概述
  • 安宝特案例丨又一落地,Vuzix AR眼镜助力亚马逊英国仓库智能化升级!
  • games101 hw1
  • 密码是什么(三):多表代替密码
  • ​​企业大模型服务合规指南:深度解析备案与登记制度​​
  • Word VBA快速制作填空题
  • configure构建工程
  • 如何高效的组织产品研发团队与产品交付开发团队
  • MeanFlow:何凯明新作,单步去噪图像生成新SOTA
  • 第六届亚太图像处理、电子与计算机国际会议(IPEC 2025)成功举办
  • 一文读懂 Docker Compose(白话版)
  • JVM参数调优,让系统可用率从95%提高到99.995%
  • ArcGIS应用与FLUS模型预测:从安装到土地利用建模,数据管理、地图制作、遥感解译、空间分析、地形分析及案例分析攻略
  • LLMs之StructuredOutput:大模型结构化输出的简介、常用方案、前沿框架之详细攻略
  • 安宝特科技丨Pixee Medical产品获FDA认证 AR技术赋能骨科手术智能化
  • Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析
  • C++算法训练营 Day13二叉树专题(1)
  • Flutter状态管理框架RiverPod入门
  • 西电【网络与协议安全】课程期末复习的一些可用情报