Kotlin Coroutines执行异步加载示例详解

前言

Kotlin Coroutines是Kotlin推出的新的异步API。并不是解决所有问题的最优方案,但是希望在许多情况下它会使事情变得更容易一些。这里只简单的展示一下这个库在安卓中的具体使用方案。下面话不多说了,来一起看看详细的介绍吧。

引入Coroutines

//在application的build.gradle文件中的android节点添加如下的代码
kotlin {
 experimental {
  coroutines 'enable'
 }
}

//添加下面两行到依赖中
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"

第一个Coroutines示例

通常我们加载一张图片到ImageView中,异步的加载任务如下所示:

fun loadBitmapFromMediaStore(imageId: Int,
        imagesBaseUri: Uri): Bitmap {
 val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString())
 return MediaStore.Images.Media.getBitmap(contentResolver, uri)
}

这个方法必须在后台线程中执行,因为他属于一个IO操作,这意味着我们有很多解决方案可以启动后台任务,一旦该方法返回一个bitmap,我们需要立即显示在Imageview中。

imageView.setImageBitmap(bitmap)

这行代码必须在主线程执行,否则会crash。

以上三行代码如果写到一起将会导致程序卡死或者是闪退,这都取决于合理的选择线程。接下来我们看一下使用kotlin的Coroutines是如何解决这个问题的:

val job = launch(Background) {
 val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString())
 val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,
 launch(UI) {
 imageView.setImageBitmap(bitmap)
 }
}

这里最重要的是launch()和参数Background和UI,launch()表示创建和启动一个Coroutine,Background参数CoroutineContext用来保证在后台线程执行,从而保证应用程序不会卡死或者闪退,你可以声明一个如下面所示的CoroutineContext。

internal val Background = newFixedThreadPoolContext(2, "bg")

这将创建一个新的上下文,并在执行他的任务的时候使用两个常规的线程。

接下来说launch(UI),这将触发另一个coroutine,将执行在Android
的主线程。

可取消

接下来的挑战是处理跟Activity声明周期相关的东西,当你在加载一个任务,还没有执行完的时候离开了该Activity,以至于他在调用imageView.setImageBitmap(bitmap)就会引起crash,所以我们在离开该activity之前就需要取消该任务,这里就用到了launch()方法的返回值job,当activity调用onStop方法时,我们需要使用job来取消任务

job.cancel()

这就像你使用Rxjava时调用dispose和使用AsyncTask时调用cancel函数是一个道理。

LifecycleObserver

Android Architecture Components 给安卓开发者提供了特别多强大的库,其中之一就是Lifecycle API.给我们提供了一个简便的方法用来实时的监听activity和fragment的生命周期,我们定义一下代码与coroutines一起使用。

class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver {
 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
 fun cancelCoroutine() {
 if (!deferred.isCancelled) {
  deferred.cancel()
 }
 }
}

我们创建一个LifecycleOwner的扩展函数:

fun <T> LifecycleOwner.load(loader: () -> T): Deferred<T> {
 val deferred = async(context = Background,
      start = CoroutineStart.LAZY) {
 loader()
 }
 lifecycle.addObserver(CoroutineLifecycleListener(deferred))
 return deferred
}

这个方法中有太多新的东西,接下来一一解释:

现在我们可以在一个activity或fragment中调用load() ,并从该函数中访问生命周期成员,并将我们的CoroutineLifecycleListener添加为观察者。

load方法需要一个loader作为参数,返回一个通用类型T,在load方法中我,我们调用了另外一个Coroutine的创造者async()函数,将会使用Background coroutine上下文在后台线程中执行任务,注意这个方法还有另外一个参数start = CoroutineStart.LAZY,这意味着coroutine不会立即执行,知道被调用为止。

coroutine接着会返回一个Defered<T>对象给调用者,这与我们之前的Job类似,但它也可以携带一个延迟值,如常规Java API中的JavaScript Promise或Future <T> ,更好的是他有一个await方法.

接下来我们定义另外一个扩展函数then() ,这次是在Deferen<T>上面定义,是我们上面的load方法返回的类型,它还将一个lambda作为参数,命名为block,它将T类型的单个对象作为其参数。

infix fun <T> Deferred<T>.then(block: (T) -> Unit): Job {
 return launch(context = UI) {
 block(this@then.await())
 }
}

这个函数将使用launch()函数创建另一个Coroutine ,这次在主线程上运行。传递给此Coroutine的lambda(命名块)将完成的Deferred对象的值作为其参数。我们调用await()来挂起这个Coroutine的执行,直到Deferred对象返回一个值。

这里是coroutine变得如此令人印象深刻的地方。 await()的调用是在主线程上完成的,但是不会阻塞该线程的进一步执行。它将简单地暂停该函数的执行,直到它准备好,当它恢复并将延迟值传递给lambda时。coroutine暂停时,主线程可以继续执行其他的事情。await函数是coroutine中的一个核心概念,是什么创造了整个事物如此有魔力。

load()函数中添加的生命周期观察者将在我们的activity上调用onDestroy()后取消第一个coroutine。这也会导致第二个coroutine被取消,阻止block()被调用。

Kotlin Coroutine DSL

现在我们得到了两个扩展函数和一个会处理coroutine被取消的类,让我们来看看如何使用:

load {
 loadBitmapFromMediaStore(imageId, imagesBaseUri)
} then {
 imageView.setImageBitmap(it)
}

上面的代码中,我们将lambda方法传给load函数,该函数调用loadBitmapFromMediaStore方法,该函数必须在后台线程上执行,直到该方法返回一个Bitmap,load方法的返回值是Deferred<Bitmap>

