Kotlin 协程作用域详解
协程作用域是 Kotlin 协程中管理并发和生命周期的核心概念。它定义了协程的执行范围和生命周期,是结构化并发的基础。
1. 协程作用域的基本概念
1.1 什么是协程作用域
// 作用域接口定义
public interface CoroutineScope {
val coroutineContext: CoroutineContext
}
// 所有作用域都提供协程上下文
// 作用域的主要职责:
// 1. 管理协程的生命周期
// 2. 提供取消机制
// 3. 传播异常
// 4. 确保结构化并发
2. 内置作用域类型
2.1 GlobalScope - 全局作用域
// GlobalScope 是应用级别的全局作用域
// ⚠️ 警告:谨慎使用,容易造成内存泄漏
fun useGlobalScope() {
// 启动一个全局协程
val job = GlobalScope.launch {
repeat(10) {
delay(1000)
println("GlobalScope task: $it")
}
}
// 问题:需要手动管理生命周期
// job.cancel() // 必须手动取消
}
// GlobalScope 的特点:
// - 生命周期与应用程序相同
// - 不会自动取消
// - 没有父协程
// - 使用 Dispatchers.Default 作为默认调度器
// GlobalScope 的正确使用场景
object GlobalTasks {
// 场景1:应用级别的后台任务
fun startHeartbeat() {
GlobalScope.launch {
while (isActive) {
sendHeartbeat()
delay(30000) // 每30秒一次
}
}
}
// 场景2:日志收集
fun logEvent(event: String) {
GlobalScope.launch(Dispatchers.IO) {
// 异步记录日志,不阻塞主线程
saveToLogFile(event)
}
}
// 场景3:缓存清理
fun scheduleCacheCleanup() {
GlobalScope.launch {
while (true) {
delay(3600000) // 每小时清理一次
cleanExpiredCache()
}
}
}
}
// GlobalScope 的错误使用示例
class UserRepository {
// ❌ 错误:可能造成内存泄漏
fun loadUserData(userId: String) {
GlobalScope.launch {
// 长时间运行的任务
val data = fetchUserData(userId)
// 如果调用方(如Activity)已经销毁,这里可能崩溃
updateUI(data)
}
}
// ✅ 正确:使用接收的作用域
fun loadUserData(scope: CoroutineScope, userId: String) {
scope.launch {
val data = fetchUserData(userId)
updateUI(data)
}
}
}
2.2 自定义作用域
// 创建自定义作用域
class MyCustomScope : CoroutineScope {
private val job = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("Caught exception: $throwable")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job + exceptionHandler + CoroutineName("MyScope")
fun startTasks() {
// 在自定义作用域中启动协程
launch {
// 任务1
}
launch {
// 任务2
}
}
fun cleanup() {
// 取消作用域中的所有协程
job.cancel()
}
}
// 工厂方式创建作用域
fun createCustomScope(): CoroutineScope {
return CoroutineScope(
Dispatchers.IO +
SupervisorJob() +
CoroutineExceptionHandler { _, e ->
logError("Scope error: $e")
} +
CoroutineName("CustomBackgroundScope")
)
}
3. Android 中的生命周期感知作用域
3.1 lifecycleScope
// lifecycleScope 与 Android 组件生命周期绑定
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// lifecycleScope 在 Activity 销毁时自动取消
// 1. 基本使用
lifecycleScope.launch {
// 在主线程执行(默认 Dispatchers.Main.immediate)
val user = withContext(Dispatchers.IO) {
loadUserData()
}
updateUI(user)
}
// 2. 使用不同的调度器
lifecycleScope.launch(Dispatchers.IO) {
// 在IO线程执行
val data = fetchData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
// 3. 生命周期感知的启动方式
lifecycleScope.launchWhenCreated {
// 只在 CREATED 状态及以上执行
// 当生命周期低于 CREATED 时自动挂起
loadInitialData()
}
lifecycleScope.launchWhenStarted {
// 只在 STARTED 状态及以上执行
// 适合UI更新操作
observeDataUpdates()
}
lifecycleScope.launchWhenResumed {
// 只在 RESUMED 状态执行
// 适合需要用户交互的操作
startAnimation()
}
// 4. 收集 Flow
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 推荐的方式:只在 STARTED 状态收集
viewModel.dataFlow.collect { data ->
updateUI(data)
}
}
}
}
}
// lifecycleScope 在 Fragment 中的使用
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 使用 viewLifecycleOwner 而不是 lifecycleOwner
// 避免 Fragment 视图重建时的生命周期问题
viewLifecycleOwner.lifecycleScope.launch {
// 安全地更新UI
loadFragmentData()
}
// 收集 Flow 的最佳实践
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
}
}
}
}
}
3.2 viewModelScope
// viewModelScope 与 ViewModel 生命周期绑定
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// 1. 基本数据加载
fun loadData() {
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 ?: "Unknown error")
}
}
}
// 2. 并行任务
fun loadMultipleData() {
viewModelScope.launch {
val deferredUser = async { repository.getUser() }
val deferredPosts = async { repository.getPosts() }
val deferredFriends = async { repository.getFriends() }
try {
val user = deferredUser.await()
val posts = deferredPosts.await()
val friends = deferredFriends.await()
_uiState.value = UiState.Success(CombinedData(user, posts, friends))
} catch (e: Exception) {
// 如果任何一个失败,所有都会取消
_uiState.value = UiState.Error(e.message ?: "Failed to load data")
}
}
}
// 3. 使用 supervisorScope 处理独立任务
fun startIndependentTasks() {
viewModelScope.launch {
supervisorScope {
// 任务1:独立运行,失败不影响其他任务
launch {
try {
syncUserData()
} catch (e: Exception) {
logError("Sync failed: $e")
}
}
// 任务2:独立运行
launch {
try {
updateCache()
} catch (e: Exception) {
logError("Cache update failed: $e")
}
}
}
}
}
// 4. 取消特定任务
private var dataJob: Job? = null
fun loadDataWithCancel() {
// 取消之前的任务
dataJob?.cancel()
// 启动新任务
dataJob = viewModelScope.launch {
val data = repository.fetchData()
_uiState.value = UiState.Success(data)
}
}
// 5. 超时控制
fun loadDataWithTimeout() {
viewModelScope.launch {
try {
val data = withTimeout(5000) { // 5秒超时
repository.fetchData()
}
_uiState.value = UiState.Success(data)
} catch (e: TimeoutCancellationException) {
_uiState.value = UiState.Error("Request timeout")
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Error")
}
}
}
}
4. 作用域构建器
4.1 coroutineScope 构建器
// coroutineScope:结构化并发的基础
suspend fun processUserData(userId: String): UserData = coroutineScope {
// coroutineScope 创建一个新的作用域
// 特点:
// 1. 继承父协程的上下文
// 2. 等待所有子协程完成
// 3. 任何一个子协程失败,所有子协程都会取消
// 并发执行多个任务
val userDeferred = async { fetchUser(userId) }
val profileDeferred = async { fetchProfile(userId) }
val postsDeferred = async { fetchPosts(userId) }
try {
// 等待所有任务完成
val user = userDeferred.await()
val profile = profileDeferred.await()
val posts = postsDeferred.await()
UserData(user, profile, posts)
} catch (e: Exception) {
// 任何一个失败,都会取消所有任务
throw ProcessUserException("Failed to process user data", e)
}
}
// 嵌套使用
suspend fun complexOperation(): Result = coroutineScope {
// 第一层:获取基础数据
val baseData = async { fetchBaseData() }.await()
// 第二层:基于基础数据获取更多数据
coroutineScope {
val detailsDeferred = async { fetchDetails(baseData.id) }
val relatedDeferred = async { fetchRelated(baseData.id) }
val details = detailsDeferred.await()
val related = relatedDeferred.await()
Result(baseData, details, related)
}
}
4.2 supervisorScope 构建器
// supervisorScope:允许子协程独立失败
suspend fun startIndependentTasks() = supervisorScope {
// supervisorScope 特点:
// 1. 使用 SupervisorJob
// 2. 子协程失败不会影响其他子协程
// 3. 不会自动取消其他子协程
// 任务1:即使失败也不影响其他任务
val task1 = launch(CoroutineExceptionHandler { _, e ->
println("Task1 failed: $e")
}) {
riskyOperation1()
}
// 任务2:独立运行
val task2 = launch {
safeOperation()
}
// 任务3:也独立运行
val task3 = launch {
anotherRiskyOperation()
}
// 等待所有任务完成(或失败)
joinAll(task1, task2, task3)
}
// 实际应用:批量处理,允许部分失败
suspend fun batchProcess(items: List<Item>): BatchResult = supervisorScope {
val results = mutableListOf<ItemResult>()
val errors = mutableListOf<Throwable>()
// 为每个项目启动独立协程
items.map { item ->
launch(CoroutineExceptionHandler { _, e ->
errors.add(e)
}) {
val result = processItem(item)
results.add(result)
}
}.joinAll() // 等待所有协程完成
BatchResult(results, errors)
}
4.3 withContext 构建器
// withContext:切换上下文
suspend fun loadData(): Data {
// withContext 不是创建新的作用域,而是切换上下文
// 1. 切换到IO线程执行耗时操作
val rawData = withContext(Dispatchers.IO) {
fetchFromNetwork()
}
// 2. 在Default线程池处理数据
val processedData = withContext(Dispatchers.Default) {
processData(rawData)
}
// 3. 返回主线程更新状态
withContext(Dispatchers.Main) {
updateLoadingState(false)
}
return processedData
}
// withContext 的嵌套使用
suspend fun complexWorkflow() {
// 外层:IO操作
val data = withContext(Dispatchers.IO) {
val raw = fetchData()
// 内层:在IO线程中继续执行CPU密集型操作
withContext(Dispatchers.Default) {
processRawData(raw)
}
}
// 回到调用者的调度器(可能是Main)
displayData(data)
}
5. 作用域与结构化并发
5.1 结构化并发原理
// 结构化并发:父协程的生命周期包含子协程
fun demonstrateStructuredConcurrency() {
val parentScope = CoroutineScope(Dispatchers.Default)
parentScope.launch {
println("Parent started")
// 子协程1
launch {
delay(1000)
println("Child 1 completed")
}
// 子协程2
launch {
delay(2000)
println("Child 2 completed")
}
// 等待所有子协程完成
delay(1500)
// 取消父协程会取消所有子协程
// 但如果子协程已经完成,则不受影响
}
// 取消整个作用域
Thread.sleep(500)
parentScope.cancel() // 会取消父协程和所有子协程
}
// 非结构化并发 vs 结构化并发
class TaskManager {
// ❌ 非结构化:难以管理
private val jobs = mutableListOf<Job>()
fun startUnstructuredTasks() {
repeat(5) { i ->
val job = GlobalScope.launch {
delay(1000 * i)
println("Task $i completed")
}
jobs.add(job)
}
// 需要手动管理所有job
}
fun cancelAllUnstructured() {
jobs.forEach { it.cancel() }
jobs.clear()
}
// ✅ 结构化:易于管理
private val structuredScope = CoroutineScope(Dispatchers.Default)
fun startStructuredTasks() {
structuredScope.launch {
repeat(5) { i ->
launch {
delay(1000 * i)
println("Structured task $i completed")
}
}
}
}
fun cancelAllStructured() {
structuredScope.cancel() // 一键取消所有
}
}
5.2 作用域层次与取消传播
// 作用域层次和取消传播
fun demonstrateCancellationPropagation() {
val scope = CoroutineScope(Dispatchers.Default + CoroutineName("RootScope"))
val parentJob = scope.launch(CoroutineName("Parent")) {
println("${coroutineContext[CoroutineName]} started")
// 子协程1
val child1 = launch(CoroutineName("Child1")) {
try {
repeat(10) { i ->
delay(500)
println("${coroutineContext[CoroutineName]} iteration $i")
}
} finally {
println("${coroutineContext[CoroutineName]} cancelled, cleaning up...")
delay(100) // 清理工作
println("${coroutineContext[CoroutineName]} cleanup completed")
}
}
// 子协程2
val child2 = launch(CoroutineName("Child2")) {
try {
repeat(5) { i ->
delay(1000)
println("${coroutineContext[CoroutineName]} iteration $i")
}
} finally {
println("${coroutineContext[CoroutineName]} cancelled")
}
}
delay(1200)
println("${coroutineContext[CoroutineName]} cancelling children")
child1.cancel() // 只取消child1,child2继续运行
child1.join() // 等待child1完成取消
child2.join() // 等待child2自然完成
}
Thread.sleep(3000)
println("Cancelling entire scope")
scope.cancel() // 取消整个作用域
}
6. 作用域的最佳实践
6.1 创建自定义作用域的指导原则
// 自定义作用域工厂
object CoroutineScopes {
// 场景1:后台任务作用域
val backgroundScope: CoroutineScope by lazy {
CoroutineScope(
Dispatchers.IO +
SupervisorJob() +
CoroutineExceptionHandler { _, e ->
Log.e("BackgroundScope", "Unhandled exception", e)
} +
CoroutineName("BackgroundScope")
)
}
// 场景2:网络请求作用域
val networkScope: CoroutineScope by lazy {
CoroutineScope(
Dispatchers.IO +
SupervisorJob() +
CoroutineExceptionHandler { _, e ->
Analytics.logError("NetworkError", e)
} +
CoroutineName("NetworkScope") +
// 添加超时配置
object : AbstractCoroutineContextElement(Key), CompletableJob {
override val key: CoroutineContext.Key<*> = Key
override fun complete() = Unit
override fun completeExceptionally(exception: Throwable) = Unit
override fun getOnJoin(): SelectClause0? = null
override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
= NonDisposableHandle
override fun isActive(): Boolean = true
override fun isCancelled(): Boolean = false
override fun isCompleted(): Boolean = false
override fun join() { throw UnsupportedOperationException() }
override suspend fun join() { throw UnsupportedOperationException() }
override fun start(): Boolean = true
companion object Key : CoroutineContext.Key<*>
}
)
}
// 场景3:数据库操作作用域
val databaseScope: CoroutineScope by lazy {
CoroutineScope(
Dispatchers.IO +
Job() + // 使用普通Job,一个失败全部取消
CoroutineExceptionHandler { _, e ->
Log.e("DatabaseScope", "Database operation failed", e)
} +
CoroutineName("DatabaseScope")
)
}
// 清理所有作用域
fun cleanup() {
backgroundScope.cancel()
networkScope.cancel()
databaseScope.cancel()
}
}
// 封装业务特定的作用域
class FeatureScope(
private val parentScope: CoroutineScope,
private val featureName: String
) : CoroutineScope {
private val featureJob = SupervisorJob(parentScope.coroutineContext[Job])
override val coroutineContext: CoroutineContext
get() = parentScope.coroutineContext + featureJob + CoroutineName(featureName)
// 启动特定类型的任务
fun launchDataTask(block: suspend CoroutineScope.() -> Unit): Job {
return launch(Dispatchers.IO) {
try {
block()
} catch (e: Exception) {
handleFeatureError(e)
throw e
}
}
}
fun launchUITask(block: suspend CoroutineScope.() -> Unit): Job {
return launch(Dispatchers.Main) {
try {
block()
} catch (e: Exception) {
showErrorToUser(e)
}
}
}
// 取消此功能的所有协程
fun cancelFeature() {
featureJob.cancel()
}
private fun handleFeatureError(e: Exception) {
// 特定的错误处理逻辑
}
private fun showErrorToUser(e: Exception) {
// 显示错误给用户
}
}
6.2 避免常见的作用域错误
// 常见错误和解决方案
class ScopeAntiPatterns {
// ❌ 错误1:泄漏的作用域
object LeakyScope {
private val scope = CoroutineScope(Dispatchers.IO)
fun processData(data: String) {
scope.launch {
// 长时间运行的任务
heavyProcessing(data)
// 问题:scope永远不会被取消
}
}
}
// ✅ 解决方案1:提供清理方法
object CleanScope {
private val scope = CoroutineScope(Dispatchers.IO)
fun processData(data: String): Job {
return scope.launch {
heavyProcessing(data)
}
}
fun cleanup() {
scope.cancel()
}
}
// ❌ 错误2:在错误的线程更新UI
class WrongThreadUI {
fun updateData() {
GlobalScope.launch(Dispatchers.IO) {
val data = fetchData()
// 错误:在IO线程更新UI
updateUI(data) // 可能崩溃
}
}
}
// ✅ 解决方案2:正确切换线程
class CorrectThreadUI {
fun updateData() {
// 假设这是Activity的方法
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
fetchData()
}
// 正确:自动回到主线程
updateUI(data)
}
}
}
// ❌ 错误3:忽视取消
class IgnoreCancellation {
suspend fun longRunningTask() {
// 问题:不检查协程是否活跃
repeat(100) {
heavyComputation() // 即使被取消也会继续执行
// 应该检查 isActive
}
}
}
// ✅ 解决方案3:协作式取消
class CooperativeCancellation {
suspend fun longRunningTask() {
ensureActive() // 先检查是否活跃
repeat(100) { i ->
ensureActive() // 定期检查
heavyComputation(i)
// 或者使用 yield() 让出执行权并检查取消
yield()
}
}
suspend fun ioOperationWithCancellation() {
// 对于阻塞操作,使用可取消的变体
withContext(Dispatchers.IO) {
// 使用可取消的阻塞调用
runInterruptible {
Thread.sleep(1000) // 现在可以被取消了
}
}
}
}
// ❌ 错误4:异常处理不当
class BadExceptionHandling {
fun startTask() {
GlobalScope.launch {
try {
riskyOperation()
} catch (e: Exception) {
// 只处理了直接异常
println("Caught: $e")
}
}
}
}
// ✅ 解决方案4:全面异常处理
class GoodExceptionHandling {
private val scope = CoroutineScope(
Dispatchers.Default +
SupervisorJob() +
CoroutineExceptionHandler { _, e ->
// 处理未捕获的异常
Log.e("Coroutine", "Uncaught exception", e)
}
)
fun startTask() {
scope.launch {
try {
riskyOperation()
} catch (e: SpecificException) {
// 处理特定异常
handleSpecificError(e)
} catch (e: Exception) {
// 处理其他异常
handleGenericError(e)
} finally {
// 清理资源
cleanup()
}
}
}
}
}
6.3 作用域的性能优化
// 作用域的性能考虑
class ScopePerformance {
// 1. 重用作用域而不是频繁创建
object ScopeReuse {
// ❌ 不好:频繁创建新作用域
fun processItemBad(item: Item) {
val scope = CoroutineScope(Dispatchers.IO) // 每次都创建
scope.launch {
process(item)
}
// 需要手动取消和清理
}
// ✅ 好:重用作用域
private val processingScope = CoroutineScope(
Dispatchers.IO +
SupervisorJob() +
CoroutineName("ProcessingScope")
)
fun processItemGood(item: Item): Job {
return processingScope.launch {
process(item)
}
}
fun cleanup() {
processingScope.cancel()
}
}
// 2. 合理配置调度器
object DispatcherConfiguration {
// 根据任务类型选择合适的调度器
// IO密集型任务
val ioScope = CoroutineScope(Dispatchers.IO.limitedParallelism(64))
// CPU密集型任务
val cpuScope = CoroutineScope(Dispatchers.Default.limitedParallelism(4))
// 单线程任务(需要顺序执行)
val singleThreadScope = CoroutineScope(newSingleThreadContext("SingleThread"))
// 无限制任务(谨慎使用)
val unlimitedScope = CoroutineScope(Dispatchers.IO.limitedParallelism(Int.MAX_VALUE))
}
// 3. 监控作用域状态
class MonitoredScope(
name: String,
parentScope: CoroutineScope
) : CoroutineScope {
override val coroutineContext: CoroutineContext
private val activeJobs = ConcurrentHashMap<Job, String>()
private val jobCounter = AtomicInteger(0)
init {
val job = SupervisorJob(parentScope.coroutineContext[Job])
coroutineContext = parentScope.coroutineContext + job + CoroutineName(name)
}
override fun launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val jobId = "Job-${jobCounter.incrementAndGet()}"
val job = super.launch(context, start) {
activeJobs[coroutineContext[Job]!!] = jobId
try {
block()
} finally {
activeJobs.remove(coroutineContext[Job])
}
}
return job
}
fun getActiveJobCount(): Int = activeJobs.size
fun getActiveJobIds(): List<String> = activeJobs.values.toList()
fun cancelAll() {
coroutineContext[Job]?.cancel()
activeJobs.clear()
}
}
}
7. 实际项目中的应用模式
7.1 MVVM 架构中的作用域使用
// 完整的 MVVM 协程作用域模式
class UserViewModel(
private val userRepository: UserRepository,
private val analytics: Analytics
) : ViewModel() {
// UI状态
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Idle)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
// 专门的协程用于收集Flow
private var collectionJob: Job? = null
// 初始化
init {
observeUserUpdates()
}
// 方法1:加载用户数据
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
// 使用 withTimeout 防止长时间挂起
val user = withTimeout(10000) {
userRepository.getUser(userId)
}
_uiState.value = UserUiState.Success(user)
analytics.logEvent("user_loaded", mapOf("userId" to userId))
} catch (e: TimeoutCancellationException) {
_uiState.value = UserUiState.Error("Request timeout")
analytics.logError("user_load_timeout", e)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message ?: "Unknown error")
analytics.logError("user_load_failed", e)
}
}
}
// 方法2:并发加载用户详情
fun loadUserDetails(userId: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
supervisorScope {
try {
val userDeferred = async { userRepository.getUser(userId) }
val postsDeferred = async { userRepository.getUserPosts(userId) }
val friendsDeferred = async { userRepository.getUserFriends(userId) }
val user = userDeferred.await()
val posts = postsDeferred.await()
val friends = friendsDeferred.await()
_uiState.value = UserUiState.DetailsSuccess(
user = user,
posts = posts,
friends = friends
)
} catch (e: Exception) {
_uiState.value = UserUiState.Error("Failed to load details")
}
}
}
}
// 方法3:观察数据更新
private fun observeUserUpdates() {
// 取消之前的观察
collectionJob?.cancel()
collectionJob = viewModelScope.launch {
userRepository.userUpdates
.filterNotNull()
.distinctUntilChanged()
.collect { user ->
_uiState.value = UserUiState.Success(user)
}
}
}
// 方法4:执行后台任务
fun syncUserData() {
viewModelScope.launch {
// 使用不同的上下文执行后台任务
withContext(Dispatchers.IO + CoroutineName("UserSync")) {
userRepository.syncWithBackend()
}
}
}
// 清理资源
override fun onCleared() {
super.onCleared()
collectionJob?.cancel()
}
}
sealed class UserUiState {
object Idle : UserUiState()
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class DetailsSuccess(val user: User, val posts: List<Post>, val friends: List<User>) : UserUiState()
data class Error(val message: String) : UserUiState()
}
7.2 在 Repository 中使用作用域
class UserRepository(
private val api: UserApi,
private val database: UserDatabase,
private val ioScope: CoroutineScope // 注入作用域
) {
// 方法1:获取用户,带缓存
suspend fun getUser(userId: String): User {
// 先尝试从数据库获取
val cachedUser = withContext(Dispatchers.IO) {
database.userDao().getById(userId)
}
if (cachedUser != null) {
// 异步更新缓存
ioScope.launch {
try {
val freshUser = api.getUser(userId)
database.userDao().insert(freshUser)
} catch (e: Exception) {
// 静默失败,使用缓存数据
Log.w("UserRepository", "Failed to refresh user cache", e)
}
}
return cachedUser
}
// 缓存未命中,从网络获取
return withContext(Dispatchers.IO) {
val user = api.getUser(userId)
database.userDao().insert(user)
user
}
}
// 方法2:批量处理
suspend fun syncAllUsers(): SyncResult = supervisorScope {
val results = mutableListOf<User>()
val errors = mutableListOf<Throwable>()
val userIds = database.userDao().getAllIds()
// 并发同步所有用户,但限制并发数
val semaphore = Semaphore(5) // 最多5个并发请求
userIds.map { userId ->
launch {
semaphore.withPermit {
try {
val user = api.getUser(userId)
database.userDao().insert(user)
results.add(user)
} catch (e: Exception) {
errors.add(e)
}
}
}
}.joinAll()
SyncResult(results, errors)
}
// 方法3:分页加载
fun getUsersPaginated(pageSize: Int): Flow<PagingData<User>> {
return Pager(
config = PagingConfig(
pageSize = pageSize,
enablePlaceholders = false,
maxSize = pageSize * 3
),
pagingSourceFactory = { UserPagingSource(api, database) }
).flow.cachedIn(ioScope) // 使用作用域缓存Flow
}
data class SyncResult(
val successful: List<User>,
val errors: List<Throwable>
)
}
8. 测试中的作用域
8.1 测试作用域
class CoroutineTestExample {
@Test
fun testViewModelCoroutines() = runTest {
// runTest 提供 TestScope 和 TestDispatcher
val viewModel = UserViewModel(
userRepository = mockRepository,
analytics = mockAnalytics
)
// 1. 测试正常流程
viewModel.loadUser("123")
// 推进时间
advanceTimeBy(1000)
// 验证状态
assertEquals(UiState.Success(expectedUser), viewModel.uiState.value)
// 2. 测试取消
val job = viewModelScope.launch {
viewModel.loadUserDetails("123")
}
advanceTimeBy(500)
job.cancel() // 模拟用户取消
// 验证取消后的状态
assertEquals(UiState.Idle, viewModel.uiState.value)
// 3. 测试超时
viewModel.loadUser("timeout-user")
advanceTimeBy(11000) // 超过10秒超时
assertEquals(UiState.Error("Request timeout"), viewModel.uiState.value)
}
@Test
fun testRepositoryConcurrency() = runTest {
val testScope = this
val repository = UserRepository(
api = mockApi,
database = mockDatabase,
ioScope = testScope // 注入测试作用域
)
// 测试并发请求
val results = mutableListOf<User>()
// 启动多个并发请求
repeat(10) { i ->
launch {
val user = repository.getUser("user$i")
results.add(user)
}
}
// 等待所有完成
advanceUntilIdle()
assertEquals(10, results.size)
}
}
总结
Kotlin 协程作用域是结构化并发的核心,正确使用作用域可以:
- 管理生命周期:自动取消,避免内存泄漏
- 组织代码结构:清晰的父子关系,易于理解和维护
- 控制并发:通过作用域限制并发数量
- 统一错误处理:集中的异常处理机制
- 资源管理:自动清理资源
关键原则:
- 优先使用
lifecycleScope和viewModelScope(Android) - 避免使用
GlobalScope,除非是真正的全局任务 - 使用
coroutineScope和supervisorScope构建结构化并发 - 为不同的任务类型创建专门的作用域
- 始终考虑取消和异常处理