Android开发Kotlin协程实战指南:从入门到解决实际问题

做Android开发,你肯定遇到过这样的痛点:发起网络请求时,直接在主线程调用会触发ANR;用Thread+Handler切换线程,代码绕得像团乱麻;处理并发任务时,还要手动管理线程池——这些问题,Kotlin协程能帮你一次性解决。它不是“新的线程框架”,而是更轻量、更易读的异步编程方案,让你用“写同步代码的方式做异步操作”。

Android开发Kotlin协程实战指南:从入门到解决实际问题

协程到底能帮你解决什么问题

先举个最常见的Android场景:你需要从服务器拉取用户信息,然后更新UI。用传统方式,你得这么写:

// 传统Thread+Handler方式
Thread {
    val user = api.getUserInfo() // 网络请求(耗时)
    runOnUiThread {
        tv_user.text = user.name // 更新UI
    }
}.start()

这段代码不算复杂,但如果再加个“读取本地缓存”“处理错误”的逻辑,代码会瞬间变得臃肿。而用协程,你可以写成这样:

// 协程方式
lifecycleScope.launch {
    val cache = withContext(Dispatchers.IO) { getCache() } // 读缓存(IO线程)
    val user = withContext(Dispatchers.IO) { api.getUserInfo() } // 网络请求(IO线程)
    tv_user.text = user.name // 直接更新UI(主线程)
}

没有Thread,没有Handler,代码线性且易读——这就是协程的核心价值:消除异步代码的“回调嵌套”,让异步逻辑像同步代码一样直观

第一步:把协程引入Android项目

要在Android中用协程,首先得加依赖。打开你的app/build.gradle(Module级别),添加以下两行:

dependencies {
    // 协程核心库
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    // Android专属支持(绑定生命周期、主线程调度器)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}

注意:版本号要选最新的(截至2025年8月,1.7.3是稳定版)。添加后同步项目,协程就 ready 了。

用Launch启动第一个协程任务

协程不能“单独存在”,必须运行在协程作用域(CoroutineScope)中。Android官方推荐使用与生命周期绑定的Scope,比如:
lifecycleScope:绑定Activity/Fragment的生命周期,页面销毁时自动取消协程;
viewModelScope:绑定ViewModel的生命周期,ViewModel销毁时自动取消协程;
– (不推荐用GlobalScope,它的生命周期和应用一致,容易导致内存泄漏)

比如,在Activity中启动第一个协程:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 用lifecycleScope启动协程(自动绑定Activity生命周期)
        lifecycleScope.launch {
            Log.d("Coroutine", "协程启动了:${Thread.currentThread().name}")
            delay(1000) // 模拟耗时操作(不会阻塞主线程!)
            Log.d("Coroutine", "协程完成了:${Thread.currentThread().name}")
        }
    }
}

运行这段代码,你会看到日志:
– 启动时的线程是main(主线程);
delay(1000)不会阻塞主线程,因为协程的“挂起”是非阻塞的;
– 完成时的线程还是main——说明协程默认运行在主线程,但能处理耗时操作?

不对,等一下:delay挂起函数(用suspend修饰),它会“暂停”协程的执行,但不会占用线程。真正的耗时操作(比如网络请求、文件IO),需要用withContext切换到后台线程

withContext:切换线程的正确姿势

协程的“线程切换”是它最常用的功能之一。Android中,你需要记住三个核心调度器(Dispatcher):

调度器 适用场景 线程类型
Dispatchers.Main Android主线程,更新UI 单线程(UI线程)
Dispatchers.IO 网络请求、文件IO、数据库操作 线程池(多线程)
Dispatchers.Default CPU密集型任务(比如排序、计算) 线程池(多线程)

比如,你想发起网络请求并更新UI,用withContext切换线程:

lifecycleScope.launch {
    // 1. 切换到IO线程做网络请求(耗时操作)
    val user = withContext(Dispatchers.IO) {
        api.getUserInfo() // 假设api.getUserInfo()是网络请求
    }
    // 2. 自动切回Main线程,更新UI
    tv_user.text = user.name
}

这里的关键是:withContext暂停当前协程,在指定的调度器上执行任务,完成后自动切回原线程(比如Main线程)。你不用手动调用runOnUiThread,代码更简洁。

async/await:处理并发任务的利器

如果需要同时执行多个异步任务(比如同时请求“用户信息”和“订单列表”),async+await是最优解。它能让多个任务“并行执行”,最后合并结果。

比如,你需要同时拉取两个接口的数据:

viewModelScope.launch {
    // 1. 启动两个并发任务(都在IO线程)
    val userDeferred = async(Dispatchers.IO) { api.getUserInfo() }
    val ordersDeferred = async(Dispatchers.IO) { api.getOrders() }

    // 2. 等待两个任务完成(不会阻塞线程)
    val user = userDeferred.await()
    val orders = ordersDeferred.await()

    // 3. 合并结果更新UI
    _uiState.value = UiState.Success(user, orders)
}

