Kotlin基础知识点 #143 协程:CoroutineScope和CoroutineContext
typealias是Kotlin提供的类型别名机制,通过为现有类型创建新名称来提升代码可读性。它适合简化复杂类型声明、统一回调类型、提供语义化命名等场景。与内联类不同,typealias不创建新类型,不提供类型安全,只是编译时的别名。在Android开发中,typealias广泛用于简化回调、泛型嵌套、函数类型等声明。使用时注意选择有意义的名称,避免过度使用,需要类型安全时使用内联类。合理使用ty
·
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()
}
}
⚡ 关键要点
- CoroutineScope管理生命周期:所有协程都应在特定scope中启动
- CoroutineContext定义行为:包含调度器、Job、异常处理器等元素
- 上下文继承:子协程继承父协程的上下文,可以覆盖特定元素
- 结构化并发:使用coroutineScope确保所有子协程完成后才返回
- 避免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是掌握协程高级用法的关键。
更多推荐

所有评论(0)