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

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

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

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

RxJava

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协程

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

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协程

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

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协程

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更多的体现是一种编程思维,你只需要去管做什么,不需要管怎么做。两者的设计理念还是大有不同的。

推荐阅读更多精彩内容