Android開發中的Kotlin Coroutine VS RxJava

dance發表於2019-02-21

前言

首先,我是個Kotlin的重度使用者。我用Kotlin寫過後臺應用,寫過前端,寫過近10個Android專案。

我個人覺得,Kotlin充滿了現代化的軟體開發所需的語言特色,在我用過的所有語言中(ES6,Python,Go,Java)是最舒服最自然的;JetBrain做了多年IDE,最懂開發者的尿性。如果你會Java,幾乎沒有學習成本。除了其他優秀的語法外,我也格外喜歡Coroutine,下面就來吹一波。

先吹一波

Coroutine,也叫協程,或者微執行緒(或者隨便你怎麼叫它);它是可暫停可恢復的比執行緒更小的任務單元。目前我所用到的支援協程式的語言有NodeJS,Go,Python;Java並不支援。從原理上看,它們的核心實現原理大都是OS Thread Pool + 狀態機,就是靠系統執行緒池來排程,靠狀態機來控制狀態。只不過有些是語言天然就支援的,比如Go;而Kotlin是用編譯技術來實現的。

不管如何實現,它們一般有這樣2個好處:

  1. 更低資源消耗和更好的排程效能
  2. 非同步程式碼變同步

至於第一點在Web應用上很實用,因為Web大多是IO密集,它在低配置的機器上可以帶來更高的併發,資源消耗還少。但是在Andorid上然並卵,就算是多執行緒下載的場景,一般併發任務也就5個左右,根本體現不出它的作用。

第二點在Android上就很實用了,我們經常會遇到先執行一段耗時操作,在執行一段後續邏輯的場景。不然Andorid也不會搞出AsyncTask,IntentService,HandlerThread了。它可以完全消除我們的callback。

場景

先看一個真實的釋出動態場景。動態中包含文字,使用者拍攝或選擇的9張圖片。整個動態的釋出流程是這樣:

  1. 先非同步對9張圖片進行壓縮
  2. 然後非同步將9張圖片上傳到Server,拿到9個圖片Id
  3. 將文字和圖片Id一塊上傳

第1步,第2步和第3步本身都是需要非同步,但1,2,3步之間又是同步順序執行的關係。

Java中可以用FutureTask實現。

用RxJava來做大概是大量的Callback流配合map操作符來完成,程式碼雖然比FetureTask好很多,但是並不美觀。

用Kotlin Coroutine來做就是:

GlobalScope.launch {
    // 其他引數
    val params = hashMapOf(
            "content" to content,
            "longitude" to longitude,
            "latitude" to latitude,
            "title" to title
    )
    
    //1. 構建壓縮圖片的task
    val compressedTasks = paths.map { compressImage(it) } // paths是使用者的圖片地址集合
    //2. 構建上傳圖片的task,上傳任務就是http請求
    val uploadTasks = compressedTasks.map { uploadImage(it.await()) } //壓縮任務併發執行
    //3. 執行上傳圖片task拿到結果
    val imageIds = uploadTasks.map { it.await()!!.data.id } // 上傳圖片任務併發執行
    params["images"] = imageIds.toJson()

    val result = "$BASEURL/weibo/create".http(this)
            .headers(...)
            .params(params)
            .post<HttpResult<Dynamic>>().await()
            
    // 更新LiveData        
    weiboCreateData.postValue(result)
}
複製程式碼

幾個構建task的程式碼在這裡:

/**
 * 構建壓縮任務
 */
fun compressImage(path: String): Deferred<File>{
    val deferred = CompletableDeferred<File>()
    // 這裡是使用Luban庫來壓縮圖片,具體邏輯可以忽略
    deferred.complete(Luban.with(App.context)
            .load(path)
            .ignoreBy(100)
            .get()[0])
    return deferred
}

/**
 * 構建上傳圖片的任務
 */
fun uploadImage(file: File): Deferred<HttpResult<List<UploadData>>?> {
    return "$BASEURL/weibo/upload".http(this)
            .headers(createCommonHeaders(null))
            .params("file" to file)
            .post<HttpResult<List<UploadData>>>()
}
複製程式碼

可以看到這種複雜邏輯下,全部程式碼也就30行左右,並且沒有一個Callback,是不是比RxJava好。

上面的網路請求是來自於我的一個庫:github.com/li-xiaojun/…

非同步邏輯之後通知UI,我建議用LiveData,省去很多介面銷燬的判斷,而且監聽可以自動解除註冊,RxJava還要手動dispose。

如果你就想在UI中執行一段非同步邏輯然後回到UI執行緒更新UI,那可以用anko庫的封裝。

最後

其實,我並不用RxJava,從看到大量的Callback時就放棄了。

我對RxJava並沒有深入瞭解,對於上面的場景,如果RxJava能做的更好,煩請指出。

相關文章