首页 / Android 入门教程 / 协程基础

Android 入门教程

协程基础

本教程共 100 篇 · 第 18 篇 · 更新于 2026-07-28 · 约 9 分钟阅读

AndroidAndroid 入门教程Kotlin协程Coroutinesuspendlaunchasync

18. 协程基础

本节目标:理解协程是什么、为什么 Android 用它处理异步、学会用 launch/async/withContext 写异步代码,知道 Dispatchers 怎么选。

为什么需要协程

Android 主线程(UI 线程)一旦被阻塞超过 5 秒,系统就弹「应用无响应」(ANR)。所以耗时操作(网络、数据库、文件 IO)必须放后台线程。

传统方案各有痛点:

  • Thread:重量级,创建销毁开销大,不好取消。
  • AsyncTask已废弃(API 30 起),代码难读,容易泄漏。
  • Handler / Thread + Runnable:回调嵌套,地狱。
  • RxJava:强大但学习曲线陡,链式调用复杂。

Kotlin 协程用「挂起」代替「阻塞」,让你写异步代码像写同步代码一样顺。轻量(一个线程能跑十万个协程)、内置取消、结构化并发,是 Android 异步的现代标准。

协程是什么

协程(Coroutine)是一种并发设计模式,不是线程。可以理解为「轻量级的线程」:

  • 多个协程跑在一个线程上,通过挂起(suspend)让出执行权,不阻塞线程。
  • 挂起时协程的状态被保存,恢复后接着跑。
  • 一个线程上能同时挂十万个协程,开销远小于十万个线程。

生活化比喻:协程像在厨房同时做几道菜。炖汤要等 30 分钟,你不会傻站着(阻塞),而是切菜、炒肉(挂起后干别的),汤好了回来继续。线程是厨房,协程是菜谱流程。

添加依赖

build.gradle.kts(Module :app)里加:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
    // 版本号用 Version Catalog 管理更好
}

Android 项目通常还要协程和 lifecycle、ViewModel 集成:

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0")

suspend 函数

suspend 关键字标记可挂起的函数,只能在协程里或其他 suspend 函数里调用:

suspend fun fetchUser(userId: String): User {
    delay(1000)              // 模拟网络延迟,不阻塞线程
    return User(userId, "张三")
}
  • delay() 是 suspend 版的 Thread.sleep(),挂起当前协程但不阻塞线程。
  • suspend 函数能像普通函数一样顺序调用,没有回调嵌套。
suspend fun loadAndShow() {
    val user = fetchUser("u1")     // 看起来同步,实际挂起等结果
    println(user.name)
    val posts = fetchPosts(user)   // 接着干
    println(posts)
}
Note
  • suspend 函数不指定在哪个线程跑,由调用它的协程上下文决定。这是协程灵活的地方,也是初学者要理解的点。

协程构建器:launch 与 async

要在普通代码里启动协程,用协程构建器

launch

启动一个不返回结果的协程(类似「发射后不管」):

import kotlinx.coroutines.*

fun main() = runBlocking {     // runBlocking 创建协程作用域,测试用
    launch {
        delay(1000)
        println("World!")
    }
    println("Hello,")
}
// 输出:
// Hello,
// World!

launch 返回一个 Job,可以 cancel()join()

async

启动一个返回结果的协程,用 await() 拿结果:

suspend fun loadData(): Pair<User, List<Post>> = coroutineScope {
    val userDeferred = async { fetchUser("u1") }
    val postsDeferred = async { fetchPosts() }
    // 两个请求并行,等两个都完成
    Pair(userDeferred.await(), postsDeferred.await())
}

async 启动后立刻开始执行,await() 时如果没完成就挂起等。两个 async 配合能并行执行多个任务。

Tip
  • 需要结果用 async,不需要结果(如打日志、发埋点)用 launch。要并行多个任务用多个 async + awaitAll

Dispatchers:协程跑在哪个线程

Dispatchers 决定协程在哪个线程或线程池执行。常用的:

Dispatcher用途
Dispatchers.Main主线程(UI),Android 上更新 UI 必须在这
Dispatchers.IOIO 线程池(网络、文件、数据库),专为阻塞 IO 设计
Dispatchers.DefaultCPU 密集任务(排序、解析、计算),线程数等于 CPU 核数
Dispatchers.Unconfined不指定,跟调用方同线程(少用)

指定方式:

launch(Dispatchers.IO) {
    val data = fetchData()        // 在 IO 线程
    withContext(Dispatchers.Main) {
        textView.text = data      // 切回主线程更新 UI
    }
}

withContext:切换线程

withContext 切换协程到指定 Dispatcher,执行完块后切回原 Dispatcher。是切线程的标准方式:

