Kotlin基础知识点 #143 协程:CoroutineScope和CoroutineContext

难度等级:⭐⭐⭐

🎯 问题背景

在实际开发中,需要在正确的作用域中启动协程,并配置协程的运行环境(线程、异常处理器、Job等)。CoroutineScope和CoroutineContext提供了这些能力。

// 问题:协程运行在错误的线程上
class UserViewModel : ViewModel() {
    fun loadData() {
        // ❌ 在Main线程执行IO操作,会卡UI
        GlobalScope.launch(Dispatchers.Main) {
            val data = database.query() // IO操作
            textView.text = data
        }
    }
}

// 解决方案:使用正确的作用域和上下文
class UserViewModel : ViewModel() {
    fun loadData() {
        // ✅ 使用viewModelScope和正确的调度器
        viewModelScope.launch {
            val data = withContext(Dispatchers.IO) {
                database.query() // 在IO线程执行
            }
            // 自动切回Main线程更新UI
            textView.text = data
        }
    }
}

💡 核心概念

1. CoroutineScope:协程作用域

CoroutineScope定义了协程的生命周期范围,所有在这个scope中启动的协程都受其管理。

import kotlinx.coroutines.*

// 创建自定义scope
class MyScope {
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)

    fun doWork() {
        scope.launch {
            println("在scope中启动协程")
            delay(1000)
        }
    }

    fun cleanup() {
        job.cancel() // 取消scope中的所有协程
    }
}

// coroutineScope:创建子作用域
suspend fun loadData() = coroutineScope {
    // 这个scope会等待所有子协程完成
    val data1 = async { fetchData1() }
    val data2 = async { fetchData2() }

    // 如果任何子协程失败,所有协程都会被取消
    CombinedData(data1.await(), data2.await())
}

// supervisorScope:子协程失败不影响其他子协程
suspend fun loadDataSafely() = supervisorScope {
    val data1 = async {
        try {
            fetchData1()
        } catch (e: Exception) {
            null
        }
    }

    val data2 = async {
        try {
            fetchData2()
        } catch (e: Exception) {
            null
        }
    }

    CombinedData(data1.await(), data2.await())
}

2. CoroutineContext:协程上下文

CoroutineContext是一组元素的集合,定义了协程的行为(调度器、Job、异常处理器等)。

// CoroutineContext的组成元素
val context: CoroutineContext =
    Dispatchers.Main +              // 调度器
    Job() +                         // Job
    CoroutineName("MyCoroutine") +  // 协程名称
    CoroutineExceptionHandler { _, exception ->
        println("异常: $exception")
    }

// 使用上下文启动协程
fun main() = runBlocking {
    launch(context) {
        println("协程名称: ${coroutineContext[CoroutineName]}")
        println("线程: ${Thread.currentThread().name}")
    }
}

3. Context的继承和覆盖

fun main() = runBlocking {
    println("父协程: ${coroutineContext[Job]}")

    // 子协程继承父协程的上下文
    launch {
        println("子协程1: ${coroutineContext[Job]}")
        println("父Job: ${coroutineContext[Job]?.parent}")
    }

    // 子协程可以覆盖上下文元素
    launch(Dispatchers.IO + CoroutineName("Worker")) {
        println("子协程2: ${coroutineContext[CoroutineName]}")
        println("调度器: ${coroutineContext[CoroutineDispatcher]}")
    }
}

4. 常用的CoroutineScope

// viewModelScope:自动绑定ViewModel生命周期
class UserViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // ViewModel清除时自动取消
            val data = repository.getData()
            _uiState.value = UiState.Success(data)
        }
    }
}

// lifecycleScope:绑定Activity/Fragment生命周期
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 方式1:启动协程,生命周期结束时取消
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 在STARTED状态时收集数据
                viewModel.uiState.collect { state ->
                    updateUI(state)
                }
            }
        }

        // 方式2:在特定生命周期状态启动
        lifecycleScope.launchWhenStarted {
            loadData()
        }
    }
}

// GlobalScope:应用全局作用域(慎用)
object NetworkMonitor {
    fun start() {
        // ⚠️ GlobalScope不会自动取消,需要手动管理
        GlobalScope.launch {
            while (true) {
                checkNetworkStatus()
                delay(5000)
            }
        }
    }
}

代码示例:Android实战

// 自定义Repository Scope
class UserRepository {
    // 创建自己的scope,精确控制生命周期
    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    private val _userFlow = MutableStateFlow<User?>(null)
    val userFlow: StateFlow<User?> = _userFlow

    init {
        // 启动后台数据同步
        scope.launch {
            while (isActive) {
                syncUserData()
                delay(60_000) // 每分钟同步一次
            }
        }
    }

    suspend fun getUser(userId: String): User {
        return withContext(Dispatchers.IO) {
            api.fetchUser(userId)
        }
    }

    fun cleanup() {
        job.cancel()
    }
}

