忘了RxJava:Kotlin Coroutines才是你需要的

原文地址:https://proandroiddev.com/forget-rxjava-kotlin-coroutines-are-all-you-need-part-1-2-4f62ecc4f99b
使用RxJava創(chuàng)建GithubApi的網(wǎng)絡(luò)層的接口如下:

interface ApiClientRx {

    fun login(auth: Authorization) : Single<GithubUser>
    fun getRepositories(reposUrl: String, auth: Authorization) : Single<List<GithubRepository>>
    fun searchRepositories(query: String) : Single<List<GithubRepository>>
  
}

雖然Rxjava是一個功能強大的庫,但它不能用作管理異步任務(wù)的工具讶踪,他是一個事件處理庫。
接口使用方法如下所示:

private fun attemptLoginRx(){
  val login = email.text.toString()
  val pass = password.test.toString()
  
  val auth = BasicAuthorization(login, pass)
  val apiClient = ApiClientRx.ApiClientRxImpl()
  showProgressVisible(true)
  compositeDisposable.add(apiClient.login(auth)
    .flatMap {
        user -> apiClient.getRepositories(user.reposUrl, auth)
    }
    .map { list -> list.map { it.fullName } }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .doAfterTerminate { showProgressVisible(false) }
    .subscribe(
            { list -> showRepositories(this@LoginActivity, list) },
            { error -> Log.e("TAG", "Failed to show repos", error) }
    ))
}

這段代碼有幾個隱含的缺陷:

性能開銷

這里的每一行都生成一個內(nèi)部對象來完成這項工作泛鸟,對于這種特殊情況,他生成了19個對象,如果更復(fù)雜的例子裕膀,那么將會生成更多的對象:


image.png

不可讀的堆棧跟蹤

如果你的代碼中發(fā)生了一個錯誤,那么如下的堆棧跟蹤相當(dāng)不好理解:

at com.epam.talks.github.model.ApiClientRx$ApiClientRxImpl$login$1.call(ApiClientRx.kt:16)
at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:44)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleMap.subscribeActual(SingleMap.java:34)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)

整個堆棧跟蹤中只有一行來自你的代碼勇哗。

學(xué)習(xí)的復(fù)雜性

還記得你花了多少時間用來理解map()和flatmap()的不同之處嗎昼扛。更不用說還有其他更多的操作符,這些時間是每個新的開發(fā)人員進(jìn)入響應(yīng)式變成世界都需要花費的欲诺。

Kotlin Coroutines能否讓我們的生活更好抄谐?

public actual interface Deferrd<out T>: Job {
  public suspend fun await() : T
}

interface Job:CoroutineContext.Element {
  public val isActive: Boolean
  public val isCompleted: Boolean
  public val isCancelled: Boolean
  public fun getCancellationException: CancellationException
  public fun start(): Boolean
}

接下來重構(gòu)代碼:

interface ApiClient {
  fun login(auth: Authorization) : Deferred<GithubUser>
  fun getRepositories(reposUrl: String, auth: Authorization): Deffered<List<GithubRepository>>
}
override fun login(auth: Authorization) : Deferred<GithubUser?> = async {
    val response = get("https://api.github.com/user", auth = auth)
    if (response.statusCode != 200) {
        throw RuntimeException("Incorrect login or password")
    }

    val jsonObject = response.jsonObject
    with (jsonObject) {
        return@async GithubUser(getString("login"), getInt("id"),
                getString("repos_url"), getString("name"))
    }
}

acync和launch構(gòu)建器使用CommonPool調(diào)度程序渺鹦。

job = launch(UI){
  showProgressVisible(true);
  val auth = BasicAuthorization(login,pass)
  try{
    val userInfo = login(auth).await()
    val repoUrl = userInfo.reposUrl
    val repos = getRepositories(repoUrl, auth).await()
    val pullRequests = getPullRequests(repos[0], auth).await()
    showRepositories(this, repos.map { it -> it.fullName })
  } catch (e: RuntimeException) {
    Toast.makeText(this, e.message, LENGTH_LONG).show()
  } finally {
    showProgressVisible(false)
  }
}

代碼變得更加的清晰和直觀,看起來就像沒有任何異步發(fā)生蛹含,在RxJava中我們還需要將subscription添加到compositeDisposable中毅厚,以便將它放在onStop中。

性能

對象數(shù)量下降到了11


image.png

不可讀的堆棧

堆棧信息依舊不可讀但是這個問題正在被解決.

可讀性

代碼更易于讀寫浦箱,因為是用同步的方法編寫的吸耿。

如何重構(gòu)測試代碼?

使用RxJava的測試代碼是如下的樣子:

@Test
    fun login() {
        val apiClientImpl = ApiClientRx.ApiClientRxImpl()
        val genericResponse = mockLoginResponse()

        staticMockk("khttp.KHttp").use {
            every { get("https://api.github.com/user", auth = any()) } returns genericResponse

            val githubUser = apiClientImpl.login(BasicAuthorization("login", "pass"))

            githubUser.subscribe { githubUser ->
                Assert.assertNotNull(githubUser)
                Assert.assertEquals("name", githubUser.name)
                Assert.assertEquals("url", githubUser.reposUrl)
            }

        }
    }

