这一系列的文章将会用来进行Coroutines和Rxjava在解决异步编程方面的对比。
案例一:为一个快速启动的App创建复杂的对象
如果希望自己的应用程序能够快速启动,那么处理创建对象的过程非常重要,如果对于复杂的对象创建发生在UI线程的话,将会引起丢帧的情况。
所以对于这种情况我们我们需要在后台线程中做这些操作。
RxJava
我们如何使用Rxjava来初始化我们想要的对象?
fun initializeObjectsAsync(): Completable{
return Completable.create{ emitter ->
try{
heavyInitialzation()
if(!emitter?.isDisposed){
emitter?.onComplete()
}
}catch(e:Exception){
if (!emitter?.isDisposed) {
emitter?.onError(e)
}
}
}
}
正如你所见,我们创建了一个方法返回Completable对象,在方法内部,我们通过Completable.create创建了一个Completable,他将使用一个发射器(这个对象是可以被订阅的)。
执行完复杂的初始化后,我们将通知成功,如果有错误发生,我们将通知所发生的错误,这是因为发射器的类型是CompletableEmitter,而onComplete和onError是可用于将结果传递给订阅服务器的方法。
另一种方法是使用Completable.fromCallable()
fun initializeObjectsAsync(): Completable {
return Completable.fromCallable({
heavyInitialization()
})
}
我们如何消费该方法呢?
Observables和Completables在Rxjava中都是冷冰冰的,这意味着我们只有订阅时,才会执行Completable.create中的代码,需要记住的是,每次订阅都会执行。
我们必须订阅我们在上面创建的initializeObjectsAsync函数中创建的Completable。
fun initializeObjects() {
initializeObjectsAsync()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// The initialization succeeded!
// We can perform UI changes here
}, {
// An Error occurred!
})
}
我们如何告诉RxJava我们想要在后台线程中执行初始化操作?我们使用subscribeOn操作符来告诉RxJava,我们希望Completable.create中的代码能够在后台线程上执行。
当执行完毕时,我们需要对UI进行更新操作,我们可以使用observableOn操作符来告诉RxJava我们想在Android主线程中监听结果。
只有当你订阅Completable时,才会执行Completable.create中的代码
定义线程之后,我们需要启动订阅才能在完成时接收到通知,使用.subscribe方法来做到这一点,我们需要传递两个代码块,一个是接收成功的方法,一个是接收失败的方法。
如果想要上述代码被执行,我们需要创建一个Android Activity,例如,我们可以在onCreate方法中调用这个方法。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeObjects()
}
Kotlin Coroutines
使用Coroutines的话会更简单,从概念上讲,协程和线程类似,我们可以编写在特定线程上运行的代码。
CoroutineBuilder是一个可以用来创建Coroutine的方法,运行一段代码,并以某种形式访问其结果,CoroutineBuilder的例子有:launch,async,runBlocking...
假设我们要像在onCreate方法中那样调用heavyInitialization方法,我们可以使用CoroutineBuilder启动创建一个Coroutine,并在要运行的代码块执行大量初始化的代码。
fun initializeObjects() {
launch(CommonPool) {
heavyInitialization()
}
}
CommonPool类似于RxJava中的Schedulers.computation(),将会在后台线程中运行代码。
让我们模仿RxJava中构建的例子,我们想知道它什么时候完成初始化复杂对象和错误的处理。
fun initializeObjects() {
launch(CommonPool) {
try {
heavyInitialization()
// The initialization succeeded!
withContext(UI) {
// We can perform UI changes here
}
} catch (e: Exception) {
// An Error occurred!
}
}
}
由于Coroutine中的代码被顺序执行,因此在初始化成功代码将接着初始化的代码执行。
和以前一样,我们可以将调用封装在try catch块中进行错误处理。
我们如何切换UI线程来通知UI更新,这里有一个方法withContext,使用另一个CoroutineContext来运行其中的代码块。
我们在示例中看到的CoroutineContext不是标准Kotlin Coroutines库中的一部分,由于它是Android专用的,因此可以在其他库中使用:org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version
示例二:在后台线程执行斐波那契数列
区别于在后台执行一些操作,我们也需要返回一个值,现在我们在用户点击按钮是计算斐波那契数列。
想象一下我们使用下面的代码来计算斐波那契数列:
private fun fib(n: Long): Long {
return if (n <= 1) n
else fib(n - 1) + fib(n - 2)
}
我们如何在后台线程中计算他并且返回结果呢?
RxJava
这次我们使用Single来实现。
fun fibonacciAsync(number: Long): Single<Long> =
Single.create({ emitter ->
val result = fib(number)
if (!emitter?.isDisposed) {
emitter.onSuccess(result)
}
})
你也可以使用fromCallback方法:
fun fibonacciAsync(number: Long): Single<Long> =
Single.fromCallable({
return fib(number)
})
我们把我们想要的数字作为该函数的参数传递,这也将在Single.create代码块中使用,例如,我们可以从EditText中获取该号码。
@OnClick(R.id.my_button)
fun onButtonClicked() {
fibonacciAsync(numberInputEditText.text.toString().toLong())
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ fibonacciNumber ->
//Update UI with the result
myTextView.text = fibonacciNumber
},{
// Error happened
})
}
用户每次点击按钮,我们都将计算一个新的斐波那契数值,如果用户修改了Edittext中的值,结果也将会变得不同。
Kotlin Coroutines
这个示例将会和上面的一样简单,当用户点击按钮,我们就开启一个Coroutine来计算斐波那契数列。
@OnClick(R.id.my_button)
fun onButtonClicked(){
launch(CommonPool) {
val result = fibonacciAsync (
numberInputEditText.text.toString().toLong()
)
withContext(UI){
fibonacciResultTextView.text = result
}
}
}
下篇文章将介绍取消执行。
如何取消Observable和Coroutine。不要错过。