怎么在Kotlin中利用Coroutines执行异步加载?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
引入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) }
关于怎么在Kotlin中利用Coroutines执行异步加载问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。