1. 从官网文档看协程的本质
第一次接触Kotlin协程时,我像大多数人一样困惑:它和线程有什么区别?为什么官方文档开篇就强调"协程是轻量级线程"?直到反复阅读官网的"Coroutines basics"章节,才真正理解这句话的含义。
协程的轻量体现在三个方面:内存占用、切换成本和创建开销。一个线程默认需要1MB栈空间,而协程只需几十KB;线程切换涉及内核态与用户态转换,协程切换完全在用户空间完成;创建1000个线程会导致多数系统崩溃,而创建10万个协程却轻而易举。这解释了为什么官网示例敢大胆演示repeat(100_000)创建海量协程。
官网特别强调的挂起(suspend)机制,是协程最精妙的设计。当我在Android项目中第一次使用viewModelScope.launch时,发现挂起函数执行时竟不会阻塞UI线程。这背后的Continuation续体模型,使得协程能在任意位置暂停/恢复。官网的"Sequential by default"示例完美展示了这点——看似同步的代码,实际执行却是异步的。
提示:阅读官网时务必动手修改示例代码。比如将
delay(1000L)改为Thread.sleep(1000L),立即就能体会到协程挂起与线程阻塞的本质区别。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 结构化并发:ViewModelScope的工程实践
在Android开发中,最让我受益的是官网对结构化并发(structured concurrency)的诠释。viewModelScope不是简单的CoroutineScope实现,而是与生命周期绑定的最佳实践。当ViewModel的onCleared()触发时,它会自动取消所有子协程,这解决了传统AsyncTask容易引发的内存泄漏问题。
通过拆解ViewModelScope.kt源码,发现其核心逻辑其实很简单:
kotlin复制private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) return scope
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
但正是这个SupervisorJob与Dispatchers.Main的组合,保证了UI协程的健壮性——子协程失败不会级联取消,且默认在主线程调度。
在真实项目中,我常这样组合使用:
kotlin复制viewModelScope.launch {
val userData = async(Dispatchers.IO) { fetchUserData() }
val newsFeed = async(Dispatchers.IO) { fetchNewsFeed() }
updateUI(userData.await(), newsFeed.await())
}
这种模式既利用了多线程并行,又通过结构化并发避免了资源泄漏。
3. StateFlow与协程的化学反应
官网在"Shared flows and state flows"章节展示的StateFlow案例,彻底改变了我对数据更新的认知。传统LiveData需要手动postValue,而StateFlow配合协程可以实现响应式编程:
kotlin复制class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadData() = viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val data = repository.fetchData()
_uiState.value = UiState.Success(data)
} catch (e: Exception) {
_uiState.value = UiState.Error(e)
}
}
}
这种模式的优势在复杂场景尤为明显。当需要组合多个数据源时,可以用combine操作符:
kotlin复制val userFlow = repository.observeUser()
val settingsFlow = repository.observeSettings()
val uiState = combine(userFlow, settingsFlow) { user, settings ->
UiState(user, settings)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = UiState.Loading
)
注意:
stateIn的started参数选择很关键。官网推荐生产环境使用WhileSubscribed(5000),这能在无订阅者时自动取消上游流,同时保留5秒缓存避免配置变更时的重复请求。
4. 协程异常处理的防坑指南
官网"Exception handling"章节看似简单,却藏着最易踩的坑。我曾遇到过一个线上崩溃:在launch块里抛出异常竟导致整个应用崩溃!查阅官网才发现,默认情况下未捕获异常会传递给Thread.defaultUncaughtExceptionHandler。
正确的异常处理应该分层进行:
kotlin复制viewModelScope.launch {
val deferred = async {
try {
riskyOperation()
} catch (e: IOException) {
// 处理特定异常
null
}
}
try {
val result = deferred.await()
// 处理正常结果
} catch (e: CancellationException) {
throw e // 必须重新抛出取消异常
} catch (e: Exception) {
// 处理其他异常
}
}
对于全局异常,可以自定义CoroutineExceptionHandler:
kotlin复制val handler = CoroutineExceptionHandler { _, exception ->
Log.e("CoroutineError", "Uncaught exception", exception)
}
viewModelScope.launch(handler) {
// 可能抛出异常的代码
}
5. 协程性能优化实战
官网"Coroutine context and dispatchers"章节揭示了调度器的选择对性能的巨大影响。通过基准测试发现,错误使用Dispatchers.Default会导致CPU密集型任务阻塞UI线程:
kotlin复制// 错误示范:在主线程执行计算
viewModelScope.launch {
val result = calculatePi(1000) // 阻塞主线程
updateUI(result)
}
// 正确做法:明确指定调度器
viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
calculatePi(1000) // 在后台线程池执行
}
updateUI(result)
}
对于IO密集型任务,更推荐自定义线程池:
kotlin复制private val ioDispatcher = Executors.newFixedThreadPool(16).asCoroutineDispatcher()
viewModelScope.launch {
val data = withContext(ioDispatcher) {
blockingIOOperation() // 使用专用IO线程池
}
// ...
}
重要:记得在不再需要时关闭自定义调度器,否则会导致线程泄漏:
kotlin复制override fun onCleared() {
super.onCleared()
ioDispatcher.close()
}
6. 协程在复杂业务中的架构应用
官网最后的高级概念章节展示了协程如何简化复杂业务逻辑。在我的电商App项目中,使用channel实现了商品秒杀队列:
kotlin复制class SeckillManager {
private val requestChannel = Channel<SeckillRequest>(capacity = 100)
init {
viewModelScope.launch {
for (request in requestChannel) {
try {
val result = processSeckill(request)
request.callback(result)
} catch (e: Exception) {
request.callback(Result.failure(e))
}
}
}
}
fun submitRequest(request: SeckillRequest) {
if (!requestChannel.trySend(request).isSuccess) {
request.callback(Result.failure(FullQueueException()))
}
}
}
这种模式比回调或RxJava更直观,且天然支持背压。结合select表达式,还能实现多路复用:
kotlin复制suspend fun fetchData(): Data {
return select {
async { fetchFromCache() }.onAwait { it }
async { fetchFromNetwork() }.onAwait { it }
}
}
在ViewModel中集成协程流(Flow)时,推荐以下架构:
kotlin复制class MyViewModel(
private val repository: MyRepository
) : ViewModel() {
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> = _loading.asStateFlow()
val data = flow {
_loading.value = true
emit(repository.fetchInitialData())
_loading.value = false
repository.observeUpdates().collect { update ->
emit(update)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null
)
}
这种架构下,UI层只需简单监听:
kotlin复制viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { data ->
// 更新UI
}
}
}
