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

前言

众所周知在android中当执行程序的耗时超过5秒时就会引发ANR而导致程序崩溃。由于UI的更新操作是在UI主线程进行的,理想状态下每秒展示60帧时人眼感受不到卡顿,1000ms/60帧,即每帧绘制时间不应超过16.67ms。如果某项操作的耗时超过这一数值就会导致UI卡顿。因此在实际的开发中我通常把耗时操作放在一个新的线程中(比如从网络获取数据,从SD卡读取图片等操作),但是呢在android中UI的更新只能在UI主线程中进行更新,因此当我们在非UI线程中执行某些操作的时候想要更新UI就需要与UI主线程进行通信。在android中google为我们提供了AsyncTask和Handler等工具来便捷的实现线程间的通信。有许多的第三方库也为我们实现了这一功能,比如现在非常流行的RxJava库。在本篇文章中呢我想给大家分享的是使用Kotlin的Coroutine(协程)来实现耗时操作的异步加载,现在有RxJava这么屌的库我们为什么还要了解这个呢?Kotlin如今已是android的官方开发语言了解他里边的异步相关的操作是很有必要的。本文只讲解Coroutine的基本使用方法,并不作深入底层的研究,我将以一个加载图片的例子来向您展示Coroutine的基本使用方法。

使用Coroutine之前的初始配置

首先我们使用android studio 新建一个项目,并在新建项目的时候勾选【Include Kotlin support】,就像下边这样

项目创建成功后,我们需要在build.gradle文件中的android配置模块下面增加如下的配置

kotlin {
 experimental {
 coroutines 'enable'
 }
}

然后在build.gradle文件中添加如下的依赖

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20'
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20'

完整的配置情况如下:

经过上边的步骤Coroutine的配置就已经完成了。接下来我们就可以使用Coroutine了。

实现你的第一个Coroutine程序

现在我们来开始编写我们的第一个Coroutine例子程序,这个程序的主要功能就是从手机媒体中加载一张图片,并把它显示在一个ImageView中。我们先来看看在未使用Coroutine之前使用同步的方式加载图片的代码如下:

val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
imageView.setImageBitmap(bitmap)

在上边的代码中我们从媒体读取了一张图片并把它转化成Bitmap对象。因为这是一个IO操作,如果我们在UI主线程中调用这段代码,将可能导致程序卡顿或产生ANR崩溃,所以我们需要在新开的线程中调用下边的代码

val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)

接着我们需要在UI线程中调用下边的代码来显示加载的图片

imageView.setImageBitmap(bitmap)

为了实现这一功能在传统的android程序中我们需要使用Handler或AsyncTask将结果从非UI主线程发送到UI主线程进行显示,我们需要编写许多额外的代码。并且这些代码的可读性也不是十分的友好。下边我们来看看使用Kotlin的Coroutine来实现图片的加载的代码,如下:

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

我们先忽略返回值job,我们稍后会进行介绍,在这儿我们关心的事情是launch函数和参数Background与UI。与之前使用同步的方式加载图片相比唯一的不同就在于这儿我们调用了lauch函数。lauch()创建并启动了一个协程,这儿的参数Background是一个CoroutineContext对象,确保这个协程运行在一个后台线程,确保你的应用程序不会因耗时操作而阻塞和崩溃。你可以像下边这样定义一个CoroutineContext:

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

他将使用含有两个线程的线程池来执行协程里边的操作。在第一个协程里边我们又调用了launch(UI)创建并启动了一个新的协程,这儿的UI并不是我们自己创建的,他是Kotlin在Android平台里边预定义的一个CoroutineContext,代表着在UI主线程中执行协程里边的操作。所以我们将更新程序界面的操作imageView.setImageBitmap(bitmap)放在了这个协程里。通过这儿的例子代码你会发现在kotlin里边使用协程来实现线程间的通信和切换非常的简单,比RxJava还简单。看上去就跟你写同步的方式的代码一样。

取消协程

