使用kotlin协程提高app性能(译)

协程是一种并发设计模式,您可以在Android上使用它来简化异步执行的代码。Kotlin1.3版本添加了 Coroutines,并基于其他语言的既定概念。

在Android上,协程有助于解决两个主要问题:

  • 管理长时间运行的任务,否则可能会阻止主线程并导致应用冻结。
  • 提供主安全性,或从主线程安全地调用网络或磁盘操作。

本主题描述了如何使用Kotlin协程解决这些问题,使您能够编写更清晰,更简洁的应用程序代码。

管理长时间运行的任务

在Android上,每个应用程序都有一个主线程来处理用户界面并管理用户交互。如果您的应用程序为主线程分配了太多工作,那么应用程序可能会明显卡顿或运行缓慢。网络请求,JSON解析,从数据库读取或写入,甚至只是迭代大型列表都可能导致应用程序运行缓慢,导致可见的缓慢或冻结的UI对触摸事件响应缓慢。这些长时间运行的操作应该在主线程之外运行。

以下示例显示了假设的长期运行任务的简单协程实现:

suspend fun fetchDocs() {        // Dispatchers.Main
 val result = get("https://developer.android.com") // Dispatchers.IO for `get`
 show(result)          // Dispatchers.Main
}

suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }

协同程序通过添加两个操作来处​​理长时间运行的任务,从而构建常规功能。除了invoke(或call)和返回之外,协同程序还添加了suspend和resume:

  • suspend暂停当前协同程序的执行,保存所有局部变量。
  • resume恢复从暂停的协同处继续执行暂停的协同程序。

您只能从其他suspend函数调用suspend函数,或者使用诸如启动之类的协程构建器来启动新的协程。

在上面的示例中,get()仍然在主线程上运行,但它在启动网络请求之前挂起协同程序。当网络请求完成时,get恢复暂停的协程,而不是使用回调来通知主线程。

Kotlin使用堆栈框架来管理与任何局部变量一起运行的函数。挂起协程时,将复制并保存当前堆栈帧以供以后使用。恢复时,堆栈帧将从保存位置复制回来,并且该函数将再次开始运行。即使代码看起来像普通的顺序阻塞请求,协程也可以确保网络请求避免阻塞主线程。

Use coroutines for main-safety

Kotlin协程使用调度程序来确定哪些线程用于协程执行。要在主线程之外运行代码,您可以告诉Kotlin协程在Default或IO调度程序上执行工作。在Kotlin中,所有协同程序必须在调度程序中运行,即使它们在主线程上运行。协同程序可以暂停,调度程序负责恢复它们。
要指定协程应该运行的位置,Kotlin提供了三个可以使用的调度程序:

  • Dispatchers.Main  - 使用此调度程序在主Android线程上运行协同程序。 这应该仅用于与UI交互并执行快速工作。 示例包括调用挂起函数,运行Android UI框架操作以及更新LiveData对象。
  • Dispatchers.IO  - 此调度程序已经过优化,可在主线程外执行磁盘或网络I / O. 示例包括使用Room组件,读取或写入文件以及运行任何网络操作。
  • Dispatchers.Default  - 此调度程序已经过优化,可以在主线程之外执行CPU密集型工作。 示例用例包括对列表进行排序和解析JSON。

继续前面的示例,您可以使用调度程序重新定义get函数。 在get的主体内部,调用withContext(Dispatchers.IO)来创建一个在IO线程池上运行的块。 放在该块中的任何代码总是通过IO调度程序执行。 由于withContext本身是一个挂起函数,因此函数get也是一个挂起函数。

使用协同程序,您可以调度具有细粒度控制的线程。 因为withContext()允许您控制任何代码行的线程池而不引入回调,所以您可以将它应用于非常小的函数,例如从数据库读取或执行网络请求。 一个好的做法是使用withContext()来确保每个函数都是主安全的,这意味着您可以从主线程调用该函数。 这样,调用者永远不需要考虑应该使用哪个线程来执行该函数。

在前面的示例中,fetchDocs()在主线程上执行; 但是,它可以安全地调用get,后者在后台执行网络请求。 因为协同程序支持挂起和恢复,所以只要withContext块完成,主线程上的协程就会以get结果恢复。

