1. Kotlin协程基础:从线程到协程的思维转变
第一次接触Kotlin协程时,我习惯性地用线程的思维去理解它,结果踩了不少坑。后来才明白,协程和线程根本是两种不同的并发模型。线程是操作系统层面的概念,由系统内核调度,而协程是用户态的轻量级线程,调度完全由程序控制。
举个生活中的例子:线程就像银行柜台,每个柜台同时只能服务一个客户(单核CPU情况下),系统负责分配客户到不同柜台。协程则像餐厅的服务员,一个服务员可以轮流服务多桌客人 - 点完A桌的菜,趁厨房准备时去B桌倒水,再回来给A桌上菜。这种协作式的工作方式,就是协程的精髓。
协程的三大核心优势:
- 轻量级:我在测试中创建10万个协程仅消耗约1GB内存,而同样数量的线程直接导致OOM崩溃
- 结构化并发:通过作用域管理生命周期,避免线程常见的资源泄漏问题
- 简化异步代码:用同步写法处理异步逻辑,告别callback hell
kotlin复制// 传统回调写法
api.getUser { user ->
api.getProfile(user) { profile ->
api.getFriends(profile) { friends ->
updateUI(friends)
}
}
}
// 协程写法
suspend fun loadData() {
val user = api.getUser()
val profile = api.getProfile(user)
val friends = api.getFriends(profile)
updateUI(friends)
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Coroutine实战:构建稳健的异步任务体系
2.1 协程作用域管理
新手最容易犯的错误就是忽视作用域管理。我在早期项目中使用GlobalScope导致内存泄漏,后来才明白合理的作用域应该与界面生命周期绑定。Android中推荐的使用模式:
kotlin复制class MyActivity : AppCompatActivity() {
private val scope = MainScope()
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 自动取消所有子协程
}
fun loadData() {
scope.launch {
// 在这里启动协程
}
}
}
作用域的最佳实践:
- ViewModel中使用
viewModelScope - 界面层使用
lifecycleScope - 跨界面共享数据使用自定义作用域+SupervisorJob
2.2 协程调度器选择
调度器决定协程运行在哪个线程,选错调度器可能导致界面卡顿或并发问题。我整理了一份调度器选择指南:
| 调度器类型 | 适用场景 | 典型用例 |
|---|---|---|
| Dispatchers.Main | UI操作 | 更新TextView |
| Dispatchers.IO | 磁盘/网络IO | 读取数据库 |
| Dispatchers.Default | CPU密集型计算 | 图片处理 |
| Dispatchers.Unconfined | 不指定线程(慎用) | 特殊测试场景 |
kotlin复制// 典型的多调度器配合使用
viewModelScope.launch {
// 在主线程启动
val data = withContext(Dispatchers.IO) {
// 切换到IO线程执行网络请求
repository.fetchData()
}
// 自动切回主线程更新UI
updateViews(data)
}
3. Channel深度解析:协程间的通信桥梁
3.1 Channel类型与选择
Channel就像协程之间的管道,我经常用它来处理生产者-消费者场景。根据业务需求选择正确的Channel类型很关键:
kotlin复制// 1. 无缓冲Channel(默认)
val channel = Channel<String>() // 发送方会挂起直到接收方准备好
// 2. 缓冲Channel
val bufferedChannel = Channel<String>(100) // 允许积压100条消息
// 3. 合并Channel
val conflatedChannel = Channel<String>(CONFLATED) // 只保留最新消息
// 4. 无限Channel
val unlimitedChannel = Channel<String>(UNLIMITED) // 无限制缓存
实战经验:在开发消息队列功能时,我最初使用无缓冲Channel导致性能瓶颈,后来改用缓冲Channel并设置合理大小,吞吐量提升了8倍。
3.2 Channel的高级用法
多路复用:使用select可以同时监听多个Channel
kotlin复制select<Unit> {
channel1.onReceive { value ->
handleValue(value)
}
channel2.onReceive { value ->
handleValue(value)
}
}
广播场景:当需要多个消费者接收相同消息时
kotlin复制val broadcastChannel = BroadcastChannel<String>(10)
broadcastChannel.send("消息")
// 消费者1
val receiver1 = broadcastChannel.openSubscription()
launch { receiver1.consumeEach { println("消费者1: $it") } }
// 消费者2
val receiver2 = broadcastChannel.openSubscription()
launch { receiver2.consumeEach { println("消费者2: $it") } }
4. Flow进阶:构建响应式数据流
4.1 Flow与LiveData的完美配合
在MVVM架构中,我常用Flow替代LiveData获得更强大的功能:
kotlin复制class MyViewModel : ViewModel() {
private val _data = MutableStateFlow<List<Item>>(emptyList())
val data: StateFlow<List<Item>> = _data
fun loadData() {
viewModelScope.launch {
repository.getItems()
.catch { e -> logError(e) }
.collect { items ->
_data.value = items
}
}
}
}
Flow的优势对比:
- 丰富的操作符:map、filter、combine等
- 灵活的线程控制:flowOn指定上游线程
- 冷流特性:按需执行,避免资源浪费
4.2 Flow异常处理全攻略
完善的错误处理是健壮应用的关键,这是我的Flow异常处理方案:
kotlin复制fun fetchData(): Flow<Result> = flow {
// 数据获取逻辑
}.catch { e ->
// 捕获上游异常
emit(Result.Error(e))
}.onCompletion { cause ->
// 完成时处理(无论成功失败)
cause?.let { log("Flow completed with exception: $it") }
}
// 使用示例
viewModelScope.launch {
fetchData()
.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(1000)
true
} else {
false
}
}
.collect { result ->
when (result) {
is Result.Success -> showData(result.data)
is Result.Error -> showError(result.exception)
}
}
}
5. 协程性能优化实战
5.1 结构化并发的最佳实践
kotlin复制suspend fun fetchUserData(): UserData = coroutineScope {
val avatar = async { repo.getAvatar() }
val friends = async { repo.getFriends() }
val posts = async { repo.getPosts() }
UserData(
avatar = avatar.await(),
friends = friends.await(),
posts = posts.await()
)
}
关键点:
- 使用coroutineScope确保所有子协程完成才返回
- 通过async并发执行独立任务
- 自动传播取消操作
5.2 避免常见性能陷阱
- 过度创建协程:每个协程都有开销,对于轻量级任务应考虑复用
kotlin复制// 不推荐 - 为每个小任务创建新协程
items.forEach { item ->
launch { processItem(item) }
}
// 推荐 - 批量处理
launch {
items.forEach { item ->
withContext(NonCancellable) {
processItem(item)
}
}
}
-
不当的调度器选择:在IO调度器执行CPU密集型计算会降低性能
-
Channel容量设置不合理:过小会导致频繁挂起,过大会占用过多内存
6. 协程在Android中的特殊应用
6.1 生命周期感知的协程
kotlin复制class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 自动在合适的生命周期启动/停止
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
try {
loadData()
} catch (e: Exception) {
// 处理异常
}
}
}
}
6.2 协程与Jetpack组件的集成
Room + Flow:
kotlin复制@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getUsers(): Flow<List<User>>
}
// ViewModel中
val users = userDao.getUsers()
.map { users ->
users.filter { it.isActive }
}
WorkManager + 协程:
kotlin复制class MyWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val data = downloadData()
saveToDatabase(data)
return Result.success()
}
}
7. 调试与问题排查
7.1 协程调试技巧
- 添加协程名称:
kotlin复制launch(CoroutineName("LoadData")) {
// ...
}
- 使用Debug模式:
bash复制-Dkotlinx.coroutines.debug
- 异常处理:
kotlin复制val handler = CoroutineExceptionHandler { _, exception ->
log("CoroutineExceptionHandler got $exception")
}
7.2 常见问题解决方案
问题1:协程不执行?
- 检查作用域是否已取消
- 确认调度器选择正确
- 确保没有使用LAZY启动模式但忘记调用start()
问题2:内存泄漏?
- 避免使用GlobalScope
- 确保与生命周期组件绑定
- 使用WeakReference持有上下文引用
问题3:并发修改异常?
- 使用Mutex保护共享状态
kotlin复制val mutex = Mutex()
var sharedState = 0
fun updateState() {
viewModelScope.launch {
mutex.withLock {
sharedState++
}
}
}
