Android中的Kt协程Coroutine

定义

协程(Coroutine)是Kotlin提供的一种轻量级线程,用于简化异步编程。它可以在单线程内实现并发操作,通过挂起(suspend)恢复(resume)机制,让异步代码看来像同步代码一样直观。

协程的核心是挂起函数(suspend),它可以在不阻塞当前线程的情况下等待操作完成,完成后自动恢复执行。

特点

  1. 轻量级

    协程运行在用户态,不直接依赖操作系统线程,可以在一个线程中运行成千上万个协程,开销极小。

  2. 结构化并发

    协程通过作用域(CoroutineScope)管理生命周期,确保所有字协程在父协程完成前完成,避免资源泄露。

  3. 取消机制

    协程支持协作取消,可以随时取消不再需要的任务,释放资源。

  4. 异步处理简单

    使用常规的try-catch即可捕捉协程内部异常,或者通过CoroutineExceptionHandler统一处理。

  5. 与Android生命周期集成

    官方提供了lifecycleScope(Activity/Fragment)和viewModelScope(ViewModel),自动感知生命周期,在销毁时自动取消协程。

  6. 调度器(Dispatcher)

    协程可以灵活切换线程:Dispathchers.Main(主线程)、Dispatchers.IO(IO密集型任务)、Disapatchers.Default(CPU密集型任务)等。

Kotlin 协程作用域

Kotlin 协程主要有以下几种作用域构建器:

GlobalScope(全局作用域)

  • 生命周期:应用程序整个生命周期
  • 使用场景:不推荐使用,仅适用于与应用程序生命周期的相同任务
GlobalScope.launch {
    // 不推荐
}
特点:
  1. 不需要在任何范围内,即可启动对象
  2. 容易导致内存泄漏
  3. 无法自动取消
  4. 以及被标记@DelicateCoroutinesApi⚠️

lifecycleScope(生命周期作用域)

  • 生命周期:与Lifecycle绑定(Activity/Fragment)
  • 使用场景:UI相关的协程操作
lifecycleScope.launch {
    // 推荐用于 Activity/Fragment
}
特点:
  1. 自动管理生命周期
  2. Lidecycle销毁时自动取消
  3. 需要引入 lifecycle-runtime-ktx
  4. 推荐使用✅

viewModeScope(ViewModel 作用域)

  • 生命周期:与ViewModel绑定
  • 使用场景:在ViewModel中执行后台任务
viewModelScope.launch {
    // 推荐用于 ViewModel
}
特点:
  1. ViewModel清除时候自动取消
  2. 需要引入lifecycle-viewmodel-ktx
  3. 推荐使用✅

coroutineScope(协程作用域)

  • 生命周期:等待所有的子协程完成
  • 使用场景:需要等待所有的子任务完成的场景
coroutineScope {
    launch { /* 任务 1 */ }
    launch { /* 任务 2 */ }
    // 等待所有任务完成
}
特点:
  1. 会阻塞当前协程直到所有的子协程完成场景
  2. 失败会传播异常,一个失败就会抛出异常一损俱损
  3. 结构化并发

SupervisorScop(监督作用域)

  • 生命周期:等待所有的子协程完成
  • 使用场景:子任务相互独立,一个失败不影响其他任务
supervisorScope {
    launch { 
        // 失败不会影响其他协程
    }
    launch {
        // 继续执行
    }
}
特点:
  1. 子协程失败不会导致其他协程取消,独立失败,互不影响
  2. 需要手动处理异常
  3. 更灵活的错误处理

CoroutineScope(自定义作用域)

  • 生命周期:手动控制
  • 使用场景:自定义生命周期管理
class MyRepository {
    val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    fun doWork() {
        scope.launch {
            // 后台工作
        }
    }
    
    fun cleanup() {
        scope.cancel()
    }
}
特点:
  1. 需要手动创建和取消
  2. 灵活性更高
  3. 需要谨慎管理避免内存泄漏

协程作用域对照表

作用域 生命周期 自动取消 异常传播 使用场景 推荐度
GlobalScope 应用全程 不推荐使用 ⚠️ 不推荐
lifecycleScope Lifecycle UI 层 (Activity/Fragment) ⭐⭐⭐⭐⭐
viewModelScope ViewModel ViewModel 层 ⭐⭐⭐⭐⭐
coroutineScope 子协程完成 需要等待所有子任务 ⭐⭐⭐⭐
supervisorScope 子协程完成 子任务相互独立,互不影响 ⭐⭐⭐⭐
CoroutineScope 手动控制 可配置 自定义场景 ⭐⭐⭐

Kotlin 协程启动器:launch、async、runBlocking 详解

Launch

//源代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
特点
  • 返回类型:Job 【不返回结果】
  • 使用场景:“发射并忘记”,不需要返回结果的异步操作
  • 异常处理:异常会被传播到 CoroutineExceptionHandler或者 父协程
参考案例:
// 在已有的协程作用域中
val job = coroutineScope.launch {
    delay(1000)
    println("执行后台任务")
}

// 可以取消任务,有内鬼终止交易
job.cancel()

async

//源代码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
特点
  • 返回类型:Deferred<T> 可等待的泛型结果
  • 使用场景:需要返回结果的异步操作,可并行执行多个任务
  • 异常处理:异常会在调用wawit()时候抛出
