在Android中使用Kotlin Coroutines

闻人嘉木
2023-12-01

What are Coroutines?

拆开Coroutines 这个单词来看 Coroutines = Co + Routines 在这里,Co意味着合作,Routines意味着功能。
这意味着当函数相互合作时,我们将其称为协同程序

Introduction

可以将coroutine视为轻量级线程。与线程一样,协同程序可以并行运行,彼此等待并进行通信。协同程序的最大优点是非常便宜,几乎是免费的:我们可以创建数千个协同程序,并且在性能方面支付很少。另一方面,真正的线程开始并保持昂贵。一千个线程对于现代机器来说可能是一个严峻的挑战。

launch {}不会阻塞一个线程,而只是挂起协同程序本身。当协程正在等待时,线程返回到池中,当等待完成时,协程将在池中的空闲线程上恢复。runBlocking{}会阻塞当前线程,只有执行完它本身之后才会执行线程之外的部分

kotlin coroutines 是一种管理后台线程的新方法。学习如何在Android应用程序中使用Kotlin协同程序 可以通过减少回调需求来简化代码。 协同程序是一种Kotlin特性,可将长时间运行的任务(如数据库或网络访问)的异步回调转换为顺序代码。Why there is a need for Kotlin Coroutines?

让我们采用非常标准的Android应用程序用例,如下所示:

  • 从服务器获取用户。
  • 在UI中显示用户。
fun fetchUser(): User {
    // make network call
    // return user
}

fun showUser(user: User) {
    // show user
}

fun fetchAndShowUser() {
    val user = fetchUser()
    showUser(user)
}

当我们调用fetchAndShowUser函数时,它将抛出NetworkOnMainThreadException,因为主线程上不允许网络调用。

有很多方法可以解决这个问题。其中一些如下:

\1. 使用回调:在这里,我们在后台线程中运行fetchUser,然后使用回调传递结果。

fun fetchAndShowUser() {
    fetchUser { user ->
        showUser(user)
    }
}

\2. 使用RxJava:反应式世界方法。这样我们就可以摆脱嵌套的回调。

fetchUser()
        .subscribeOn(Schedulers.io())
        .observerOn(AndroidSchedulers.mainThread())
        .subscribe { user ->
            showUser(user)
        }

\3. 使用协同程序是的,协同程序。

suspend fun fetchAndShowUser() {
     val user = fetchUser() // fetch on IO thread
     showUser(user) // back on UI thread
}

这里,上面的代码看起来是同步的,但它是异步的。

Launch vs Async in Kotlin Coroutines

启动协程的三种方式:launch {}runBlocking{}和async

launch不会阻塞,runBlocking会阻塞,

    println("Start")

    // Start a coroutine
    GlobalScope.launch {
        delay(3000)
        println("Hello")
    }

    runBlocking {
        delay(2000)
         println("Hello runBlocking")
    }
    println("Stop")

打印结果:

Start
Hello runBlocking
Stop

    println("Start")

    // Start a coroutine
    GlobalScope.launch {
        delay(2000)
        println("Hello")
    }

    runBlocking {
        delay(3000)
         println("Hello runBlocking")
    }
    println("Stop")

Start
Hello
Hello runBlocking
Stop