// 组合多个上下文元素
class NetworkService {
    private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        Log.e("NetworkService", "请求失败", exception)
        // 上报错误到监控系统
    }

    private val scope = CoroutineScope(
        Dispatchers.IO +
        SupervisorJob() +
        CoroutineName("NetworkService") +
        exceptionHandler
    )

    fun fetchData(url: String) {
        scope.launch {
            try {
                val response = api.get(url)
                processResponse(response)
            } catch (e: Exception) {
                // 异常会被exceptionHandler捕获
                throw e
            }
        }
    }
}

结构化并发:使用coroutineScope和supervisorScope

// coroutineScope:任何子协程失败都会取消所有协程
suspend fun loadUserProfile(userId: String): UserProfile = coroutineScope {
    try {
        val userDeferred = async { userApi.getUser(userId) }
        val postsDeferred = async { postApi.getPosts(userId) }
        val friendsDeferred = async { friendApi.getFriends(userId) }

        // 如果任何一个请求失败,所有请求都会被取消
        UserProfile(
            user = userDeferred.await(),
            posts = postsDeferred.await(),
            friends = friendsDeferred.await()
        )
    } catch (e: Exception) {
        // 处理异常
        throw ProfileLoadException("加载用户资料失败", e)
    }
}

// supervisorScope:子协程失败不影响其他子协程
suspend fun loadDashboard(): Dashboard = supervisorScope {
    val newsDeferred = async {
        try {
            newsApi.getLatest()
        } catch (e: Exception) {
            emptyList() // 失败返回空列表
        }
    }

    val weatherDeferred = async {
        try {
            weatherApi.getCurrent()
        } catch (e: Exception) {
            null // 失败返回null
        }
    }

    val stocksDeferred = async {
        try {
            stockApi.getMarket()
        } catch (e: Exception) {
            null
        }
    }

    // 各个模块互不影响
    Dashboard(
        news = newsDeferred.await(),
        weather = weatherDeferred.await(),
        stocks = stocksDeferred.await()
    )
}

切换上下文:withContext

class ImageProcessor {
    suspend fun processImage(imageFile: File): Bitmap {
        // 在IO线程读取文件
        val bytes = withContext(Dispatchers.IO) {
            imageFile.readBytes()
        }

        // 在Default线程处理图像
        val processedBitmap = withContext(Dispatchers.Default) {
            BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                .let { applyFilters(it) }
        }

        // 返回Main线程(如果需要)
        return withContext(Dispatchers.Main) {
            processedBitmap.also {
                // 可以在这里更新UI
            }
        }
    }

    private fun applyFilters(bitmap: Bitmap): Bitmap {
        // CPU密集型图像处理
        return bitmap
    }
}

获取和使用当前上下文

suspend fun logCoroutineInfo() {
    println("=== 协程信息 ===")
    println("Job: ${coroutineContext[Job]}")
    println("Dispatcher: ${coroutineContext[CoroutineDispatcher]}")
    println("Name: ${coroutineContext[CoroutineName]}")
    println("Thread: ${Thread.currentThread().name}")

    // 检查是否处于活动状态
    if (coroutineContext[Job]?.isActive == true) {
        println("协程正在运行")
    }
}

// 使用示例
fun main() = runBlocking {
    launch(Dispatchers.IO + CoroutineName("Worker")) {
        logCoroutineInfo()
    }
}

⚡ 关键要点

  1. CoroutineScope管理生命周期:所有协程都应在特定scope中启动
  2. CoroutineContext定义行为:包含调度器、Job、异常处理器等元素
  3. 上下文继承:子协程继承父协程的上下文,可以覆盖特定元素
  4. 结构化并发:使用coroutineScope确保所有子协程完成后才返回
  5. 避免GlobalScope:除非确实需要全局作用域,否则使用受限作用域

最佳实践

// ✅ 使用Android提供的scope
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch { /* ... */ }
    }
}

class MyFragment : Fragment() {
    fun loadData() {
        viewLifecycleOwner.lifecycleScope.launch { /* ... */ }
    }
}

// ✅ 创建自定义scope时使用SupervisorJob
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
}

// ✅ 使用withContext切换线程
suspend fun getData(): Data {
    return withContext(Dispatchers.IO) {
        database.query()
    }
}

// ✅ 使用coroutineScope实现结构化并发
suspend fun loadAll() = coroutineScope {
    val d1 = async { load1() }
    val d2 = async { load2() }
    Pair(d1.await(), d2.await())
}

// ❌ 避免在suspend函数中创建新的scope
suspend fun badExample() {
    CoroutineScope(Dispatchers.IO).launch {
        // 这个协程不受调用者控制
    }
}

🔗 相关知识点

  • #141 协程基础:suspend函数:在scope中执行的函数
  • #142 协程:Job和协程生命周期:CoroutineContext的重要组成部分
  • #144 协程:Dispatchers调度器:CoroutineContext的调度器元素
  • #137 解构声明:解构上下文元素
  • #125 异常处理基础:CoroutineExceptionHandler

提示:理解CoroutineScope和CoroutineContext是掌握协程高级用法的关键。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