在异步编程领域,Kotlin 协程以其轻量级、高并发和简洁的代码风格,成为现代 Android 的首选方案。
协程的核心优势
-
轻量级任务单元
协程基于线程池调度,单个线程可同时运行数千个协程,相比传统线程(约 1MB 栈空间),协程内存占用极低(约 2KB)。 -
结构化并发设计
通过作用域(CoroutineScope)管理生命周期,避免回调地狱和内存泄漏。 -
挂起函数机制
使用suspend
关键字实现非阻塞挂起,代码结构更接近同步编程。
协程的启动方式
// 1. launch方式(无返回值)
val job = CoroutineScope(Dispatchers.IO).launch {// 执行异步任务
}// 2. async方式(带返回值)
val deferred = CoroutineScope(Dispatchers.IO).async {"Result from async"
}
挂起函数的实现
// 定义挂起函数
suspend fun fetchData() = withContext(Dispatchers.IO) {// 模拟耗时操作delay(1000)"Data loaded"
}// 使用挂起函数
CoroutineScope(Dispatchers.Main).launch {val result = fetchData()println(result)
}
作用域管理最佳实践
// 推荐使用viewModelScope(Android ViewModel)
viewModelScope.launch {// 自动绑定生命周期
}// 手动管理作用域
val parentScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
parentScope.launch {// 子协程异常不影响父作用域
}
异常处理策略
// 1. try-catch捕获
try {val result = async { throw Exception() }.await()
} catch (e: Exception) {// 处理异常
}// 2. 使用CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->// 全局异常处理
}CoroutineScope(Dispatchers.IO + handler).launch {// 执行可能抛异常的操作
}
i调度器的选择策略
调度器类型 | 适用场景 | 线程来源 |
---|---|---|
Dispatchers.Main | Android UI 更新 | Android 主线程 |
Dispatchers.IO | 文件 / 网络操作 | 后台线程池(默认 2 个) |
Dispatchers.Default | CPU 密集型计算 | 后台线程池(动态调整) |
Dispatchers.Unconfined | 不指定调度器(谨慎使用) | 当前线程 |
性能优化技巧
- 复用作用域:避免频繁创建新的 CoroutineScope
- 限制并发量:使用
Semaphore
控制资源访问 - 避免阻塞:确保挂起函数只执行非阻塞操作
- 缓存线程池:对自定义调度器使用
Executors.newFixedThreadPool()
常见陷阱与解决方案
-
协程泄漏
始终通过CoroutineScope
管理生命周期,避免在 Activity/Fragment 中直接使用GlobalScope
-
异常传播
子协程异常默认会取消父作用域,可通过SupervisorJob
实现独立异常处理 -
线程切换开销
减少不必要的withContext
调用,优先使用线程局部变量
面试实践扩展:
协程与线程的本质区别
面试题:协程为何比线程更轻量?
答:
- 线程由操作系统调度,上下文切换成本高(约 1000+ CPU 周期)
- 协程基于协作式调度,通过状态机(Continuation)实现挂起恢复
- 内存占用:线程默认 1MB 栈空间 vs 协程 2KB
代码示例:
// 线程上下文切换测试
val threadTest = measureTimeMillis {repeat(1000) {Thread {}.start()}
}// 协程启动测试
val coroutineTest = measureTimeMillis {repeat(1000) {GlobalScope.launch {}}
}
挂起函数的实现原理
面试题:suspend 关键字的底层实现机制?
答:
- 编译器将挂起函数转换为状态机(State Machine)
- 通过 Continuation 接口传递执行状态
- 反编译后的 invokeSuspend 方法包含状态标签(label)
字节码分析:
public final Object invokeSuspend(Object $result) {int label = this.label;if (label == 0) {ResultKt.throwOnFailure($result);this.label = 1;return Dispatchers.IO.scheduleResumeAfterDelay(this, 1000L);} else if (label == 1) {ResultKt.throwOnFailure($result);return "Data loaded";}return Unit.INSTANCE;
}
结构化并发管理
面试题:如何避免协程泄漏?
解决方案:
- 使用 Jetpack 组件作用域(viewModelScope/lifecycleScope)
- 手动管理时结合 CoroutineScope.cancel ()
- 避免在 Activity 中使用 GlobalScope
实战代码:
// ViewModel中的正确用法
class MainViewModel : ViewModel() {val users = MutableLiveData<List<User>>()init {viewModelScope.launch {users.value = userRepository.getUsers()}}
}
异常处理机制
面试题:协程异常传播的规则是什么?
关键规则:
- 子协程异常默认会取消父作用域
- 通过 SupervisorJob 实现异常隔离
- 推荐使用 CoroutineExceptionHandler 处理全局异常
异常策略对比:
处理方式 | 适用场景 | 代码示例 |
---|---|---|
try-catch | 局部异常处理 | try { ... } catch (e: ...) |
SupervisorJob | 独立任务组 | SupervisorJob() + Dispatchers.IO |
CoroutineExceptionHandler | 全局异常监控 | CoroutineScope(handler).launch |
框架集成实践
与 Room 数据库结合
面试题:如何在 Room 中使用协程?
实现步骤:
- 在 Dao 接口中声明 suspend 函数
- 在 Repository 层使用协程调度
- 在 ViewModel 中启动协程
代码示例:
@Dao
interface UserDao {@Query("SELECT * FROM users")suspend fun getUsers(): List<User>
}class UserRepository(private val dao: UserDao) {suspend fun getUsers() = withContext(Dispatchers.IO) {dao.getUsers()}
}
与 LiveData 的集成
面试题:如何在协程中更新 LiveData?
最佳实践:
- 使用
withContext(Dispatchers.Main)
切换线程 - 推荐使用
viewModelScope.launch
自动绑定生命周期
优化方案:
viewModelScope.launch {val users = withContext(Dispatchers.IO) {userRepository.getUsers()}_users.value = users
}
性能优化与实战经验
调度器选择策略
面试题:如何选择合适的协程调度器?
决策树:
协程与 RxJava 对比
高频问题:协程相比 RxJava 的优势是什么?
核心对比:
维度 | Kotlin 协程 | RxJava |
---|---|---|
学习成本 | 低(语言原生支持) | 高(需要理解响应式编程) |
内存占用 | 轻量(2KB / 协程) | 较重(每个订阅者对象) |
异常处理 | 结构化(try-catch/handler) | 隐式(onErrorResumeNext) |
线程切换 | 显式(withContext) | 链式调用(subscribeOn/observeOn) |
5.2 协程泄漏排查
面试题:如何定位协程泄漏?
排查步骤:
- 使用 Android Profiler 查看协程状态
- 添加 CoroutineName 标签便于识别
- 利用 leakcanary 检测内存泄漏时关联协程信息
工具代码:
CoroutineScope(Dispatchers.IO + CoroutineName("DataLoader")).launch {// 执行任务
}
总结:
Kotlin 协程以轻量级、结构化并发和挂起函数为核心优势,成为 Android 开发异步编程的首选方案,在面试中需重点掌握其作用域管理、异常处理机制、调度器选择策略及与 Jetpack 组件(如 Room/LiveData)的集成实践,同时关注性能优化技巧(如背压处理、线程池配置) 。
感谢观看!!!