由于文章涉及到的只是点比较多、内容可能过长,可以根据自己的能力水平和熟悉程度分阶段跳着看。如有讲述的不正确的地方劳烦各位私信给笔者,万分感谢
由于时间原因,笔者白天工作只有晚上空闲时间才能写作,所以更新频率应该在2-3天一篇,当然我也会尽量的利用时间,争取能够提前发布。为了方便阅读将本文章拆分个多个章节,根据自己需要选择对应的章节,现在也只是目前笔者心里的一个大概目录,最终以更新为准:
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
本章前言
本章节中除了会对协程做讲解外,不会对其他引入的框架做讲解。文章是基于用户已经对这些框架有一定的入门基础上,对与框架如何结合kotlin
协程的使用做一个引导。整个篇幅会有些长,我们会在结合使用的同时,做一些架构上的封装,也是为了方便后续在实战的时候,大家能更方便、直观的理解代码。
笔者也只是一个普普通通的开发者,架构上的设计不一定合理,大家可以自行吸收文章精华,去糟粕。
kotlin协程的使用封装
在上一章节中,我们已经了解了协程在Activity
、Fragment
、Lifecycle
、Viewmodel
的基础使用,以及如何简单的自定义一个协程。本章节中,我们主要是做一些基础的封装工作。我们将在上一章节的内容基础上,引入DataBinding
、LiveData
、Flow
等做一些基础封装。比如:Base类的定义,协程的使用封装,常用扩展函数等。
我们先引入本章节所使用到的相关库:
1 | kotlin复制代码 // Kotlin |
现在我们就可以开始做一些基础的封装工作,同时在app的bulid.gradle
文件中开启dataBinding
的使用
1 | kotlin复制代码android { |
文章中基于DataBinding
的使用,可以参考 封装DataBinding让你少写万行代码
ViewModel
的使用可以参考ViewModel的日常使用封装
这两篇文章是为了本章节专门写的扩展性阅读。
最近因为电脑炸机了,信号输出不稳定,开始以为是显卡坏了,折腾了几天还是没整好,最后发现是主板被腐蚀导致线路故障,当前用的主板停产很久了,最后只能找个兼容的,等了几天才到货,最终也导致本章节稍微延后了几天。电子产品太脆弱了,一定要注意防摔
、防磕碰
、防腐蚀
!废话不多说,下面进入我们今天的正题。
协程的常用环境
在实际的开发过程中,我们经常需要把耗时处理移到非主线程
上执行,等耗时操作异步完成以后,再回到主线程
上刷新界面。基于这些需求,我们大致可以把使用协程的环境分为下面五种环境:
网络请求
回调处理
数据库操作
文件操作
其他耗时操作
下面我们首先对网络请求
这块进行处理。目前市面上大多数APP的在处理网络请求
时候,都是使用的RxJava
结合Retrofit
、OkHttp
进行网络请求处理。我们最终的目的也是使用协程
结合Retrofit
、okHttp
进行网络请求处理。
我们在这里只是针对Retrofit
、OkHttp
结合协程
、ViewModel
、LiveData
使用讲解,如果需要了解Retrofit
和okHttp
的原理,可以看看其他作者的原理分解文章。
协程在网络请求下的封装及使用
为了演示效果,笔者在万维易源申请了一面免费的天气API,我们使用的接口地址:
1 | kotlin复制代码http[s]://route.showapi.com/9-2?showapi_appid=替换自己的值&showapi_sign=替换自己的值 |
此接口返回的通用数据格式,其中showapi_res_body
返回的json内容比较多,笔者从中挑选了我们主要关注的几个字段:
参数名称 | 类型 | 描述 |
---|---|---|
showapi_res_body |
String | 消息体的JSON封装,所有应用级的返回参数将嵌入此对象 。 |
showapi_res_code |
int | 查看错误码 |
showapi_res_error |
String | 错误信息的展示 |
1 | JSON复制代码{ |
当然我们还需要一个接收数据的对象,为了避免和其他库容易弄混淆,我们命名为CResponse
,这个结构大家都很熟悉:
1 | KOTLIN复制代码data class CResponse<T>( |
由于API返回的字段名称实在是不符合笔者的胃口,而且用起来也不美观。所以笔者通过Gson
的注解SerializedName
将属性进行重命名。我们在实际开发中常常也会遇到这种问题,同样可以通过这种方法进行处理。
1 | KOTLIN复制代码data class Weather( |
然后我们创建一下okHttp
、Retrofit
。在Retrofit
2.6版本以后我们不再需要引入Retrofit
的coroutine-adapter
适配器库,我们直接使用即可:
1 | KOTLIN复制代码object ServerApi { |
1 | KOTLIN复制代码object OkHttpClientManager { |
由于我们在调用的天气API接口的时候showapi_appid
和showapi_sign
必传的值,所以我们增加了一个CommonInterceptor
拦截器来统一处理:
1 | KOTLIN复制代码class CommonInterceptor : Interceptor { |
为了方便快速演示,笔者从请求的参数列表中只抽取了一个用来演示,接下来我们定义我们在请求需要通过Retrofit
使用的接口CoroutineService
:
请求参数 | 类型 | 描述 |
---|---|---|
area |
String | 要查询的地区名称 。 |
1 | KOTLIN复制代码interface CoroutineService { |
可以看到我们在使用Retrofit
结合协程使用时,我们只需要在函数前增加suspend
关键字就可以,同时返回结果可以直接定义为,我们需要从请求结果中解析出来的数据对象,而不再是像以前一样定义为Call<T>
。
到此为止,我们基于基础数据的定义已经结束了,下面我们将正式进入我们今天的主题。为了更加清晰的理解,笔者这里不会采用直接一步到位的方式。那样可能会有很多人阅读理解起来有困难。笔者将会对请求过程进行一步一步的封装,这里需要一点耐心。
我们先创建一个Repository
来请求数据:
1 | KOTLIN复制代码class WeatherRepository { |
同时在创建一个MainViewModel
来使用Repository
1 | KOTLIN复制代码class MainViewModel(private val repository: WeatherRepository):ViewModel() { |
现在我们就可以在MainActivity
中创建MainViewModel
来调用方法获取天气数据。我们在创建ViewModel
对象的时候不再使用ViewModelProviders.of(this).get(MainViewModel::class.java) 这种方式。而是使用activity-ktx
库中的viewModels
方法去创建:
1 | KOTLIN复制代码public inline fun <reified VM : ViewModel> ComponentActivity.viewModels( |
这个方法需要我们传入一个Factory
,我们自己定义一个实现:
1 | KOTLIN复制代码object ViewModelUtils { |
接下来我们在MainActivity
使用,通过使用ViewModelUtils
获取MainViewModelFactory
,然后使用viewModels
进行创建我们需要的viewModel
对象:
1 | KOTLIN复制代码class MainActivity : BaseActivity<ActivityMainBinding>() { |
initObserve
是我们在BaseActivity
中定义的抽象方法。我们只在activity_main.xml
简单定义了一个Textview
来显示数据,虽然在XML中引入了mainViewModel
,但是为演示过程,我们没有使用DataBinding
直接做数据绑定。而在实际开发中应该是使用DataBinding
直接在XML中数据绑定。
1 | XML复制代码<?xml version="1.0" encoding="utf-8"?> |
我们成功的请求到数据,并且显示在我们界面上。但是有个问题上我们现在的请求是没有做异常处理的。现在我们处理下请求过程中的异常:
1 | KOTLIN复制代码 fun getWeather(area: String){ |
这种做法虽然处理了异常,但是非常丑陋,而且我们需要在每一个请求地方写的时候,那将会是一个噩梦般的诅咒。
接下来将会是我们的重点内容,笔者将会封装出三种形式的调用,开始的时候对应的场景使用即可。
高阶函数方式
这个时候我们需要创建一个BaseRepository
来进行封装处理,我们通过onSuccess
获取成功的结果,通过onError
来处理针对此次请求的特有异常,以及通过onComplete
来处理执行完成的操作:
1 | KOTLIN复制代码open class BaseRepository { |
这个时候我们再修改一下WeatherRepository
中的getWeather
方法,我们需要通过launchRequest
来包裹一下请求就可以:
1 | KOTLIN复制代码 suspend fun getWeather( |
然后我们修改一下MainViewModel
中的getWeather
方法,我们在处理异常的位置处理此次接口特有的异常即可,同时可以在请求结束后做一些收尾工作:
1 | KOTLIN复制代码 fun getWeather(area: String) { |
同时除第一个执行请求的参数外,后面三个参数都可以传入为空实现。避免在不需要处理成功,异常,执行完成等操作的时候,出现这种影响美观的代码。假如我们通过sendData
服务器发送一个数据,这个数据是否处理成功我们不需要关心,这个时候我们就可以如下操作:
1 | KOTLIN复制代码 fun sendData(data: String) { |
我们再回过头来看看launchRequest
方法,我们在处理请求返回的结果时候直接就返回response
。但是实际开发中我们一般在请求接口返回数据的时候,是需要判断接口数据状态code
值是成功的时候才能返回数据。
我们本例中这个状态值是0
。这个时候我们需要处理一下增加一个处理response
的方法.我们再修改一下launchRequest
方法:
1 | KOTLIN复制代码 suspend inline fun <reified T : Any> launchRequest( |
可以看到我们在处理response
的时候,我们先通过判断返回的诗句类型是否为List
集合类型。如果是集合类型且数据返回了一个null
的时候,我们就尝试把一个的空集合转换为结果。
1 | KOTLIN复制代码 val isListType = T::class.isSubclassOf(List::class) |
多状态函数返回值方式
上面的封装方式我们是通过kotlin的高阶函数去实现的。假如我们想直接通过请求结果的时候,再结合其他请求处理数据通知界面刷新的时候,上面就显得很麻烦,而且好像又走到的无限嵌套的坑里。
这个时候我们就需要直接通过函数返回值来处理。现在我们首先的创建一个DataResult
来封装一下返回结果,我们将返回的数据分成成功或者失败两种:
1 | KOTLIN复制代码sealed class DataResult<out T> { |
然然后在创建一个launchRequestForResult
把之前的launchRequest
代码拷贝过来稍作修改:
1 | KOTLIN复制代码 suspend inline fun <reified T : Any> launchRequestForResult( |
我们在WeatherRepository
中再增加getWeather
方法,通过launchRequestForResult
来处理请求:
1 | KOTLIN复制代码 suspend fun getWeather(area: String): DataResult<Weather> { |
然后我们同时我们也在MainViewModel
中增加一个getWeatherForResult
方法,这个时候我们就可以按我们的常规的编写代码顺序处理结果:
1 | KOTLIN复制代码 fun getWeatherForResult(area: String) { |
当然,这种方式处理起来还是相对有些繁琐,因为当我们有多个请求是,我们需要写多个when
来判断结果。那如果我们也不想写这些模板代码又该如何处理呢
直接返回值的方式
这个时候我们就需要在launchRequestForResult
的基础上进一步的处理:
1 | KOTLIN复制代码 suspend inline fun <reified T : Any> launchRequest( |
因为考虑到实际开发环境中,我们还是可能需要在外部处理异常提示的所以在这里还是通过throw
重新抛出异常。如果外部不想处理非内接口CException
异常,可以参考下面直接在catch
返回null
即可:
1 | KOTLIN复制代码 suspend inline fun <reified T : Any> launchRequest( |
同样在WeatherRepository
中再增加getWeather
方法,通过获取返回值的launchRequest
来处理请求:
1 | KOTLIN复制代码 suspend fun getWeather(area: String): Weather? { |
因为我们在launchRequest
重新抛出了异常,所以我们需要在请求的地方捕获一下:
1 | KOTLIN复制代码 fun getWeather(area: String) { |
上面的三种方式算上一种抛砖引玉,其实我们还可以进一步的通过抽象ViewModel
来统一处理内部接口请求异常。
如果您有更好的方法或者思路想法,欢迎交流。架构的演进以及代码的封装需要不断的学习和沟通,每一次知识交流与碰撞都是有意义的。
需要源码的看这里:demo源码
原创不易。如果您喜欢这篇文章,您可以动动小手点赞收藏。
关联文章
Kotlin
协程基础及原理系列
- 史上最详Android版kotlin协程入门进阶实战(一) -> kotlin协程的基础用法
- 史上最详Android版kotlin协程入门进阶实战(二) -> kotlin协程的关键知识点初步讲解
- 史上最详Android版kotlin协程入门进阶实战(三) -> kotlin协程的异常处理
- 史上最详Android版kotlin协程入门进阶实战(四) -> 使用kotlin协程开发Android的应用
- 史上最详Android版kotlin协程入门进阶实战(五) -> kotlin协程的网络请求封装
Flow
系列
扩展系列
本文转载自: 掘金