作为扩展函数,then()方法使用infix声明,尽管load方法中返回的是Deferred<Bitmap> ,但是将会传送给then方法一个bitmap返回值,所以我们可以直接在then方法中调用imageView.setImageBitmap(it)

上面的代码可以用于任何需要在后台线程上发生的异步调用,以及返回值应该返回到主线程的地方,就像上面的例子。它不像RxJava那样可以编写多个调用,但它更容易阅读,可能会涵盖很多最常见的情况。现在你可以安全地做这样的事情,而不必担心在每个调用中造成context泄漏或处理线程;

load { restApi.fetchData(query) } then { adapter.display(it) }

then()和load()方法只不过是这个新库的冰山一角,但是我确实希望在未来的基于Kotlin的Android库中出现类似的东西,一旦coroutine版本达到稳定版本。在此之前,您可以使用或修改上面的代码,或者查看Anko Coroutines。我还在GitHub上发布了一个更完整的版本。 (https://github.com/ErikHellman/KotlinAsyncWithCoroutines (本地下载)).

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

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

相关推荐

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

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

  • Kotlin Coroutines执行异步加载示例详解

    前言 Kotlin Coroutines是Kotlin推出的新的异步API.并不是解决所有问题的最优方案,但是希望在许多情况下它会使事情变得更容易一些.这里只简单的展示一下这个库在安卓中的具体使用方案.下面话不多说了,来一起看看详细的介绍吧. 引入Coroutines //在application的build.gradle文件中的android节点添加如下的代码 kotlin { experimental { coroutines 'enable' } } //添加下面两行到依赖中 implem

  • vue前端性能优化之预加载和懒加载示例详解

    目录 预加载 图片预加载 JS预加载 js的加载方式 preload prefetch Preload & Prefetch 的区别 不同资源加载的优先级规则 懒加载 图片懒加载 路由懒加载 组件懒加载 最后 预加载 预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源:我们使用该技术预先告知浏览器,等下某些资源可能要被使用,先把资源下载下来,不要等使用的时候再下载,可以看出这样的加载技术会增加服务器的压力,但是用户的体验会比较好,因为可以较快的看到后面的

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

  • zTree树形插件异步加载方法详解

    本文实例为大家分享了zTree树形插件异步加载,Struts2框架,供大家参考,具体内容如下 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>异步加载</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/zTree_v

  • 基于js文件加载优化(详解)

    在js引擎部分,我们可以了解到,当渲染引擎解析到script标签时,会将控制权给JS引擎,如果script加载的是外部资源,则需要等待下载完后才能执行. 所以,在这里,我们可以对其进行很多优化工作. 放置在BODY底部 为了让渲染引擎能够及早的将DOM树给渲染出来,我们需要将script放在body的底部,让页面尽早脱离白屏的现象,即会提早触发DOMContentLoaded事件. 但是由于在IOS Safari, Android browser以及IOS webview里面即使你把js脚本放到

  • React 悬浮框内容懒加载实例详解

    目录 界面隐藏 懒加载 React实现 原始代码 放入新的DIV 状态设置 样式设置 事件设置 事件优化 延迟显示悬浮框 悬浮框内容懒加载 完整代码 界面隐藏 一个容器放置视频,默认情况下 display: none; z-index: 0; transform: transform3d(10000px, true_y, true_z); y轴和z轴左边都是真实的(腾讯视频使用绝对定位,因此是计算得到的),只是将其移到右边很远的距离. 懒加载 React监听鼠标移入(获取坐标) 添加事件监听 o

  • classloader类加载器_基于java类的加载方式详解

    基础概念 Classloader 类加载器,用来加载 Java 类到 Java 虚拟机中.与普通程序不同的是.Java程序(class文件)并不是本地的可执行程序.当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader. JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现

  • Python自动重新加载模块详解(autoreload module)

    守护进程模式 使用python开发后台服务程序的时候,每次修改代码之后都需要重启服务才能生效比较麻烦. 看了一下Python开源的Web框架(Django.Flask等)都有自己的自动加载模块功能(autoreload.py),都是通过subprocess模式创建子进程,主进程作为守护进程,子进程中一个线程负责检测文件是否发生变化,如果发生变化则退出,主进程检查子进程的退出码(exist code)如果与约定的退出码一致,则重新启动一个子进程继续工作. 自动重新加载模块代码如下: autorel

  • Springboot常用注解及配置文件加载顺序详解

    Springboot常用注解及底层实现 1.@SpringBootApplication:这个注解标识了一个SpringBoot工程,她实际上是另外三个注解的组合,分别是: @SpringBootConfiguration:源码可以看到,这个注解除了元注解外,实际就只有一个@Configuration,把该类变成一个配置类,表示启动类也是一个配置类: @EnableAutoConfiguration:是开启自动配置的功能,向Spring容器中导入了一个Selector,用来加载ClassPath

  • Android开发之自定义加载动画详解

    目录 一.demo简介 二.分析贪吃动画的尺寸比例 三.画圆 四.实现张嘴闭嘴动画 五.小球移动动画 一.demo简介 1.效果展示如下图,我截了三个瞬间,但其实这是一个连续的动画,就是这个大圆不停地吞下小圆. 2.这个动画可以拆分为两部分,首先是大圆张嘴闭嘴的动画,相当于画一个圆弧,规定一下它的角度就好.小圆就是一个从右向左移动的动画.然后不停地刷新界面,让动画的持续时间为永恒,这样就会有一个持续的动态效果. 二.分析贪吃动画的尺寸比例 1.在制作动画之前,我们要先建一个模型,来确定一下大圆和

随机推荐