在 Android 中使用协程(Coroutine)

在 Android 中使用协程(Coroutine)是简化异步操作(如网络请求、数据库操作、UI 更新)的最佳实践之一。它能以同步代码的形式编写异步逻辑,避免回调地狱,同时自动处理线程切换,非常适合 Android 开发场景。

一、准备工作:添加依赖

首先,在 app/build.gradle 中添加协程相关依赖(确保版本兼容):

gradle

复制代码
dependencies {
    // 核心协程库
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
    // Android 专用协程(含主线程调度器等)
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
    // (可选)与生命周期组件集成(如 lifecycleScope)
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
    // (可选)与 ViewModel 集成(viewModelScope)
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
}

二、核心概念与 Android 场景结合

在 Android 中使用协程,需重点关注 作用域(Scope)调度器(Dispatcher)暂停函数(Suspend Function),它们直接影响协程的生命周期和线程管理。

1. 协程作用域(Coroutine Scope)

作用域用于管理协程的生命周期,确保协程在不需要时被取消(如页面销毁时),避免内存泄漏。Android 中常用的作用域:

作用域 适用场景 生命周期
lifecycleScope Activity/Fragment 随组件销毁(onDestroy)而取消
viewModelScope ViewModel 随 ViewModel 销毁而取消
CoroutineScope 自定义场景(如全局任务) 需手动管理(通过 Job 取消)

示例:在 Activity 中使用 lifecycleScope lifecycleScopeandroidx.lifecycle:lifecycle-runtime-ktx 提供的扩展,自动绑定组件生命周期:

kotlin

复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 启动协程(自动在 Activity 销毁时取消)
        lifecycleScope.launch {
            // 协程逻辑(如网络请求)
            val data = fetchDataFromNetwork()
            // 自动切换到主线程更新 UI
            binding.textView.text = data
        }
    }
}

示例:在 ViewModel 中使用 viewModelScope viewModelScope 随 ViewModel 生命周期自动取消,适合数据加载:

kotlin

复制代码
class MyViewModel : ViewModel() {
    fun loadData() {
        // 启动协程(ViewModel 销毁时自动取消)
        viewModelScope.launch {
            val data = repository.getData()
            // 更新 LiveData(通知 UI)
            _uiState.value = data
        }
    }
}
2. 调度器(Dispatcher):指定协程运行的线程

Android 中关键的调度器:

调度器 作用 适用场景
Dispatchers.Main 运行在 Android 主线程(UI 线程) 更新 UI(如设置 TextView 文本)
Dispatchers.IO 运行在 IO 线程池(专门处理 IO 操作) 网络请求、数据库操作、文件读写
Dispatchers.Default 运行在 CPU 密集型线程池 复杂计算、数据排序等 CPU 密集任务

注意

  • lifecycleScopeviewModelScope 的默认调度器是 Dispatchers.Main(主线程),但可通过参数指定其他调度器。
  • 耗时操作(如网络请求)必须放在 Dispatchers.IODispatchers.Default 中,避免阻塞主线程。
3. 暂停函数(Suspend Function)

suspend 关键字标记的函数,只能在协程或其他暂停函数中调用。它可以「暂停」执行(释放线程),待任务完成后再「恢复」,但不会阻塞线程。

示例:定义一个网络请求的暂停函数 结合 Retrofit 使用时,可直接将接口方法定义为 suspend 函数(Retrofit 2.6+ 支持):

kotlin

复制代码
// 网络接口(Retrofit)
interface ApiService {
    @GET("data")
    suspend fun fetchData(): Response<Data> // 暂停函数,自动在 IO 线程执行
}

// 仓库层调用
class DataRepository(private val api: ApiService) {
    // 暂停函数:封装网络请求
    suspend fun getData(): Data {
        val response = api.fetchData()
        if (response.isSuccessful) {
            return response.body() ?: throw Exception("空数据")
        } else {
            throw Exception("请求失败")
        }
    }
}

三、完整示例:从网络请求到更新 UI

下面是一个完整流程:在 Activity 中启动协程 → 切换到 IO 线程执行网络请求 → 切换回主线程更新 UI。

kotlin

复制代码
class MainActivity : AppCompatActivity() {
    private val apiService by lazy { RetrofitClient.create(ApiService::class.java) }
    private val repository by lazy { DataRepository(apiService) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 点击按钮触发数据加载
        binding.loadButton.setOnClickListener {
            loadData()
        }
    }

    private fun loadData() {
        // 用 lifecycleScope 启动协程(默认在主线程)
        lifecycleScope.launch {
            // 显示加载中状态
            binding.progressBar.visibility = View.VISIBLE

            try {
                // 切换到 IO 线程执行网络请求(withContext 会暂停当前协程,执行完后恢复)
                val data = withContext(Dispatchers.IO) {
                    repository.getData() // 调用暂停函数
                }

                // 自动切回主线程,更新 UI
                binding.textView.text = data.content
                binding.progressBar.visibility = View.GONE
            } catch (e: Exception) {
                // 处理异常(如网络错误)
                binding.textView.text = "加载失败:${e.message}"
                binding.progressBar.visibility = View.GONE
            }
        }
    }
}

关键说明

  • withContext(Dispatchers.IO):将代码块切换到 IO 线程执行,执行完毕后自动切回原调度器(这里是主线程)。
  • try-catch:捕获网络请求可能抛出的异常(如超时、数据为空),避免应用崩溃。

四、取消协程(避免内存泄漏)

协程作用域(如 lifecycleScopeviewModelScope)会自动取消协程,但如果是自定义作用域,需手动管理:

kotlin

复制代码
// 自定义作用域(需手动取消)
val customScope = CoroutineScope(Dispatchers.Main + Job())

fun startTask() {
    customScope.launch {
        repeat(10) {
            delay(1000) // 模拟耗时操作
            Log.d("Coroutine", "执行中:$it")
        }
    }
}

// 在不需要时取消(如页面销毁)
fun cancelTask() {
    customScope.cancel() // 取消作用域内所有协程
}

五、常见场景与最佳实践

  1. 并行请求 :用 async 同时发起多个请求,再用 awaitAll 等待全部完成:

    kotlin

    复制代码
    lifecycleScope.launch {
        val deferred1 = async(Dispatchers.IO) { repository.getData1() }
        val deferred2 = async(Dispatchers.IO) { repository.getData2() }
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        // 处理两个结果
    }
  2. 延迟任务 :用 delay() 替代 Handler.postDelayed()

    kotlin

    复制代码
    lifecycleScope.launch {
        delay(2000) // 延迟 2 秒(非阻塞)
        binding.textView.text = "延迟后更新"
    }
  3. 与 Room 配合 :Room 支持 suspend 函数,直接在协程中调用数据库操作:

    kotlin

    复制代码
    @Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        suspend fun getUsers(): List<User> // 暂停函数,自动在 IO 线程执行
    }

总结

在 Android 中使用协程的核心是:

  • lifecycleScopeviewModelScope 管理生命周期,避免泄漏;
  • Dispatchers.IO 处理耗时操作,Dispatchers.Main 更新 UI;
  • suspend 函数封装异步逻辑,用 withContext 切换线程。

协程让异步代码更简洁、可读性更强,是 Android 开发的首选异步方案。

相关推荐
FunnySaltyFish17 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z4 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream5 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam5 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc6 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景6 天前
kotlin协程学习小计
android·kotlin