async会返回一个Deferred对象(类似“未来的结果”);
await会“等待”任务完成,并获取结果;
– 两个任务是并行执行的,总耗时等于“最长的那个任务”的时间(比如请求用户用1秒,请求订单用1.5秒,总耗时1.5秒),比串行执行(2.5秒)快很多。

协程的取消与异常处理

协程的“可取消性”是它的重要特性——当用户退出页面时,你需要取消未完成的协程,避免内存泄漏或无效请求。

1. 自动取消:用生命周期绑定的Scope

最推荐的方式是使用lifecycleScopeviewModelScope——当页面销毁/ViewModel销毁时,这些Scope会自动取消所有子协程。比如:

// 在ViewModel中使用viewModelScope
class MainViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val data = api.fetchData() // 网络请求
            _data.value = data
        }
    }
}

当用户退出Activity时,ViewModel会被销毁,viewModelScope中的协程会自动取消,无需手动处理。

2. 手动取消:用Job对象

如果需要手动取消协程(比如用户点击“取消按钮”),可以保存Job对象:

class MainActivity : AppCompatActivity() {
    private var fetchJob: Job? = null

    fun startFetch() {
        fetchJob = lifecycleScope.launch {
            val data = api.fetchData()
            tv_data.text = data
        }
    }

    fun cancelFetch() {
        fetchJob?.cancel() // 手动取消协程
    }
}

3. 异常处理:两种常用方式

协程的异常处理和同步代码类似,用try-catchCoroutineExceptionHandler
方式1:局部异常处理(try-catch

lifecycleScope.launch {
    try {
        val data = withContext(Dispatchers.IO) { api.fetchData() }
        tv_data.text = data
    } catch (e: IOException) {
        // 处理网络错误
        tv_error.text = "网络连接失败"
    } catch (e: Exception) {
        // 处理其他错误
        tv_error.text = "加载失败"
    }
}

方式2:全局异常处理(CoroutineExceptionHandler
如果多个协程需要统一处理异常,可以用CoroutineExceptionHandler

// 1. 创建全局异常处理器
val globalExceptionHandler = CoroutineExceptionHandler { _, throwable ->
    Log.e("CoroutineError", "全局异常: ${throwable.message}")
    // 比如弹Toast提示用户
    Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show()
}

// 2. 在启动协程时传入处理器
lifecycleScope.launch(globalExceptionHandler) {
    val data = withContext(Dispatchers.IO) { api.fetchData() }
    tv_data.text = data
}

实战:用协程重构网络请求代码

最后,我们用一个真实的Android场景——“网络请求+更新UI”——来展示协程的威力。假设你用Retrofit做网络请求,传统方式需要用Callback

// 传统Retrofit Callback方式
api.getUserInfo().enqueue(object : Callback<User> {
    override fun onResponse(call: Call<User>, response: Response<User>) {
        if (response.isSuccessful) {
            runOnUiThread { tv_user.text = response.body()?.name }
        }
    }

    override fun onFailure(call: Call<User>, t: Throwable) {
        runOnUiThread { tv_error.text = "加载失败" }
    }
})

用协程重构后,代码会简洁很多:
1. 第一步:给Retrofit接口加suspend修饰符(Retrofit 2.6.0+支持协程):

interface UserApi {
    @GET("user/info")
    suspend fun getUserInfo(): Response<User> // 加suspend修饰
}

2. 第二步:用协程发起请求

lifecycleScope.launch {
    val response = withContext(Dispatchers.IO) { api.getUserInfo() }
    if (response.isSuccessful) {
        tv_user.text = response.body()?.name
    } else {
        tv_error.text = "加载失败(${response.code()})"
    }
}

没有Callback,没有runOnUiThread,代码线性且易维护——这就是协程的魅力。

最后提醒:协程的“避坑指南”

  1. 不要用GlobalScope:它的生命周期和应用一致,容易导致内存泄漏(比如Activity销毁了,协程还在跑);
  2. 不要嵌套withContext:比如withContext(Dispatchers.IO) { withContext(Dispatchers.IO) { ... } },会浪费线程池资源;
  3. lifecycleScope/viewModelScope:优先使用与生命周期绑定的Scope,自动管理协程生命周期;
  4. 不要在Main线程做耗时操作:即使协程默认跑在Main线程,也不能在里面做网络请求或文件IO——一定要用withContext(Dispatchers.IO)切换线程。

学会协程不是“为了用新框架”,而是让你的代码更简洁、更易读、更易维护。它解决的是Android开发中最核心的“异步编程痛点”——如果你还在用Thread+Handler或者AsyncTask,赶紧试试协程吧!

原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/259

(0)