suspend fun fetchAndDisplay() {
    val data = withContext(Dispatchers.IO) {
        // 这块在 IO 线程
        api.getData()
    }
    // 这里回到原 Dispatcher(如果原是 Main,就在主线程)
    showData(data)
}
Note
  • 别用 Thread.sleep() 或阻塞 API 在 Dispatchers.Main 里。需要阻塞就 withContext(Dispatchers.IO) 切过去。

协程作用域(CoroutineScope)

协程跑在作用域里,作用域管理其内所有协程的生命周期。作用域取消时,里面所有协程都取消。这叫结构化并发

常用作用域

// 1. runBlocking:阻塞当前线程直到协程完成,主要用于测试
runBlocking {
    launch { /* ... */ }
}

// 2. coroutineScope:挂起当前协程直到子协程完成,suspend 函数里用
suspend fun loadAll() = coroutineScope {
    launch { /* ... */ }
    async { /* ... */ }
}

// 3. GlobalScope:全局作用域,生命周期跟应用一样长
// 慎用!容易泄漏,官方不推荐
GlobalScope.launch { /* ... */ }

Android 提供的作用域

AndroidX lifecycle 库给 ViewModel、Activity、Fragment 提供了现成作用域:

// ViewModel 里
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {       // ViewModel 销毁时自动取消
            val data = repo.fetch()
            _state.value = data
        }
    }
}

// Activity / Fragment 里
lifecycleScope.launch {                // 生命周期结束自动取消
    val data = repo.fetch()
    // 更新 UI
}
Warning
  • 不要用 GlobalScope.launch,它没有自动取消机制,Activity 销毁了协程还在跑,会泄漏甚至崩溃。用 viewModelScopelifecycleScope

取消协程

协程支持协作式取消。Job.cancel() 后,协程在下个挂起点退出:

val job = launch {
    repeat(1000) { i ->
        println("处理 $i")
        delay(100)        // 挂起点,这里检查取消
    }
}

delay(500)
job.cancel()               // 取消协程
job.join()                 // 等待完全退出

cancelAndJoin() 是两者合一。

协作式取消的注意点

CPU 密集循环里没有挂起点,cancel 不会立即生效,要主动检查:

val job = launch(Dispatchers.Default) {
    var i = 0
    while (i < Int.MAX_VALUE && isActive) {   // isActive 检查取消
        i++
    }
}

ensureActive()yield() 也能插入取消检查点。

异常处理

协程里抛异常会传播到父协程。async 的异常在 await() 时才抛出。

try {
    val result = async { throw RuntimeException("网络错误") }.await()
} catch (e: Exception) {
    // 处理异常
}

顶层协程建议用 CoroutineExceptionHandler

val handler = CoroutineExceptionHandler { _, exception ->
    Log.e("Coroutine", "崩溃了", exception)
}

viewModelScope.launch(handler) {
    // 异常会被 handler 捕获
}
Tip
  • Android 里推荐用 try-catch 包住可能失败的协程代码,比依赖全局 handler 更可控。SupervisorJob 用于「子协程失败不影响其他子协程」的场景,第 74 章进阶讲。

Android 实战示例

class UserViewModel(
    private val repo: UserRepository
) : ViewModel() {

    private val _state = MutableStateFlow<UiState>(UiState.Loading)
    val state: StateFlow<UiState> = _state.asStateFlow()

    fun loadUser(userId: String) {
        viewModelScope.launch {
            _state.value = UiState.Loading
            try {
                // 并行加载用户和文章
                val userDeferred = async { repo.fetchUser(userId) }
                val postsDeferred = async { repo.fetchPosts(userId) }

                val user = userDeferred.await()
                val posts = postsDeferred.await()

                _state.value = UiState.Success(UserData(user, posts))
            } catch (e: Exception) {
                _state.value = UiState.Error(e.message ?: "加载失败")
            }
        }
    }
}

// Repository 里
class UserRepository(private val api: ApiService) {
    suspend fun fetchUser(id: String): User {
        return withContext(Dispatchers.IO) {
            api.getUser(id)    // Retrofit 协程适配,本身就是 suspend
        }
    }
}

小结

协程是轻量并发方案,用挂起代替阻塞。suspend 函数只能在协程里调。launch 启动无返回值协程,async 启动有返回值协程(await 取结果)。Dispatchers.Main 更新 UI,Dispatchers.IO 做网络/文件,Dispatchers.Default 做 CPU 计算。withContext 切线程。Android 用 viewModelScopelifecycleScope,结构化并发自动取消。第 2 部分结束,下一部分学应用基础与工程结构。