内容简介:前面两篇文章介绍了协程的一些基本概念和基本知识,这篇则介绍在此篇文章的Demo地址在前面的文章中我写到
前面两篇文章介绍了协程的一些基本概念和基本知识,这篇则介绍在 Android
中如何使用协程配合 Retrofit
发起网络请求,同时介绍在使用协程时如何优雅的取消已经发起的网络请求。
此篇文章的Demo地址 :https://github.com/huyongli/AndroidKotlinCoroutine
创建CoroutineScope
在前面的文章中我写到 CoroutineScope.launch
方法是一个很常用的协程构建器。因此使用协程必须先得创建一个 CoroutineScope
对象,代码如下:
CoroutineScope(Dispatchers.Main + Job()) 复制代码
上面的代码创建了一个 CoroutineScope
对象,为其协程指定了在主线程中执行,同时分配了一个 Job
在demo中我使用的是MVP模式写的,所以我将 CoroutineScope
的创建放到了 BasePresenter
中,代码如下:
interface MvpView interface MvpPresenter<V: MvpView> { @UiThread fun attachView(view: V) @UiThread fun detachView() } open class BasePresenter<V: MvpView> : MvpPresenter<V> { lateinit var view: V val presenterScope: CoroutineScope by lazy { CoroutineScope(Dispatchers.Main + Job()) } override fun attachView(view: V) { this.view = view } override fun detachView() { presenterScope.cancel() } } 复制代码
使用CoroutineScope.cancel()取消协程
大家应该可以看到上面 BasePresenter.detachView
中调用了 presenterScope.cancel()
,那这个方法有什么作用呢,作用就是取消掉 presenterScope
创建的所有协程和其子协程。
前面的文章我也介绍过使用 launch
创建协程时会返回一个 Job
对象,通过 Job
对象的 cancel
方法也可以取消该任务对应的协程,那我这里为什么不使用这种方式呢?
很明显,如果使用 Job.cancel()
方式取消协程,那我创建每个协程的时候都必须保存返回的 Job
对象,然后再去取消,显然要更复杂点,而使用 CoroutineScope.cancel()
则可以一次性取消该协程上下文创建的所有协程和子协程,该代码也可以很方便的提取到基类中,这样后面在写业务代码时也就不用关心协程与View的生命周期的问题。
其实大家看源码的话也可以发现 CoroutineScope.cancel()
最终使用的也是 Job.cancel()
取消协程
扩展Retrofit.Call适配协程
interface ApiService { @GET("data/iOS/2/1") fun getIOSGank(): Call<GankResult> @GET("data/Android/2/1") fun getAndroidGank(): Call<GankResult> } class ApiSource { companion object { @JvmField val instance = Retrofit.Builder() .baseUrl("http://gank.io/api/") .addConverterFactory(GsonConverterFactory.create()) .build().create(ApiService::class.java) } } 复制代码
大家可以看到上面的api接口定义应该很熟悉,我们可以通过下面的代码发起异步网络请求
ApiSource.instance.getAndroidGank().enqueue(object : Callback<T> { override fun onFailure(call: Call<T>, t: Throwable) { } override fun onResponse(call: Call<T>, response: Response<T>) { } }) 复制代码
前面的文章介绍过协程可以让异步代码像写同步代码那样方便,那上面这段异步代码能不能使用协程改造成类似写同步代码块那样呢?很显然是可以的,具体改造代码如下:
//扩展Retrofit.Call类,为其扩展一个await方法,并标识为挂起函数 suspend fun <T> Call<T>.await(): T { return suspendCoroutine { enqueue(object : Callback<T> { override fun onFailure(call: Call<T>, t: Throwable) { //请求失败,抛出异常,手动结束当前协程 it.resumeWithException(t) } override fun onResponse(call: Call<T>, response: Response<T>) { if(response.isSuccessful) { //请求成功,将请求结果拿到并手动恢复所在协程 it.resume(response.body()!!) } else{ //请求状态异常,抛出异常,手动结束当前协程 it.resumeWithException(Throwable(response.toString())) } } }) } } 复制代码
上面的代码扩展了一个挂起函数 await
,执行该方法时,会执行 Retrofit.Call
的异步请求同时在协程中挂起该函数,直到异步请求成功或者出错再重新恢复所在协程。
suspendCoroutine
全局函数,此函数可以获取当前方法所在协程上下文,并将当前协程挂起,直到某个时机再重新恢复协程执行,但是这个时机其实是由开发者自己控制的,就像上面代码中的 it.resume
和 it.resumeWithException
。
发起请求,写法一
//使用CoroutineScope.launch创建一个协程,此协程在主线程中执行 presenterScope.launch { val time = System.currentTimeMillis() view.showLoadingView() try { val ganks = queryGanks() view.showLoadingSuccessView(ganks) } catch (e: Throwable) { view.showLoadingErrorView() } finally { Log.d(TAG, "耗时:${System.currentTimeMillis() - time}") } } suspend fun queryGanks(): List<Gank> { //此方法执行线程和调用者保持一致,因此也是在主线程中执行 return try { //先查询Android列表,同时当前协程执行流程挂起在此处 val androidResult = ApiSource.instance.getAndroidGank().await() //Android列表查询完成之后恢复当前协程,接着查询IOS列表,同时将当前协程执行流程挂起在此处 val iosResult = ApiSource.instance.getIOSGank().await() //Android列表和IOS列表都查询结束后,恢复协程,将两者结果合并,查询结束 val result = mutableListOf<Gank>().apply { addAll(iosResult.results) addAll(androidResult.results) } result } catch (e: Throwable) { //处理协程中的异常,否则程序会崩掉 e.printStackTrace() throw e } } 复制代码
从上面的代码大家可以发现,协程中对异常的处理使用的是 try-catch
的方式,初学,我也暂时只想到了这种方式。所以在使用协程时,最好在业务的适当地方使用 try-catch
捕获异常,否则一旦协程执行出现异常,程序就崩掉了。
另外上面的代码的写法还有一个问题,因为挂起函数执行时会挂起当前协程,所以上述两个请求是依次顺序执行,因此上面的 queryGanks()
方法其实是耗费了两次网络请求的时间,因为请求Android列表和请求ios列表两个请求不是并行的,所以这种写法肯定不是最优解。
发起请求,写法二
下面我们再换另外一种写法。
suspend fun queryGanks(): List<Gank> { /** * 此方法执行线程和调用者保持一致,因此也在主线程中执行 * 因为网络请求本身是异步请求,同时async必须在协程上下文中执行,所以此方法实现中采用withContext切换执行线程到主线程,获取协程上下文对象 */ return withContext(Dispatchers.Main) { try { //在当前协程中创建一个新的协程发起Android列表请求,但是不会挂起当前协程 val androidDeferred = async { val androidResult = ApiSource.instance.getAndroidGank().await() androidResult } //发起Android列表请求后,立刻又在当前协程中创建了另外一个子协程发起ios列表请求,也不会挂起当前协程 val iosDeferred = async { val iosResult = ApiSource.instance.getIOSGank().await() iosResult } val androidResult = androidDeferred.await().results val iosResult = iosDeferred.await().results //两个列表请求并行执行,等待两个请求结束之后,将请求结果进行合并 //此时当前方法的执行时间实际上两个请求中耗时时间最长的那个,而不是两个请求所耗时间的总和,因此此写法优于上面一种写法 val result = mutableListOf<Gank>().apply { addAll(iosResult) addAll(androidResult) } result } catch (e: Throwable) { e.printStackTrace() throw e } } } 复制代码
这种写法与前一种写法的区别是采用 async
构建器创建了两个子协程分别去请求Android列表和IOS列表,同时因为 async
构建器执行的时候不会挂起当前协程,所以两个请求是并行执行的,因此效率较上一个写法要高很多。
发起请求,写法三
第三个写法就是在 Retorfit
的 CallAdapter
上做文章,通过自定义实现 CallAdapterFactory
,将api定义时的结果 Call
直接转换成 Deferred
,这样就可以同时发起Android列表请求和IOS列表请求,然后通过 Deferred.await
获取请求结果,这种写法是写法一写法二的结合。
这种写法 JakeWharton
大神早已为我们实现了,地址在这 github.com/JakeWharton…
这里我就不说这种方案的具体实现了,感兴趣的同学可以去看其源码。
写法三的具体代码如下:
val instance = Retrofit.Builder() .baseUrl("http://gank.io/api/") .addCallAdapterFactory(CoroutineCallAdapterFactory()) .addConverterFactory(GsonConverterFactory.create()) .build().create(CallAdapterApiService::class.java) suspend fun queryGanks(): List<Gank> { return withContext(Dispatchers.Main) { try { val androidDeferred = ApiSource.callAdapterInstance.getAndroidGank() val iosDeferred = ApiSource.callAdapterInstance.getIOSGank() val androidResult = androidDeferred.await().results val iosResult = iosDeferred.await().results val result = mutableListOf<Gank>().apply { addAll(iosResult) addAll(androidResult) } result } catch (e: Throwable) { e.printStackTrace() throw e } } } 复制代码
上面的第三种写法看起来更简洁,也是并行请求,耗时为请求时间最长的那个请求的时间,和第二种差不多。
具体实现demo的地址见文章开头,有兴趣的可以看看。
以上所述就是小编给大家介绍的《Android中用Kotlin Coroutine(协程)和Retrofit进行网络请求和取消请求》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- R中用线性回归进行预测建模
- 在Markdown中用mermaid语法绘制图表
- iOS面试题·项目中用过 Runtime 吗?
- 在docker中用Tomcat运行web项目
- 在项目实践中用更优雅的方式处理数组问题
- ajax中用josnp接收josn数据的实现方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Servlet&JSP经典实例
(美)佩里 / 朱涛江、邹红霞、林琪 / 中国电力出版社 / 2005-7 / 86.00元
本书将用于帮助指导Java web开发人员的日常任务,提供典型的web相关问题的快速解决方案。本书集中介绍了如何用Java初始化某些与web相关的任务,而不是教会读者如何使用Java语言,或者事无巨细地解释servlet和JSP API。书中包含了大量关于复杂的日常开发任务的技巧,这些技巧涵盖了许多与Servlet 2.4和JSP 2.0规范相关联的新特性,包括ServletRequestList......一起来看看 《Java Servlet&JSP经典实例》 这本书的介绍吧!