使用kotin Coroutine 之后的測試代碼如下:

@Test
    fun login() {
        val apiClientImpl = ApiClient.ApiClientImpl()
        val genericResponse = mockLoginResponse()

        staticMockk("khttp.KHttp").use {
            every { get("https://api.github.com/user", auth = any()) } returns genericResponse

            runBlocking {
                val githubUser = apiClientImpl.login(BasicAuthorization("login", "pass")).await()

                assertNotNull(githubUser)
                assertEquals("name", githubUser.name)
                assertEquals("url", githubUser.repos_url)
            }
        }
    }

還有更進(jìn)一步的改進(jìn)嗎憎茂?

我們使用suspend修飾符替換Deferred對象

interface SuspendingApiClient {

    suspend fun login(auth: Authorization) : GithubUser
    suspend fun getRepositories(reposUrl: String, auth: Authorization) : List<GithubRepository>
    suspend fun searchRepositories(searchQuery: String) : List<GithubRepository>

}

接下來看一下客戶端代碼和測試代碼的改變:

private fun attemptLoginSuspending() {
        val login = email.text.toString()
        val pass = password.text.toString()
        val apiClient = SuspendingApiClient.SuspendingApiClientImpl()
        job = launch(UI) {
            showProgressVisible(true)
            val auth = BasicAuthorization(login, pass)
            try {
                val userInfo = async(parent = job) { apiClient.login(auth) }.await()
                val repoUrl = userInfo.repos_url
                val list = async(parent = job) { apiClient.getRepositories(reposUrl, auth) }.await()
                showRepositories(this, list.map { it -> it.fullName })
            } catch (e: RuntimeException) {
                Toast.makeText(this, e.message, LENGTH_LONG).show()
            } finally {
                showProgressVisible(false)
            }
        }
    }

跟之前的對比只加了async(parent=job){}

為了在onStop中取消job時取消此協(xié)同程序,需要傳遞父對象锤岸。

@Test
fun testLogin() = runBlocking {
    val apiClient = mockk<SuspendingApiClient.SuspendingApiClientImpl>()
    val githubUser = GithubUser("login", 1, "url", "name")
    val repositories = GithubRepository(1, "repos_name", "full_repos_name")

    coEvery { apiClient.login(any()) } returns githubUser
    coEvery { apiClient.getRepositories(any(), any()) } returns repositories.asList()

    val loginPresenterImpl = SuspendingLoginPresenterImpl(apiClient, CommonPool)
      runBlocking {
        val repos = loginPresenterImpl.doLogin("login", "password")
            assertNotNull(repos)
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竖幔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子是偷,更是在濱河造成了極大的恐慌拳氢,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛋铆,死亡現(xiàn)場離奇詭異馋评,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)刺啦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門留特,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玛瘸,你說我怎么就攤上這事蜕青。” “怎么了糊渊?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵右核,是天一觀的道長。 經(jīng)常有香客問我渺绒,道長贺喝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任宗兼,我火速辦了婚禮躏鱼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘殷绍。我一直安慰自己挠他,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布篡帕。 她就那樣靜靜地躺著殖侵,像睡著了一般贸呢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拢军,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天楞陷,我揣著相機(jī)與錄音,去河邊找鬼茉唉。 笑死固蛾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的度陆。 我是一名探鬼主播艾凯,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼懂傀!你這毒婦竟也來了趾诗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蹬蚁,失蹤者是張志新(化名)和其女友劉穎恃泪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犀斋,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡贝乎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叽粹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片览效。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虫几,靈堂內(nèi)的尸體忽然破棺而出朽肥,到底是詐尸還是另有隱情,我是刑警寧澤持钉,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布衡招,位于F島的核電站,受9級特大地震影響每强,放射性物質(zhì)發(fā)生泄漏始腾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一空执、第九天 我趴在偏房一處隱蔽的房頂上張望浪箭。 院中可真熱鬧,春花似錦辨绊、人聲如沸奶栖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宣鄙。三九已至袍镀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冻晤,已是汗流浹背苇羡。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留鼻弧,地道東北人设江。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像攘轩,于是被迫代替她去往敵國和親叉存。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,789評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理度帮,服務(wù)發(fā)現(xiàn)歼捏,斷路器,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 前言 人生苦多够傍,快來 Kotlin 甫菠,快速學(xué)習(xí)Kotlin挠铲! 什么是Kotlin冕屯? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,168評論 9 118
  • 今天有小伙伴向我咨詢:有朋友要我分享課程給她,我擔(dān)心拂苹,如果她們都去聽老師的課安聘,不愿意跟我咨詢了,我就擔(dān)心自己沒有價...
    蟬衣cy閱讀 196評論 0 0
  • 梁爽 理性天蝎女 治拎不清、玻璃心脯宿、擰巴癥 點燃你自律的心 新書《你來人間一趟念颈,你要發(fā)光發(fā)亮》正在銷魂出售中 前幾...
    哪梁爽哪呆著閱讀 475評論 0 6