在上边的例子中我们返回了一个Job类型的对象job。通过调用job.cancel()我们能够取消一个协程。例如当我们退出当前Activity的时候,图片还没有加载完。这个时候我们就可以在onDestroy中调用job.cancel()来取消这个未完成的任务。这与我们使用Rxjava时调用dipose()或使用AsyncTask时调用cancel() 来取消未完成的操作的作用是一样的。

LifecycleObserver

android 架构组件( Android Architecture Components )里边引入了许多非常好的东西,比如:ViewModel, Room 和 LiveData以及Lifecycle API。给予我们一种非常安全简便的方式监听Activity和Fragment的生命周期变化。接下来我们将使用他们来对之前加载图片的例子进行改进,利用lifecycle对Activity生命周期进行监听并做出相应的处理(监听到Activity调用onDestroy()时自动取消后台任务)。

我们定义如下的代码来使用协程:

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
}

在这个函数里边有许多新的东西,即使看上去感到疑惑也不要紧,我们会一步一步的对其进行讲解。我们在所有实现LifecycleOwner接口的类中扩展了一个load函数。也就是说当我们使用支持库的时候我们可以在Activity或Fragment中直接调用这个load函数(支持库里边的AppCompatActivity和Fragment实现了LifecycleOwner接口)。为了能够在这个函数里边访问lifecycle成员添加CoroutineLifecycleListener作为一个观察者。

load()函数使用名为loader的lambda表达式作为参数(这个lambda表达式返回一个泛型类型T),在load()函数里边我们调用了名叫async的函数,这个函数的作用也是用于创建一个协程。它使用Background作为上下文。注意第二个参数start = CoroutineStart.LAZY。它的意思是不会立即启动一个协程。直到你显示的请求他返回一个值的时候它才会启动,稍后你会看到具体怎样做。这个协程返回了一个Deferred<T>对象到调用者。它与我们之前提到的job对象是类似的,但是他可以携带一个延迟的值,类似于JavaScript 中的Promise或Java APIs中的Future<T>

接下来我们定义Deferred<T>类(前面我们在load函数中返回的类型)的一个扩展函数then() ,它也使用一个名叫block的lambda表达式作为参数。这个lambda表达式以T类型的对象作为参数。具体代码如下:

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

这个函数使用launch()创建了另外一个协程,这个新的协程将运行在程序的主线程中。我们在这个新的协程中调用了then函数中传入的名叫block的lambda表达式并使用await()函数作为它的参数。await()是在主线程中调用的,但是他并不会阻塞主线程的执行,它将挂起这个函数,主线程可以继续做其他的事情。当值从其他协程中返回的时候,他将被唤醒并将值从Deferred传递到这个lambda中。挂起函数(Suspending functions)是协程中最主要的概念。

一旦Activity的onDestroy方法被调用的时候,我们在load()函数中添加的lifecycle观察者将会取消第一个协程,也会使第二个协程被取消,避免block()被调用。

Kotlin Coroutine DSL

上边我们定义了两个扩展函数和一个用于取消协程的类,让我们来看看如何使用它们,代码如下:

load {
 MediaStore.Images.Media.getBitmap(contentResolver,uri)
} then {
 imageView.setImageBitmap(it)
}

在上边的代码中我们传递一个lambda到load()函数中,在这个lambda中调用了loadBitmapFromMediaStore()函数运行在一个后台进程中。一旦loadBitmapFromMediaStore()函数返回Bitmap,load()函数将返回Deferred<Bitmap> 。扩展的函数then()是被infix修饰的,因此当Deferred<Bitmap>返回之后我们可以使用上面那种奇特的语法调用它。我们传递到then()中的lambda将接收到一个Bitmap对象。因此我们可以简单的调用imageView.setImageBitmap(it)显示这个Bitmap。

上边的代码可以被应用到任何别的需要使用异步调用并将值转递到主线程的操作中。和RxJava这种框架比起来Kotlin的协程可能没有它那么强大。但是Kotlin的协程可读性更强,也更简单。现在你可以安全的使用它来执行你的异步操作了,再也不用担心内存泄漏的发生了。如下是将上边的代码用于从网络加载数据并显示的例子:

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

以上就是本篇文章所要分享的全部内容,希望能够对你有所帮助。如果你发现文章中有不对的地方也欢迎你帮忙指出,以便我做出及时的更正。

源码地址: https://github.com/chenyi2013/CoroutineDemo (本地下载)

总结

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

参考文章:

https://developer.android.com/topic/libraries/architecture/lifecycle.html

https://kotlinlang.org/docs/reference/coroutines.html

https://hellsoft.se/simple-asynchronous-loading-with-kotlin-coroutines-f26408f97f46

您可能感兴趣的文章:

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

相关推荐

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

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

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

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

  • Kotlin协程Job生命周期结构化并发详解

    目录 1.Job的生命周期 2.Deffered 3.Job与结构化并发 4.launch和async的使用场景 前面在学习协程启动方式的时候在launch的源码中有一个返回值是Job,async的返回Deferred也是实现了Job,那么而也就是说launch和async在创建一个协程的时候也会创建一个对应的Job对象.还提到过Job是协程的句柄,那么Job到底是什么?它有什么用? 1.Job的生命周期 先看一下Job的源码,这里只保留了跟标题相关的内容 public interface Jo

  • Python获取协程返回值的四种方式详解

    目录 介绍 源码 依次执行结果 介绍 获取协程返回值的四种方式: 1.通过ensure_future获取,本质是future对象中的result方 2.使用loop自带的create_task, 获取返回值 3.使用callback, 一旦await地方的内容运行完,就会运行callback 4.使用partial这个模块向callback函数中传入值 源码 import asyncio from functools import partial async def talk(name): pr

  • 比较简单的异步加载JS文件的代码

    复制代码 代码如下: <script> function getJsFile(url, callBack){ var XH = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject('Msxml2.XMLHTTP'); XH.open('get',url,true); XH.onreadystatechange = function(){ if(XH.readyState == 4 && XH.status

  • kotlin之协程的理解与使用详解

    前言         为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理.协程的概念并不陌生,在python也有提及.任何事务的作用大多是对于所依赖的环境相应而生的,协程对于kotlin这门语言也不例外.协程的优点,总的来说有如下几点:轻量级,占用更少的系统资源: 更高的执行效率: 挂起函数较于实现Runnable或Callable接口更加方便可控: kotlin.coroutine 核心库的支持,让编写异步代码更加简单.当然在一些不适应它的用法下以上优势也会成为劣势. 1.协程

  • Kotlin中协程的创建过程详析

    目录 为什么需要协程? 创建并启动协程 协程的执行过程 suspend block 是如何变为协程体被执行的? 总结 总结 为什么需要协程? 协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价.更可控的操作替代线程阻塞的方法 – 挂起函数. Kotlin 的协程是依靠编译器实现的, 并不需要操作系统和硬件的支持.编译器为了让开发者编写代码更简单方便, 提供了一些关键字(例如suspend), 并在内部自动生成了一些支持型的代码. 创建并启动协程 fun create

  • Python协程 yield与协程greenlet简单用法示例

    本文实例讲述了Python协程 yield与协程greenlet简单用法.分享给大家供大家参考,具体如下: 协程 协程,又称微线程,纤程.英文名Coroutine. 协程是啥 协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源). 为啥说它是一个执行单元,因为它自带CPU上下文.这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程. 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的. 通俗的理解:在一个线程中的某个函数,可以在任

  • 利用CSS、JavaScript及Ajax实现图片预加载的三大方法

    预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度.这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速.无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验.本文将分享三个不同的预加载技术,来增强网站的性能与可用性. 方法一:用CSS和JavaScript实现预加载 实现预加载图片有很多方法,包括使用CSS.JavaScript及两者的各种组合.这些技术可根据不同设计场景设计出相应的解决方案,十分高效.

  • 利用CSS、JavaScript及Ajax实现图片预加载的方法

    预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度.这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速.无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验.本文将分享三个不同的预加载技术,来增强网站的性能与可用性. 实现图片预加载可以使用css.JavaScript.Ajax三种方法.下面分别介绍这些方法的实现. 使用CSS 单纯的使用css可以将图片加载到页面元素的背景上,这种方法简单.高效: #d

随机推荐