第一次接触Kotlin协程时,我习惯性地用线程的思维去理解它,结果踩了不少坑。后来才明白,协程和线程根本是两种不同的并发模型。线程是操作系统层面的概念,由系统内核调度,而协程是用户态的轻量级线程,调度完全由程序控制。
举个生活中的例子:线程就像银行柜台,每个柜台同时只能服务一个客户(单核CPU情况下),系统负责分配客户到不同柜台。协程则像餐厅的服务员,一个服务员可以轮流服务多桌客人 - 点完A桌的菜,趁厨房准备时去B桌倒水,再回来给A桌上菜。这种协作式的工作方式,就是协程的精髓。
协程的三大核心优势:
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)
}
新手最容易犯的错误就是忽视作用域管理。我在早期项目中使用GlobalScope导致内存泄漏,后来才明白合理的作用域应该与界面生命周期绑定。Android中推荐的使用模式:
kotlin复制class MyActivity : AppCompatActivity() {
private val scope = MainScope()
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 自动取消所有子协程
}
fun loadData() {
scope.launch {
// 在这里启动协程
}
}
}
作用域的最佳实践:
viewModelScopelifecycleScope调度器决定协程运行在哪个线程,选错调度器可能导致界面卡顿或并发问题。我整理了一份调度器选择指南:
| 调度器类型 | 适用场景 | 典型用例 |
|---|---|---|
| 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)
}
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倍。
多路复用:使用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") } }
在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的优势对比:
完善的错误处理是健壮应用的关键,这是我的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)
}
}
}
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()
)
}
关键点:
kotlin复制// 不推荐 - 为每个小任务创建新协程
items.forEach { item ->
launch { processItem(item) }
}
// 推荐 - 批量处理
launch {
items.forEach { item ->
withContext(NonCancellable) {
processItem(item)
}
}
}
不当的调度器选择:在IO调度器执行CPU密集型计算会降低性能
Channel容量设置不合理:过小会导致频繁挂起,过大会占用过多内存
kotlin复制class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 自动在合适的生命周期启动/停止
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
try {
loadData()
} catch (e: Exception) {
// 处理异常
}
}
}
}
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()
}
}
kotlin复制launch(CoroutineName("LoadData")) {
// ...
}
bash复制-Dkotlinx.coroutines.debug
kotlin复制val handler = CoroutineExceptionHandler { _, exception ->
log("CoroutineExceptionHandler got $exception")
}
问题1:协程不执行?
问题2:内存泄漏?
问题3:并发修改异常?
kotlin复制val mutex = Mutex()
var sharedState = 0
fun updateState() {
viewModelScope.launch {
mutex.withLock {
sharedState++
}
}
}