重要说明:使用suspend并不能告诉Kotlin在后台线程上运行函数。 暂停函数在主线程上运行是正常的。 在主线程上启动协同程序也很常见。 当您需要主安全时,例如在读取或写入磁盘,执行网络操作或运行CPU密集型操作时,应始终在挂起函数内使用withContext()。

与等效的基于回调的实现相比,withContext()不会增加额外的开销。 此外,在某些情况下,可以优化withContext()调用,而不是基于等效的基于回调的实现。 例如,如果一个函数对网络进行十次调用,则可以通过使用外部withContext()告诉Kotlin只切换一次线程。 然后,即使网络库多次使用withContext(),它仍然停留在同一个调度程序上,并避免切换线程。 此外,Kotlin优化了Dispatchers.Default和Dispatchers.IO之间的切换,以尽可能避免线程切换。

要点:使用使用Dispatchers.IO或Dispatchers.Default等线程池的调度程序并不能保证该块从上到下在同一个线程上执行。 在某些情况下,Kotlin协程可能会在暂停和恢复后将执行移动到另一个线程。 这意味着线程局部变量可能不会指向整个withContext()块的相同值。

指定CoroutineScope

定义协程时,还必须指定其CoroutineScope。 CoroutineScope管理一个或多个相关协程。 您还可以使用CoroutineScope在该范围内启动新协程。 但是,与调度程序不同,CoroutineScope不会运行协同程序。

CoroutineScope的一个重要功能是当用户离开应用程序中的内容区域时停止协程执行。 使用CoroutineScope,您可以确保正确停止任何正在运行的操作。

将CoroutineScope与Android架构组件配合使用

在Android上,您可以将CoroutineScope实现与组件生命周期相关联。这样可以避免泄漏内存或为与用户不再相关的activity或fragment执行额外的工作。使用Jetpack组件,它们自然适合ViewModel。由于ViewModel在配置更改(例如屏幕旋转)期间不会被销毁,因此您不必担心协同程序被取消或重新启动。

范围知道他们开始的每个协同程序。这意味着您可以随时取消在作用域中启动的所有内容。范围传播自己,所以如果一个协程开始另一个协同程序,两个协同程序具有相同的范围。这意味着即使其他库从您的范围启动协程,您也可以随时取消它们。如果您在ViewModel中运行协同程序,这一点尤为重要。如果因为用户离开了屏幕而导致ViewModel被销毁,则必须停止它正在执行的所有异步工作。否则,您将浪费资源并可能泄漏内存。如果您在销毁ViewModel后应该继续进行异步工作,则应该在应用程序架构的较低层中完成。

警告:通过抛出CancellationException协同取消协同程序。 在协程取消期间触发捕获异常或Throwable的异常处理程序。

使用适用于Android体系结构的KTX库组件,您还可以使用扩展属性viewModelScope来创建可以运行的协同程序,直到ViewModel被销毁。

启动一个协程

您可以通过以下两种方式之一启动协同程序:

  • launch会启动一个新的协程,并且不会将结果返回给调用者。 任何被认为是“发射并忘记”的工作都可以使用launch来开始。
  • async启动一个新的协同程序,并允许您使用名为await的挂起函数返回结果。

通常,您应该从常规函数启动新协程,因为常规函数无法调用等待。 仅在另一个协同程序内部或在挂起函数内部执行并行分解时才使用异步。

在前面的示例的基础上,这里是一个带有viewModelScope KTX扩展属性的协程,它使用launch从常规函数切换到协同程序:

fun onDocsNeeded() {
 viewModelScope.launch { // Dispatchers.Main
  fetchDocs()   // Dispatchers.Main (suspend function call)
 }
}

警告:启动和异步处理异常的方式不同。 由于async期望在某个时刻最终调用await,它会保留异常并在await调用中重新抛出它们。 这意味着如果您使用await从常规函数启动新的协同程序,则可能会以静默方式删除异常。 这些丢弃的异常不会出现在崩溃指标中,也不会出现在logcat中。

并行分解

当函数返回时,必须停止由挂起函数启动的所有协同程序,因此您可能需要保证这些协程在返回之前完成。 通过Kotlin中的结构化并发,您可以定义一个启动一个或多个协同程序的coroutineScope。 然后,使用await()(对于单个协同程序)或awaitAll()(对于多个协程),可以保证这些协程在从函数返回之前完成。

