协程

2023-05-08  本文已影响0人  长点点

安卓开发中kotlin中的协程

一、什么是协程

协程(coroutine)是一种编写异步代码的方式,它可以让我们用同步的方式来写异步的代码,避免了回调地狱和复杂的线程管理。协程可以在不阻塞主线程的情况下,执行一些耗时的任务,例如网络请求、数据库操作或文件读写等。

协程的核心概念有以下几个:

二、如何使用协程

要在安卓开发中使用协程,我们需要添加以下依赖项:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
}

这些依赖项包含了kotlin协程的核心库和安卓专用的扩展函数。

接下来,我们看一些使用协程的示例:

1. 启动一个简单的协程

要启动一个协程,我们需要使用一个协程构建器,例如launchasync。协程构建器是一种特殊的函数,它可以在某个作用域内创建和启动一个新的协程。作用域是一种控制结构,它可以定义协程的生命周期和上下文。

以下是一个使用launch协程构建器在全局作用域内启动一个简单的协程的示例:

fun main() {
    // 在全局作用域内启动一个新的协程
    GlobalScope.launch {
        // 在协程中执行一些耗时操作
        delay(1000) // 模拟网络请求
        println("Hello from coroutine") // 在控制台打印结果
    }
    // 在主线程中继续执行其他任务
    println("Hello from main thread") // 在控制台打印结果
    Thread.sleep(2000) // 阻塞主线程,等待协程完成
}

输出:

Hello from main thread
Hello from coroutine

在上面的示例中,我们使用GlobalScope.launch在全局作用域内启动了一个新的协程。全局作用域是一种预定义的顶层作用域,它可以让我们在应用程序的整个生命周期内创建和运行协程。我们可以给launch传入一个代码块,它会在协程中执行。在代码块中,我们使用了delay函数来模拟一个耗时的网络请求,然后打印了结果。注意,delay是一种特殊的挂起函数,它可以让当前的协程暂停一段时间,而不会阻塞当前的线程。挂起函数是一种只能在协程或其他挂起函数中调用的函数,它可以让协程在等待结果时释放线程资源,以供其他协程或任务使用。

同时,在主线程中,我们也打印了结果,并使用了Thread.sleep函数来阻塞主线程,等待协程完成。注意,这里我们使用了Thread.sleep而不是delay,因为delay只能在协程或挂起函数中使用,而主函数不是一个挂起函数。另外,这里我们使用了Thread.sleep只是为了演示效果,实际开发中不推荐这样做,因为这样会浪费线程资源,并且可能导致应用程序无响应。

从输出结果可以看出,主线程和协程是并发执行的,而不是顺序执行的。这意味着主线程不会等待协程完成就继续执行其他任务。如果我们想要让主线程等待协程完成再执行其他任务,我们可以使用一些同步机制,例如使用joinawait函数来等待协程的结束或结果。

2. 在生命周期感知型组件中使用协程

在安卓开发中,我们通常需要根据组件的生命周期来管理协程的启动和取消,以避免内存泄漏或无用的工作。例如,我们可能只想在Activity或Fragment处于活动状态时才执行某些协程任务,而在它们被销毁时取消这些任务。

为了方便我们在生命周期感知型组件中使用协程,Kotlin提供了一些内置的协程作用域和扩展函数,它们可以让我们根据组件的生命周期自动启动和取消协程。这些作用域和函数包含在KTX扩展中,我们需要添加以下依赖项:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
}

以下是一些常用的生命周期感知型协程作用域和函数:

class MyViewModel : ViewModel() {
    init {
        viewModelScope.launch {
            // 在ViewModelScope中启动一个协程
        }
    }
}
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            // 在LifecycleScope中启动一个协程
        }
    }
}
class MyFragment : Fragment() {
    val viewModel: MyViewModel by viewModel()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle会在每次Lifecycle达到STARTED状态时启动一个新的协程,并在STOPPED状态时取消它
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 在这个挂起函数中执行需要重启的协程任务
                viewModel.someDataFlow.collect { 
                    // 处理数据流
                }
            }
        }
    }
}

可见性来控制收集的协程任务,例如收集数据流或播放音频等。我们可以指定Lifecycle的最低状态(如STARTED或RESUMED),并传入一个数据流作为参数,如下所示:

viewLifecycleOwner.lifecycleScope.launch {
    // flowWithLifecycle会在Lifecycle达到STARTED状态时开始收集数据流,并在STOPPED状态时停止收集
    exampleProvider.exampleFlow()
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect { 
            // 处理数据流
        }
}

3. 在协程中返回结果

有时候,我们需要在协程中执行一些返回结果的异步任务,例如从网络或数据库中获取数据等。这时候,我们可以使用async协程构建器来创建一个返回Deferred对象的协程,然后使用await挂起函数来获取结果。asyncawait的组合类似于Java中的Futureget

以下是一个使用asyncawait的示例:

fun main() = runBlocking {
    // 在主协程中启动两个子协程
    val deferred1 = async { getData1() } // 返回Deferred<String>
    val deferred2 = async { getData2() } // 返回Deferred<String>
    // 等待两个子协程都完成,并获取它们的结果
    val result1 = deferred1.await() // 挂起并获取String
    val result2 = deferred2.await() // 挂起并获取String
    // 在主协程中处理结果
    println("result1: $result1")
    println("result2: $result2")
}

suspend fun getData1(): String {
    delay(1000) // 模拟耗时操作
    return "Hello"
}

suspend fun getData2(): String {
    delay(2000) // 模拟耗时操作
    return "World"
}

输出:

result1: Hello
result2: World

注意,两个子协程是并发执行的,而不是顺序执行的。这意味着总共只花了大约2秒钟,而不是3秒钟。如果我们想要顺序执行两个子协程,我们可以直接在主协程中调用挂起函数,而不需要使用asyncawait,如下所示:

fun main() = runBlocking {
    // 在主协程中顺序调用两个挂起函数
    val result1 = getData1() // 挂起并获取String
    val result2 = getData2() // 挂起并获取String
    // 在主协程中处理结果
    println("result1: $result1")
    println("result2: $result2")
}

suspend fun getData1(): String {
    delay(1000) // 模拟耗时操作
    return "Hello"
}

suspend fun getData2(): String {
    delay(2000) // 模拟耗时操作
    return "World"
}

输出:

result1: Hello
result2: World

这次,两个子协程是顺序执行的,总共花了大约3秒钟。

三、协程的优缺点

协程相比于传统的异步编程方式,有以下一些优点:

当然,协程也有一些缺点,例如:

上一篇下一篇

猜你喜欢

热点阅读