Kotlin协程巩固

该篇基于Kotlin 协程学习,国庆放假来巩固一下知识。

Retrofit更新到 2.6.0的版本后,引入了对协程的支持,这样更利于我们打造优雅的网络框架。但是现在大部分Android框架都用 RxJava + Retrofit的,而协程本身是和RxJava功能重合的(便于切线程),所以我需要理清这两者的区别,在使用时合理取舍。

1. Retrofit对协程的支持

2.6.0版本,Retrofit可以使用协程来进行网络请求。我们先看看前后有什么区别。

1.1 原始的Retrofit使用

Api定义如下:

interface Api {
    @GET("user/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<ResponseBody>
}

接着在代码中使用:

    val apiService = Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build().create(Api::class.java)
    apiService.listRepos("Rikkatheworld").enqueue(object : Callback<ResponseBody> {
        override fun onFailure(call: Call<ResponseBody>?, t: Throwable?) {
            textview.text = t?.message
        }
        override fun onResponse(call: Call<ResponseBody>?, response: Response<ResponseBody>?) {
            textview.text = call
        }
     })
    }

老版本的Retrofit需要在构建请求时,声明函数类型为 Call,这样可以回调到主线程,接着只要在回调方法中实现 onResponse()onFailure()的成功失败回调就行了。

1.2 使用协程的Retrofit

协程可以帮我们解决掉写回调的麻烦,所以我们可以把请求写在协程中,这也需要我们调用的Api函数需要被 @suspend修饰

interface Api {
    @GET("user/{user}/repos")
    suspeend fun listRepos(@Path("user") user: String): ResponseBody
}

接着在代码中开一个协程来消除回调:

        GlobalScope.launch(Dispatchers.Main) {  // 声明为主线程的协程
            val repos = apiService.listRepos("Rikkatheworld")  // 在子线程进行网络请求
            textview.text = repos.toString()   // 回到主线程更新ui
        }

这样来看,就真的消除了回调。但是有一个问题:上面代码的 repos是请求成功后的结果,那么失败怎么办?
目前Kotlin和Retrofit都没有给出方案,所以只能采用 try-catch的方法

       GlobalScope.launch(Dispatchers.Main) {
            try {
                val repos = apiService.listRepos("Rikkatheworld")
                textview.text = repos.toString()
            } catch (e: Exception) {
                textview.text = e.message
            }
        }

这就比较难受了…

2. 协程与RxJava

2.1 代码的比较

RxJava的代码大家都写惯了,大概是这样的:

        apiService.listRepos("Rikkatheworld")
            //将请求放在后台
            .subscribeOn(Schedulers.io())
            //将结果的返回放在前台
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : SingleObserver<List<Repo>>() {
                fun onSubscribe(d: Disposable) {
                    Log.d(TAG, "onSubscribe")
                }

                fun onSuccess(repo: List<Repo>) {
                    Log.d(TAG, repo[0].toString())
                }

                fun onError(e: Throwable) {
                    Log.e(TAG, e.message)
                }
            })

而使用协程,则可以使用 async来切线程:

        GlobalScope.launch(Dispatchers.Main) {
            try {
                val res = async { apiService.listRepos("Rikkathewrold") } //异步获取挂起结果
                textview.text = res.await().toString()   //主线程更新ui
            } catch (e: Exception) {
                Log.e(TAG, "${e.message}")
            }
        }

2.2 总结

他们的共同点:

  • 都可以切换线程
  • 都不需要嵌套

他们的不同点

  • RxJava需要写回调,而协程不需要
  • RxJava链式调用,结构清晰;而协程会自己切换线程,所以代码更加简洁

RxJava和协程的功能和使用场景基本一致,而协程在结构上比RxJava更加简单,如果使用数据流,协程还可以使用 Flow,可以学下这篇文章:Kotlin协程(5)Flow

而性能上,在 RxJava vs. Kotlin Coroutines, a quick look的文章中进行了对比,因为协程在处理挂起函数的逻辑上较为复杂,所以在性能上 协程比RxJava更弱一点。 但是这是2017年的文章了,现在不知道Kotlin官方有没有进行改进。

我个人认为,以后Android开发都是Jetpack全家桶了,而Kotlin团队JetBrains又是Google“亲儿子”,所以以后Jetpack肯定会更加支持协程的, 所以我更相信KT官方会解决协程的问题,而更偏向在代码中使用协程去取代RxJava。

3. 协程的“泄漏”

Kotlin 协程学习的第四节中,讲到了如何去取消一个协程,为什么要取消没有写的很明白,我们让协程跑完不就行了?

在Android日常的开发中,我们是不知道用户会做什么事情的,假如当前程序正在执行一个协程来执行一个耗时的任务,而用户在任务还没有完成的时候把页面关了(就是会走 onDestroy()那种),那我们的协程也需要关闭才对,不然的话,协程可是会跑一个线程的,它继续跑下去的,GC就会受不了,导致产生“内存泄漏”。因为 一个线程就是一个 GC Root

所以在面试中, 面试官所说的 “协程泄漏”其实就等于 “线程的内存泄漏”,它本质上和 AsyncTask的泄漏是样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值