例如,让我们定义一个以异步方式获取两个文档的coroutineScope。 通过在每个延迟引用上调用await(),我们保证在返回值之前两个异步操作都完成:

suspend fun fetchTwoDocs() =
 coroutineScope {
  val deferredOne = async { fetchDoc(1) }
  val deferredTwo = async { fetchDoc(2) }
  deferredOne.await()
  deferredTwo.await()
 }

即使fetchTwoDocs()使用异步启动新的协同程序,该函数也会使用awaitAll()等待那些启动的协同程序在返回之前完成。 但请注意,即使我们没有调用awaitAll(),coroutineScope构建器也不会恢复调用fetchTwoDocs的协程,直到所有新的协程完成。

此外,coroutineScope捕获协程抛出的任何异常并将它们路由回调用者。

有关并行分解的更多信息,请参阅编写挂起函数。

具有内置支持的架构组件

一些体系结构组件(包括ViewModel和Lifecycle)通过其自己的CoroutineScope成员包含对协同程序的内置支持。
例如,ViewModel包含一个内置的viewModelScope。 这提供了在ViewModel范围内启动协同程序的标准方法,如以下示例所示:

class MyViewModel : ViewModel() {

 fun launchDataLoad() {
  viewModelScope.launch {
   sortList()
   // Modify UI
  }
 }

 /**
 * Heavy operation that cannot be done in the Main Thread
 */
 suspend fun sortList() = withContext(Dispatchers.Default) {
  // Heavy work
 }
}

LiveData还使用带有liveData块的协同程序:
liveData {
 // runs in its own LiveData-specific scope
}

