如何在Kotlin中使用协程实现一个异步加载功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
使用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) }
看完上述内容,你们掌握如何在Kotlin中使用协程实现一个异步加载功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。