launch{}不返回任何东西,而async{}返回一个实例Deferred,它有一个await()`返回协同程序结果的函数,就像我们在Java中有future 一样,我们在future.get()中获取结果。

有一个函数fetchUserAndSaveInDatabase,如下所示:

fun fetchUserAndSaveInDatabase() {
    // fetch user from network
    // save user in database
    // and do not return anything
}

现在,我们可以使用launch:

GlobalScope.launch(Dispatchers.IO) {
    fetchUserAndSaveInDatabase() // do on IO thread
}

由于fetchUserAndSaveInDatabase不返回任何内容,我们可以使用launch

但是当我们需要返回结果时,我们需要使用async

我们有两个函数返回User,如下所示:

fun fetchFirstUser(): User {
    // make network call
    // return user
}

fun fetchSeconeUser(): User {
    // make network call
    // return user
}

不需要将上述函数作为挂起,因为我们没有从它们调用任何其他挂起函数。

现在,我们可以使用如下的async

GlobalScope.launch(Dispatchers.Main) {
    val userOne = async(Dispatchers.IO) { fetchFirstUser() }
    val userTwo = async(Dispatchers.IO) { fetchSeconeUser() }
    showUsers(userOne.await(), userTwo.await()) // back on UI thread
}

在这里,它使网络调用并行,等待结果,然后调用showUsers函数。

withContext

withContext只是编写异步的另一种方式,我们不必编写await()

suspend fun fetchUser(): User {
    return withContext(Dispatchers.IO) {
        // make network call
        // return user
    }
}

What you’ll learn

  • 如何调用用协同程序编写的代码并获得结果。
  • 如何使用suspend函数使异步代码顺序。
  • 如何使用launchrunBlocking来控制代码的执行方式。
  • 使用suspendCoroutine将现有API转换为coroutines的技术

Implementation of Kotlin Coroutines in Android

要在Kotlin中使用协程,必须在项目的build.gradle(Module:app)文件中包含coroutines-core库。Android上的Coroutines可作为核心库和Android特定扩展:

dependencies {
  ...
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}
  • kotlinx-corountines-core - 在Kotlin中使用协程的主要接口
  • kotlinx-coroutines-android - 支持协同程序中的Android主线程

现在,我们的函数fecthUser将如下所示:

suspend fun fetchUser(): User {
    return GlobalScope.async(Dispatchers.IO) {
        // make network call
        // return user
    }.await()
}

GlobalScope,await和Dispatchers

Dispatchers:Dispatchers帮助协同程序决定在哪个线程上完成工作。主要有三种类型的调度程序,分别是IO,Default和Main。IO调度程序用于执行与网络和磁盘相关的工作。Default用于执行CPU密集型工作。Main是Android的UI线程。为了使用它们,我们需要在异步函数下包装工作。异步功能如下所示

suspend fun async() // implementation removed for brevity

suspend: Suspend 函数是一个可以启动,暂停和恢复的函数。只允许从协程或其他挂起函数调用挂起函数。您可以看到包含关键字suspend异步函数。所以,为了使用它,我们也需要使我们的函数suspend

因此,fetchAndShowUser只能从另一个挂起函数或协程中调用。我们不能使activity挂起onCreate函数,所以我们需要从如下的协程中调用它:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    GlobalScope.launch(Dispatchers.Main) {
        fetchAndShowUser()
    }

}

这实际上是

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    GlobalScope.launch(Dispatchers.Main) {
        val user = fetchUser() // fetch on IO thread
        showUser(user) // back on UI thread
    }

}

showUser将在UI线程上运行,因为我们已经使用Dispatchers.Main来启动它。

Scopes in Kotlin Coroutines:Kotlin协同程序中的范围非常有用,因为我们需要在活动被销毁后立即取消后台任务。在这里,我们将学习如何使用范围来处理这些类型的情况。

假设我们的活动是范围,一旦活动被销毁,后台任务应该被取消。

在活动中,我们需要实施CoroutineScope。

class MainActivity : AppCompatActivity(), CoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    private lateinit var job: Job

}

在onCreate和onDestory函数中。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    job = Job() // create the Job
}

override fun onDestroy() {
    job.cancel() // cancel the Job
    super.onDestroy()
}

现在,只需使用如下所示的启动:

launch {
    val userOne = async(Dispatchers.IO) { fetchFirstUser() }
    val userTwo = async(Dispatchers.IO) { fetchSeconeUser() }
    showUsers(userOne.await(), userTwo.await())
}

一旦活动被销毁,任务将在运行时被取消,因为我们已经定义了范围。

当我们需要全局范围是我们的应用范围而不是活动范围时,我们可以使用GlobalScope如下:

GlobalScope.launch(Dispatchers.Main) {
    val userOne = async(Dispatchers.IO) { fetchFirstUser() }
    val userTwo = async(Dispatchers.IO) { fetchSeconeUser() }
}

在这里,即使活动被破坏,fetchUser函数也会继续运行,就像我们使用GlobalScope一样

这就是Kotlin协同程序中的Scopes非常有用的方法。

Kotlin中,所有协程都在CoroutineScope内部运行。 通过它的job来控制协程的生命周期。 取消作用域的job时,它将取消在该作用域中启动的所有协程。 在Android上,您可以使用作用域取消所有正在运行的协程,例如,用户导航远离活动或片段。 范围还允许您指定默认调度程序dispatcher。 调度程序dispatcher控制哪个线程运行协程。

private val viewModelJob = Job()

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

uiScope将在Dispatchers.Main中启动协程,它是Android上的主要线程。 当挂起时,主程序上启动的协程不会阻塞主线程。 在主线程上启动协同程序是合理的默认值。 正如我们稍后将在此代码框中看到的那样,协程可以在调度程序启动后的任何时候切换。 例如,协程可以在主调度程序上启动,然后使用另一个调度程序从主线程解析大的JSON结果。

CoroutineContext

CoroutineScope可以将CoroutineContext作为参数。 CoroutineContext是一组配置协程的属性。 它可以定义线程策略,异常处理程序等。在上面的示例中,我们使用CoroutineContext plus运算符来定义线程策略(Dispatchers.Main)和作业(viewModelJob)。 生成的CoroutineContext是两个上下文的组合。

Cancel the scope when ViewModel is cleared

override fun onCleared() {
    super.onCleared()
    viewModelJob.cancel()
}

ViewModel不再使用时将调用onCleared()并将其销毁。 当用户导航远离使用ViewModel的Activity或Fragment时,通常会发生这种情况。由于viewModelJob作为作业传递给uiScope,当取消viewModelJob时,uiScope启动的每个协同程序也将被取消。 取消任何不再需要的协同程序以避免不必要的工作和内存泄漏非常重要。

使用添加隐式jobCoroutineScope构造函数创建的作用域,您可以使用uiScope.coroutineContext.cancel()取消该作业

 

 

 类似资料: