1. 协程基本概念

协程(Coroutine)是用于简化异步编程的一种轻量级线程,能够帮助我们更简单地写出非阻塞UI代码。主要概念包括:

  • 挂起函数(suspend function)
    使用 suspend 修饰的函数可以在协程中挂起执行,而不会阻塞线程。

  • 构建协程的作用域(Coroutine Scope)
    协程必须在某个 CoroutineScope 中启动。Android 中常用的是 lifecycleScope 或 viewModelScope。

  • 启动协程的方式

    • launch {}:启动一个协程,不关心返回结果。
    • async {}:启动一个协程,同时返回一个 Deferred<T> 对象,可以调用 await() 获取结果。
  • 调度器(Dispatchers)

    • Dispatchers.Main:在主线程运行,用于更新 UI。
    • Dispatchers.IO:在 IO 线程运行,用于网络请求或数据库操作。
    • Dispatchers.Default:用于计算任务。

2. 完整示例讲解

以下示例将展示如何在 Android Activity 中使用协程执行网络请求或者耗时操作,同时更新 UI。示例中使用了 lifecycleScope(需要依赖 AndroidX Lifecycle KTX)来管理协程,保证在生命周期结束时自动取消协程。

2.1 示例文件:MainActivity.kt

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * MainActivity 示例展示了如何使用 Kotlin 协程完成以下任务:
 * 1. 使用 lifecycleScope 在主线程中启动协程。
 * 2. 使用 withContext 切换到 IO 线程执行耗时操作。
 * 3. 使用 async/await 并发执行任务并获取返回结果。
 * 4. 更新 UI 或日志输出。
 */
/*
运行顺序:
启动协程,在主线程运行
启动并发任务
在IO线程中执行耗时操作
协程延时结束,更新UI
并发任务结果: 任务1完成, 任务2完成
从IO线程返回结果: 耗时操作结果
 */
class MainActivity : AppCompatActivity() {

    companion object {
        const val TAG = "MainActivity"
    }

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

        // 示例1: 在主线程中启动一个协程,延时后更新 UI
        /*
        lifecycleScope 是一个与 AndroidX Lifecycle 库集成的 Kotlin 协程作用域
        它提供了一种便捷的方式来管理与 Android 组件(如 Activity 或 Fragment)生命周期相关的协程。

        Coroutine Scope (协程作用域):
        在 Kotlin 协程中,所有的协程都需要在一个作用域 (scope) 中启动
        作用域控制着协程的生命周期。当一个作用域被取消时,它内部启动的所有协程也会被取消。
        lifecycleScope 就是一个协程作用域,它与 Android 组件的生命周期相关联。

        launch 是 Kotlin 协程库提供的一个函数,用于启动一个新的协程。
        launch 函数返回一个 Job 对象,该对象代表正在运行的协程
        你可以使用 Job 对象来取消协程、等待协程完成或获取协程的状态。

        Job 对象代表一个正在执行的协程,它是一个可取消的、具有生命周期的后台任务。

        Job 可以被看作是协程的句柄或引用。当你使用 launch 或 async 函数启动一个协程时,这些函数会返回一个 Job 对象。
        你可以使用 Job 对象来控制协程的生命周期,例如取消协程、等待协程完成或获取协程的状态。

        delay 是一个挂起函数。挂起函数只能在协程或另一个挂起函数中调用。
        挂起函数可以暂停协程的执行,而不会阻塞线程。当挂起函数完成时,协程会自动恢复执行。

        suspend 关键字用于声明一个挂起函数 (suspending function)
        挂起函数是一种可以在协程中暂停执行,而不会阻塞线程的特殊函数。
         */
        lifecycleScope.launch {
            Log.d(TAG, "启动协程,在主线程运行")
            // 延时 2 秒,模拟网络请求延时
            delay(2000)
            Log.d(TAG, "协程延时结束,更新UI")
            // 这里可以更新UI,比如更新TextView的文本等
        }

        // 示例2: 切换到 IO 线程执行耗时操作并获取结果,再回到主线程更新UI
        /*
        withContext(Dispatchers.IO) 是一个挂起函数
        用于在指定的协程上下文中执行一段代码块,并在代码块执行完成后自动恢复到原始的协程上下文。

        withContext 函数允许你临时更改协程的上下文,并在代码块执行完成后自动恢复到原始的协程上下文。
         */
        lifecycleScope.launch {
            val result = withContext(Dispatchers.IO) {
                Log.d(TAG, "在IO线程中执行耗时操作")
                // 模拟耗时任务
                delay(3000)
                "耗时操作结果"
            }
            Log.d(TAG, "从IO线程返回结果: $result")
            // 更新UI或处理结果
        }

        // 示例3: 使用 async/await 并发执行多个任务
        /*
        async(Dispatchers.IO) 是一个函数,用于在指定的协程上下文中异步启动一个新的协程
        并返回一个 Deferred 对象,该对象代表异步计算的结果。

        async 函数用于异步启动一个新的协程,并返回一个 Deferred 对象。

        Deferred 对象是 Job 接口的子接口,它代表异步计算的结果。

        await() 是 Deferred 接口提供的一个挂起函数。
        await() 函数用于等待异步计算完成,并返回结果。
        await() 函数只能在协程或另一个挂起函数中调用。
        await() 函数会挂起当前协程,直到异步计算完成。
        如果异步计算成功完成,await() 函数会返回异步计算的结果。
        如果异步计算失败 (例如抛出异常),await() 函数会重新抛出该异常。
         */
        lifecycleScope.launch {
            Log.d(TAG, "启动并发任务")
            // 启动并发任务
            val deferredTask1 = async(Dispatchers.IO) {
                delay(2000)
                "任务1完成"
            }
            val deferredTask2 = async(Dispatchers.IO) {
                delay(2500)
                "任务2完成"
            }
            // 使用 await 获取结果
            val result1 = deferredTask1.await()
            val result2 = deferredTask2.await()
            Log.d(TAG, "并发任务结果: $result1, $result2")
        }
    }
}

2.2 依赖说明

确保在你的项目中已经添加了协程和 lifecycle 的相关依赖。在 app 模块的 build.gradle 文件中添加类似如下依赖项:

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
    // 其他依赖项
}

3. 教学总结

  • 协程启动:推荐在 Android 中使用 lifecycleScope 来启动协程,这样能在 Activity/Fragment 生命周期内自动取消协程任务,避免内存泄漏。
  • 线程切换:使用 withContext(Dispatchers.IO) 将耗时任务放入 IO 线程,处理完毕后返回主线程更新 UI。
  • 并发处理:使用 async 启动并发任务,通过 await() 获取结果,能提高执行效率。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