参考案例:
val scope = CoroutineScope(Dispatchers.IO + Job())

// 并行执行多个任务
val deferred1 = scope.async { fetchData1() }
val deferred2 = scope.async { fetchData2() }

// 等待结果
val result1 = deferred1.await()
val result2 = deferred2.await()

// 或使用 awaitAll 并行等待
val results = listOf(deferred1, deferred2).awaitAll()

runBlocking

//源代码
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        // create or use private event loop if no dispatcher is specified
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        // See if context's interceptor is an event loop that we shall use (to support TestContext)
        // or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}
特点
  • 返回类型:T 阻塞当前线程直到完成
  • 使用场景:桥接同步和异步代码,测试环境
  • 异常处理:直接抛出异常
参考案例
// 阻塞主线程
fun main() {
    runBlocking {
        val result = async { loadData() }.await()
        println(result)
    }
    // 上面的代码会阻塞直到协程完成
}

// 测试中使用
@Test
fun testCoroutine() = runBlocking {
    val result = repository.getData()
    assertEquals("expected", result)
}

对比总结表 launch、async、runBlocking

启动器 launch async runBlocking
返回值 Job Deferred<T> T
是否阻塞
获取结果 ✅ await() ✅ 直接返回
使用场景 不关心结果 需要结果 测试/桥接
UI 线程可用
性能开销 ⭐⭐⭐ ⭐⭐

suspend 挂起状态

一个挂起任务runSuspendTalks

suspend fun runSuspendTalks(): String {
    log("协程挂起任务 runSuspendTalks ")
    delay(1000 * 3)
    return "100"
}

反编译成Java语音查看区别

// 方法签名添加了 Continuation 参数
@Nullable
public final String runSuspendTalks(@NotNull Continuation<? super runSuspendTalks> continuation) {
    // 检查 Continuation 是否是特定状态机的实例
    if (continuation instanceof runSuspendTalks$CoroutineImpl) {
        runSuspendTalks$CoroutineImpl coroutineImpl = (runSuspendTalks$CoroutineImpl)continuation;
        int label = coroutineImpl.label;
        coroutineImpl.label = 0;
        
        // 根据状态执行不同的代码段
        if (label == 0) {
            // 正常执行逻辑
            this.log("协程挂起任务 runSuspendTalks ");
            
            // 调用 delay 时会挂起
            Object result = DelayKt.delay(3000L, coroutineImpl);
            if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
                return result; // 挂起,返回挂起点
            }
            
            // 恢复执行
            return "100";
        } else if (label == 1) {
            // 从 delay 挂起点恢复
            // ... 继续执行后续代码
            return "100";
        }
    }
    
    // 创建新的状态机实例
    runSuspendTalks$CoroutineImpl impl = new runSuspendTalks$CoroutineImpl(this, continuation);
    return impl.invokeSuspend(Unit.INSTANCE);
}

==============================  生成的状态机类(伪代码): ===========================================

// 编译器生成的状态机类
final class runSuspendTalks$CoroutineImpl extends CoroutineImpl {
    int label;
    Object L$0; // 局部变量存储
    
    public final Object invokeSuspend(Object result) {
        // 状态机核心逻辑
        switch(label) {
            case 0:
                // 第一次执行
                label = 1;
                this.result = result;
                break;
            case 1:
                // 从挂起恢复
                break;
        }
        return result;
    }
}
关键点
  1. Continuation参数:每个suspend函数都会多出一个Continuation<T>参数,用于回调恢复
  2. 状态机:编译器将suspend函数转换成有限状态机,通过label标记执行位置
  3. 挂起点:每次调用其他suspend函数(如:delay)时:
    • 保存当前状态到Continuation
    • 返回特殊值 COROUTINE_SUSPENDED
    • 异步操作完成后,通过Continuation.resume()恢复
  4. 局部变量提示:suspend函数中的局部变量会提升状态机的字段

withContent调度器

可用withContent切换协程上下文的线程,如 I/O 操作、CPU 密集型任务等

suspend fun fetchUserData(): String {
    // 在 IO 线程执行网络请求或数据库操作
    val userData = withContext(Dispatchers.IO) {
        // 执行耗时的 I/O 操作
        performNetworkRequest()
    }
    
    // 自动返回到原始上下文(可能是主线程)
    // 更新 UI
    updateUI(userData)
    
    return userData
}
  • Dispatchers.Main:Android 主线程,用于更新 UI
  • Dispatchers.IO:适用于 I/O 密集型任务,如网络请求、文件读写
  • Dispatchers.Default:适用于 CPU 密集型任务,例如算法,非I/O操作流
  • Dispatchers.Unconfined:无限制调度器,很少使用

cancel 取消协程

  • Job.cancel()
val job = launch {
    // 协程任务
}
job.cancel()  // 取消协程
  • async 返回的是 Deferred<T>(继承自 Job),取消方式类似 deferred.cancel()
val deferred = scope.async {}
// 取消 async 协程
deferred.cancel()
  • 取消整个 CoroutineScope,及父作用域
val scope = CoroutineScope(Dispatchers.IO + Job())
scope.launch { /* ... */ }
scope.coroutineContext[Job]?.cancel()  // 取消作用域内所有协程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容