协程调度
🌟 协程调度结构
// 想象一个工厂的工作流程:
val scope = CoroutineScope(
SupervisorJob() + // 工厂主管
Dispatchers.IO + // 工作车间
CoroutineName("Coil") // 工人工牌
)
💡 详细解析
// 1. SupervisorJob
class SupervisorExample {
/*
就像工厂主管:
- 普通Job: 一个工人出错,整条生产线停止
- SupervisorJob: 一个工人出错,其他工人继续工作
*/
fun example() {
val supervisor = SupervisorJob()
// 工人1失败不影响工人2
scope.launch(supervisor) { // 工人1
throw Exception("工人1出错")
}
scope.launch(supervisor) { // 工人2
// 继续工作
}
}
}
// 2. Dispatchers.IO
class DispatcherExample {
/*
就像工作车间:
- Main: 展厅(UI线程)
- IO: 生产车间(适合IO操作)
- Default: 计算车间(适合计算密集)
*/
fun example() {
scope.launch(Dispatchers.IO) {
// 在IO车间工作
loadImage() // 加载图片
saveToCache() // 保存缓存
}
}
}
// 3. CoroutineName
class CoroutineNameExample {
/*
就像工人工牌:
- 方便调试和日志追踪
- 识别协程来源
*/
fun example() {
scope.launch(CoroutineName("图片加载工人")) {
// 工作时能清楚知道是谁在干活
}
}
}
⚡ 实际运作示例
// 完整的工作流程
class CoilWorkflow {
val imageLoader = ImageLoader(context) {
val scope = CoroutineScope(
SupervisorJob() + // 主管监督
Dispatchers.IO + // 在IO车间工作
CoroutineName("Coil-Worker") // 带工牌
)
scope.launch {
// 工人1: 加载图片
async { loadImage() }
// 工人2: 处理缓存
async { handleCache() }
// 即使工人1失败
// 工人2依然继续工作
}
}
}
🔄 工作流程图
工厂运作流程:
SupervisorJob (工厂主管)
↓
监督管理
↓
Dispatchers.IO (工作车间)
↓
具体工作
↓
CoroutineName (工人工牌)
📊 实际应用场景
// 图片加载实例
class ImageLoadingExample {
fun loadImages() {
val scope = CoroutineScope(
SupervisorJob() + // 主管
Dispatchers.IO + // 车间
CoroutineName("ImageLoader") // 工牌
)
scope.launch {
// 并行加载多张图片
val images = listOf(
async { loadImage("1.jpg") },
async { loadImage("2.jpg") },
async { loadImage("3.jpg") }
)
// 即使某张图片加载失败
// 其他图片仍继续加载
images.awaitAll()
}
}
}
⚠️ 注意事项
class CoroutinePrecautions {
// 1. 资源管理
fun cleanup() {
scope.cancel() // 下班时关闭工厂
}
// 2. 异常处理
fun handleErrors() {
scope.launch {
try {
loadImage()
} catch (e: Exception) {
// 工人出错时的处理方案
reportError(e)
}
}
}
}
记住:
- SupervisorJob 是宽容的主管
- Dispatchers.IO 是专门的工作车间
- CoroutineName 是工人的身份标识
- 三者配合实现高效工作
协程job
// Job就像是工作任务的"跟踪单"
val job = launch {
// 任务内容
}
1. 基本功能:
// 1. 检查任务状态
job.isActive // 任务是否在进行中(像跟踪快递:在途中)
job.isCompleted // 任务是否完成(快递已送达)
job.isCancelled // 任务是否被取消(快递被退回)
// 2. 等待任务完成
job.join() // 等待任务完成(像等快递送达)
// 3. 取消任务
job.cancel() // 取消任务(像取消快递订单)
生活中的比喻:
想象Job像餐厅的点餐单:
val orderJob = launch {
// 准备食物
}
orderJob.isActive // 厨师正在做
orderJob.isCompleted // 菜已经做好了
orderJob.isCancelled // 顾客取消了订单
orderJob.join() // 等待上菜
orderJob.cancel() // 取消订单
2. 父子关系:
val parentJob = launch {
val childJob1 = launch {
// 子任务1
}
val childJob2 = launch {
// 子任务2
}
}
生活中的比喻:
像项目经理分配任务:
- parentJob = 项目总任务
- childJob1 = 小组A的任务
- childJob2 = 小组B的任务
如果项目取消(parentJob.cancel()),
所有小组的任务也会取消
3. 异常处理:
val job = launch {
try {
// 可能出错的任务
} catch (e: Exception) {
// 处理错误
}
}
生活中的比喻:
像送快递:
- 正常情况:送到目的地
- 异常情况:地址错误、收件人不在
- try-catch就像快递员的应急预案
4. 任务协调:
// 等待多个任务完成
val job1 = launch { task1() }
val job2 = launch { task2() }
val job3 = launch { task3() }
joinAll(job1, job2, job3) // 等待所有任务完成
生活中的比喻:
像等待多个快递:
- job1 = 衣服快递
- job2 = 食品快递
- job3 = 图书快递
joinAll = 等所有快递都送到
5. 超时处理:
val job = launch {
withTimeout(5000L) { // 5秒超时
// 任务内容
}
}
生活中的比喻:
像餐厅承诺的出餐时间:
- 如果30分钟内没做好
- 顾客可以取消订单
6. 任务状态转换:
New(新建)→ Active(活动)→ Completed(完成)
↓
Cancelled(取消)
生活中的比喻:
像快递状态:
待揽收 → 运输中 → 已送达
↓
已取消
使用示例:
// 实际应用场景
coroutineScope.launch {
val jobs = listOf(
launch { downloadFile() },
launch { processData() },
launch { updateUI() }
)
// 等待所有任务完成
jobs.joinAll()
// 或者取消所有任务
jobs.forEach { it.cancel() }
}
它就像是一个任务的"管理器",可以:
- 跟踪任务状态
- 控制任务执行
- 处理任务异常
- 协调多个任务
- 管理任务生命周期
协程Flow
让我用生动的例子来讲解Flow,特别是冷流和热流的区别:
1. Flow基础概念:
// Flow就像是一个传送带
val flow = flow {
emit(1) // 发送数据
emit(2)
emit(3)
}
// 收集数据
flow.collect { value ->
println(value)
}
生活比喻:
Flow像是点外卖的过程:
- emit 就像厨师做菜
- collect 就像顾客接收外卖
- 不collect(没人点餐)就不会emit(不会做菜)
2. 冷流(Cold Flow):
val coldFlow = flow {
println("开始准备数据")
emit("数据1")
emit("数据2")
emit("数据3")
}
// 每个collect都会重新执行流
coldFlow.collect { println("第一个收集者: $it") }
coldFlow.collect { println("第二个收集者: $it") }
生活比喻:
冷流像点外卖:
- 每个人点餐,厨师都要重新做一份
- 不点餐就不会做
- 点了才开始做
3. 热流(Hot Flow):
val sharedFlow = MutableSharedFlow<String>()
// 或者
val stateFlow = MutableStateFlow("初始值")
// 多个收集者共享同一个数据流
launch { sharedFlow.collect { println("收集者1: $it") } }
launch { sharedFlow.collect { println("收集者2: $it") } }
// 发送数据
sharedFlow.emit("新数据") // 两个收集者都会收到
生活比喻:
热流像看直播:
- 主播开始直播(emit数据)
- 所有观众(collectors)同时看到
- 不看也在直播
- 中途进来的观众只能看到之后的内容
4. 对比表格:
特性 冷流(Flow) 热流(SharedFlow/StateFlow)
启动时机 首次收集时 创建后立即开始
数据重放 每次收集都重新执行 多个收集者共享数据
资源消耗 按需执行 持续运行
使用场景 数据库查询、文件读取 实时更新、UI状态
5. 面试重点例子:
// 冷流例子:数据库查询
val userFlow = flow {
println("执行数据库查询")
val users = database.queryUsers()
users.forEach { emit(it) }
}
// 热流例子:UI状态更新
val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
// 实时位置更新
val locationFlow = MutableSharedFlow<Location>()
6. 实际应用场景:
// 冷流:文件下载
val downloadFlow = flow {
val file = downloadFile()
emit(file)
}
// 热流:聊天消息
val messageFlow = MutableSharedFlow<Message>()
面试回答要点:
- 概念对比:
冷流:
- 懒加载(lazy)
- 每个收集者获取完整数据
- 适合一次性数据获取
热流:
- 立即执行
- 多个收集者共享数据
- 适合实时更新场景
- 使用场景:
冷流适合:
- 数据库查询
- 文件操作
- 网络请求
热流适合:
- UI状态管理
- 实时数据更新
- 事件总线
- 性能考虑:
冷流:
- 按需执行,节省资源
- 适合大量数据处理
热流:
- 持续运行,消耗资源
- 适合实时性要求高的场景
生活类比总结:
冷流像点外卖:
- 点了才做
- 每个人都是新做的
- 适合个性化需求
热流像看直播:
- 持续进行
- 所有人看到一样的
- 适合实时共享
协程中launch与async区别
让我用生动的例子来解释 launch 和 async 的区别:
1. 返回值不同:
// launch: "发射不管" - 不返回结果
val job = launch {
println("发射火箭")
// 返回 Job
}
// async: "等待结果" - 返回 Deferred
val deferred = async {
println("计算结果")
42 // 返回 Deferred<Int>
}
val result = deferred.await() // 获取结果
2. 生活中的例子:
launch 像是快递员:
// 派送包裹
launch {
deliverPackage() // 送完就完了,不需要等待回执
}
// 类似于:
快递员:"包裹已送到,我走了"
// 不需要等待收件人确认
async 像是外卖员:
// 送外卖
val delivery = async {
deliverFood() // 需要等待顾客签收
"送达成功" // 返回送达状态
}
val status = delivery.await() // 等待送达确认
// 类似于:
外卖员:"等待顾客确认收货"
// 需要等待顾客确认才能完成订单
3. 实际应用场景:
使用 launch:
// 适合"发射不管"的场景
class LoggerService {
fun logEvent(event: String) {
scope.launch {
// 记录日志
saveLog(event)
// 不关心结果
}
}
}
// 发送通知
fun sendNotification() {
launch {
// 发送推送
pushNotification()
// 不需要等待结果
}
}
使用 async:
// 需要结果的场景
class ProductViewModel {
fun loadProduct(id: String) {
viewModelScope.launch {
// 并行加载数据
val product = async { repository.getProduct(id) }
val reviews = async { repository.getReviews(id) }
// 等待所有数据
val productInfo = ProductInfo(
product.await(), // 需要等待结果
reviews.await() // 需要等待结果
)
// 更新UI
showProduct(productInfo)
}
}
}
4. 形象比喻:
launch 像播放音乐:
// 按下播放键就行,不需要返回值
launch {
playMusic()
}
async 像煮咖啡:
// 需要等待咖啡煮好,才能喝
val coffee = async {
brewCoffee()
}
drinkCoffee(coffee.await()) // 等咖啡煮好才能喝
5. 错误处理:
// launch:适合"发了不管"的场景
launch {
try {
riskyOperation()
} catch (e: Exception) {
// 处理错误
handleError(e)
}
}
// async:需要处理结果的场景
val result = async {
riskyOperation()
}.await() // 可能抛出异常
6. 组合使用:
// 在一个协程中组合使用
suspend fun loadData() = coroutineScope {
// 后台任务,不需要结果
launch {
trackAnalytics()
}
// 需要等待结果的任务
val data = async {
fetchData()
}
// 使用结果
processData(data.await())
}
总结:
-
launch:- "发射不管"
- 不返回结果
- 适合后台任务
- 返回 Job
-
async:- "等待结果"
- 返回 Deferred
- 适合需要结果的场景
- 需要调用 await()
选择建议:
- 不需要结果用
launch - 需要结果用
async - 需要并行处理用
async