Kotlin协程和RxJava在不同业务场景下的使用体验

虽然协程和RxJava有着不同的设计理念,但他们都不约而同的解决了Java编程中回调地狱的硬伤。这篇文章就带大家尝试在特定业务场景下分别用Kotlin协程和用RxJava,来体验一把两者在代码风格上的差异。

场景一:请求数据到UI线程渲染

在Android开发中,由于主线程不能耗时请求,子线程不能更新UI,所以这是一个很常见的业务需求。

RxJava

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fun main() {
getUser().observeOn(AndroidSchedulers.mainThread())//指定回调发生在UI线程
.subscribe(Consumer { s ->
println("I get RESULT $s,CurrentThread is " + Thread.currentThread().name + "...")
updateUI(s)
}, { err ->
println(err.message)
})
Thread.sleep(3000)//延时3s,避免主线程销毁
}
fun getUser(): Observable<String> {
val random = Random()
return Observable
.create { emitter: ObservableEmitter<String> ->
//模拟网络请求
println("I'm doing network,CurrentThread is " + Thread.currentThread().name + "...")
Thread.sleep(1000)
if (random.nextBoolean()) {
emitter.onNext("Jack")
} else {
emitter.onError(TimeoutException("Net Error!"))
}
}
.subscribeOn(Schedulers.io())//指定网络请求在IO线程
}

Kotlin协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun main() = runBlocking {
try{
val s = getUser()
println("I get RESULT $s,CurrentThread is " + Thread.currentThread().name + "...")
updateUI(s)
}catch(e:Exception){
println(e.message)
}
}

suspend fun getUser():String{
val random = Random()
var res:String? = null
withContext(Dispatchers.IO){
//模拟网络请求
println("I'm doing network,CurrentThread is " + Thread.currentThread().name + "...")
delay(1000)
if (!random.nextBoolean()) {
throw TimeoutException("Net Error")
}
res = "Jack"
}
return res!!
}

跟着下面👇的代码中,将延用这里的getUser方法。

场景二:串行两次请求,后更新UI

getUser -> getArticle(user)

RxJava

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
getUser()
.flatMap { user -> getArticle(user)}
.observeOn(AndroidSchedulers.mainThread())//指定回调发生在UI线程
.subscribe({ article ->
println("I get RESULT $article,CurrentThread is " + Thread.currentThread().name + "...")
}, { err ->
println(err.message)
})

Thread.sleep(3000)//延时3s,避免主线程销毁
}

fun getUser(){...}
fun getArticle(user:String):Observable<String>{
return Observable.create { article ->
println("I'm doing network[getArticle],CurrentThread is " + Thread.currentThread().name + "...")
Thread.sleep(1000)
article.onNext("$user's Article~")
}
}

Kotlin协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() = runBlocking {
try {
val user = getUser()
val article = getArticle(user)
println("I get RESULT $article,CurrentThread is " + Thread.currentThread().name + "...")
updateUI(s)
}catch (e:Exception){
println(e.message)
}
}

suspend fun getUser(){...}
suspend fun getArticle(user:String):String{
val article:String
withContext(Dispatchers.IO){
println("I'm doing network[getArticle],CurrentThread is " + Thread.currentThread().name + "...")
delay(1000)
article = "${user}'s Article"
}
return article
}

可以看到两者都没有因此增加多少复杂度。

下面代码的网络请求操作都类似,所以会省略getXX方法。

场景三:并行两次请求,合并后更新UI

RxJava

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
RxJavaPlugins.setErrorHandler { err -> println(err.message) }//异常捕获
getUser()
.zipWith(getAnotherUser(), { user, auser -> "$user and $auser" })
.observeOn(AndroidSchedulers.mainThread())//指定回调发生在UI线程
.subscribe({ s ->
println("I get RESULT $s,CurrentThread is " + Thread.currentThread().name + "...")
}, { err ->
println(err.message)
})

Thread.sleep(3000)
}

Kotlin协程

1
2
3
4
5
6
7
8
9
10
fun main() = runBlocking(handler) {
try {
val user = async { getUser() }
val auser = async { getAnotherUser() }
val merge = "${user.await()} and ${auser.await()}"
println("I get RESULT ${merge},CurrentThread is " + Thread.currentThread().name + "...")
}catch (e:Exception){
println(e.message)
}
}

如果getUser耗时1s,getAnatherUser耗时2s,最终都是耗时两秒。两边写法都还是很简洁的。


其实这些也不是RxJava真正发力的场景,比如说这时候来个场景加载九张图片,奇数张模糊化,偶数张圆角话,这时候Kotlin协程就无能为力了,因为它只是个异步工具,借助Kotlin的语言优势,处理异步问题如鱼得水。RxJava更多的体现是一种编程思维,你只需要去管做什么,不需要管怎么做。两者的设计理念还是大有不同的。