欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-16 21:11:46
前言 kotlin coroutines是kotlin推出的新的异步api。并不是解决所有问题的最优方案,但是希望在许多情况下它会使事情变得更容易一些。这里只简单的展示一...

前言

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 (本地下载)).

总结

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