原文

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Kotlin学习教程之协程Coroutine

    定义 Coroutine翻译为协程,Google翻译为协同程序,一般也称为轻量级线程,但需要注意的是线程是操作系统里的定义概念,而协程是程序语言实现的一套异步处理的方法. 在Kotlin文档中,Coroutine定义为一个可被挂起的计算实例,下面话不多说了,来一起看看详细的介绍吧. 配置 build.gradle中dependencies 添加下面2行,注意coroutine目前仍处于experiment阶段,但Kotline官方保证向前兼容. dependencies { implementa

  • 一篇文章揭开Kotlin协程的神秘面纱

    前言 Kotlin协程提供了一种新的异步执行方式,但直接查看库函数可能会有点混乱,本文中尝试揭开协程的神秘面纱. 理论 它是什么 这是别人翻译: 协程把异步编程放入库中来简化这类操作.程序逻辑在协程中顺序表述,而底层的库会将其转换为异步操作.库会将相关的用户代码打包成回调,订阅相关事件,调度其执行到不同的线程(甚至不同的机器),而代码依然想顺序执行那么简单. 我的理解:子任务程协作运行,优雅的处理异步问题解决方案. 它能干什么? 我在做安卓开发,它能替换掉Handler,AsyncTask 甚至

  • 利用Kotlin的协程实现简单的异步加载详解

    前言 众所周知在android中当执行程序的耗时超过5秒时就会引发ANR而导致程序崩溃.由于UI的更新操作是在UI主线程进行的,理想状态下每秒展示60帧时人眼感受不到卡顿,1000ms/60帧,即每帧绘制时间不应超过16.67ms.如果某项操作的耗时超过这一数值就会导致UI卡顿.因此在实际的开发中我通常把耗时操作放在一个新的线程中(比如从网络获取数据,从SD卡读取图片等操作),但是呢在android中UI的更新只能在UI主线程中进行更新,因此当我们在非UI线程中执行某些操作的时候想要更新UI就需

  • 使用kotlin协程提高app性能(译)

    协程是一种并发设计模式,您可以在Android上使用它来简化异步执行的代码.Kotlin1.3版本添加了 Coroutines,并基于其他语言的既定概念. 在Android上,协程有助于解决两个主要问题: 管理长时间运行的任务,否则可能会阻止主线程并导致应用冻结. 提供主安全性,或从主线程安全地调用网络或磁盘操作. 本主题描述了如何使用Kotlin协程解决这些问题,使您能够编写更清晰,更简洁的应用程序代码. 管理长时间运行的任务 在Android上,每个应用程序都有一个主线程来处理用户界面并管理

  • Android kotlin+协程+Room数据库的简单使用

    Room Room是Google为了简化旧版的SQLite操作专门提供的 1.拥有了SQLite的所有操作功能 2.使用简单(类似于Retrofit),通过注解的方式实现相关功能.编译时自动生成实现类impl 3.LiveData,LifeCycle,Paging天然融合支持 导入 ... plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' id 'kotlin-kap

  • kotlin 协程上下文异常处理详解

    目录 引言 一.协程上下文 1.CoroutineContext 2.CorountineScope 3.子协程继承父协程 二.协程的异常传递 1.协程的异常传播 2.不同上下文(没有继承关系)之间协程异常会怎么样? 3.向用户暴露异常 三.协程的异常处理 使用SupervisorJob 异常捕获器CoroutineExceptionHandler Android中全局异常的处理 引言 从前面我们可以大致了解了协程的玩法,如果一个协程中使用子协程,那么该协程会等待子协程执行结束后才真正退出,而达

  • Kotlin 协程的取消机制详细解读

    目录 引言 协程的状态 取消协程的用法 协程取消的有效性 如何写出可以取消的代码 在 finally 中释放资源 使用不可取消的 block CancellationException 超时取消 异步的超时和资源 取消检查的底层原理 引言 在 Java 语言中提供了线程中断的能力,但并不是所有的线程都可以中断的,因为 interrupt 方法并不是真正的终止线程,而是将一个标志位标记为中断状态,当运行到下一次中断标志位检查时,才能触发终止线程. 但无论如何,终止线程是一个糟糕的方案,因为在线程的

  • Kotlin协程到底是如何切换线程的

    随着kotlin在Android开发领域越来越火,协程在各个项目中的应用也逐渐变得广泛 但是协程到底是什么呢? 协程其实是个古老的概念,已经非常成熟了,但大家对它的概念一直存在各种疑问,众说纷纷 有人说协程是轻量级的线程,也有人说kotlin协程其实本质是一套线程切换方案 显然这对初学者不太友好,当不清楚一个东西是什么的时候,就很难进入为什么和怎么办的阶段了 本文主要就是回答这个问题,主要包括以下内容 1.关于协程的一些前置知识 2.协程到底是什么? 3.kotlin协程的一些基本概念,挂起函数

  • Kotlin协程上下文与上下文元素深入理解

    目录 一.EmptyCoroutineContext 二.CombinedContext 三.Key与Element 四.CoroutineContext 五.AbstractCoroutineContextKey与AbstractCoroutineContextElement 一.EmptyCoroutineContext EmptyCoroutineContext代表空上下文,由于自身为空,因此get方法的返回值是空的,fold方法直接返回传入的初始值,plus方法也是直接返回传入的cont

  • Kotlin协程概念原理与使用万字梳理

    目录 一.协程概述 1.概念 2.特点 3.原理 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 三.协程使用 1.协程的启动 2.协程间通信 3.多路复用 4.序列生成器 5.协程异步流 6.全局上下文 一.协程概述 1.概念 协程是Coroutine的中文简称,co表示协同.协作,routine表示程序.协程可以理解为多个互相协作的程序.协程是轻量级的线程,它的轻量体现在启动和切换,协程的启动不需要申请额外的堆栈空间:协程的切换发生在

  • Kotlin协程操作之创建启动挂起恢复详解

    目录 一.协程的创建 1.start方法 2.CoroutineStart类 3.startCoroutineCancellable方法 4.createCoroutineUnintercepted方法 5.createCoroutineFromSuspendFunction方法 二.协程的启动 1.ContinuationImpl类 2.resumeCancellableWith方法 3.BaseContinuationImpl类 4.invokeSuspend方法 三.协程的挂起与恢复 下面

  • Kotlin协程launch原理详解

    目录 正文 launch使用 launch原理 CoroutineStart中找invoke方法 startCoroutineCancellable逻辑 小结 正文 launch我们经常用,今天来看看它是什么原理. 建议: 食用本篇文章之前记得先食用Kotlin协程之createCoroutine和startCoroutine launch使用 launch我们应该很熟悉了,随便举个例子: fun main() { val coroutineScope = CoroutineScope(Job(

随机推荐