1、前言

之前在学习郭霖《第一行代码》时按部就班地写过一个彩云天气 App,对里面的网络请求框架的封装印象非常深刻,很喜欢这种 Retrofit + Kotlin + 协程的搭配使用。随后也在自己的项目里参考了这部分的代码。但随着代码的深入编写和功能的复杂,原来的框架已经无法满足我的使用了。原主要有如下的痛点:

  • 缺少失败的回调
  • 显示加载中动画比较麻烦

后面我自己试着努力去封装一个简单易用的框架,可惜个人能力有限,自己封装的框架总是不如人意。好在还有很多优秀的博客和代码可供参考。在此基础上,对彩云天气 App中的网络请求框架做了一些修改,尽可能地做到简单易用。以请求玩安卓的登录接口为例(用户名和密码是我自己申请的,见代码),页面上有一个按钮,点击按钮后就发起登录请求。

先来看看发起请求后的回调怎么写:

viewModel.loginLiveData.observeState(this) {onStart {LoadingDialog.show(activity)Log.d(TAG, "请求开始")}onSuccess {Log.d(TAG, "请求成功")showToast("登录成功")binding.tvResult.text = it.toString()}onEmpty {showToast("数据为空")}onFailure {Log.d(TAG, "请求失败")showToast(it.errorMsg.orEmpty())binding.tvResult.text = it.toString()}onFinish {LoadingDialog.dismiss(activity)Log.d(TAG, "请求结束")}
}

回调一共有五种,会在下文详细介绍。这里采用了DSL的写法,如果你喜欢传统的写法,可以调用另外一个扩展方法observeResponse(),由于它最后一个参数就是请求成功的回调,所以借助 Lambda 表达式的特性,可以简洁地写成如下的形式:

viewModel.loginLiveData.observeResponse(this){binding.tvResult.text = it.toString()
}

如果还需要其他回调,可以使用具名参数加上,如下所示:

viewModel.loginLiveData.observeResponse(this, onStart = {LoadingDialog.show(this)
}, onFinish = {LoadingDialog.dismiss(activity)
}) {binding.tvResult.text = it.toString()
}

2、框架搭建

开始之前必须说明,这个框架是基于《第一行代码》(第三版)中的彩云天气 App的,它的架构图如下所示,如果你阅读过《第一行代码》或者谷歌的相关文档,那么想必对此不会陌生。

2.1 添加依赖库

//简化在 Activity 中声明 ViewModel 的代码
implementation "androidx.activity:activity-ktx:1.3.1"// lifecycle
def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"// retrofit2
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'// okhttp
def okhttp_version = "4.8.1"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"//日志拦截器
implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0') {exclude group: 'org.json', module: 'json'
}

2.2 Retrofit构建器

Retrofit构建器这里做了分层,基类做了一些基本的配置,子类继承后可以添加新的配置,并配置自己喜欢的日志拦截器。

private const val TIME_OUT_LENGTH = 8Lprivate const val BASE_URL = "https://www.wanandroid.com/"abstract class BaseRetrofitBuilder {private val okHttpClient: OkHttpClient by lazy {val builder = OkHttpClient.Builder().callTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS).connectTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS).readTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS).writeTimeout(TIME_OUT_LENGTH, TimeUnit.SECONDS).retryOnConnectionFailure(true)initLoggingInterceptor()?.also {builder.addInterceptor(it)}handleOkHttpClientBuilder(builder)builder.build()}private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(okHttpClient).build()fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)inline fun <reified T> create(): T = create(T::class.java)/*** 子类自定义 OKHttpClient 的配置*/abstract fun handleOkHttpClientBuilder(builder: OkHttpClient.Builder)/*** 配置日志拦截器*/abstract fun initLoggingInterceptor(): Interceptor?
}

RetrofitBuilder

private const val LOG_TAG_HTTP_REQUEST = "okhttp_request"
private const val LOG_TAG_HTTP_RESULT = "okhttp_result"object RetrofitBuilder : BaseRetrofitBuilder() {override fun handleOkHttpClientBuilder(builder: OkHttpClient.Builder) {}override fun initLoggingInterceptor()= LoggingInterceptor.Builder().setLevel(Level.BASIC).log(Platform.INFO).request(LOG_TAG_HTTP_REQUEST).response(LOG_TAG_HTTP_RESULT).build()
}

2.3 全局异常处理

请求时可能会遇到诸如网络断开、Json 解析失败等意外情况,如果我们每次请求都要处理一遍这些异常,那也未免太麻烦了。正确的做法是把异常集中到一起处理。

创建一个定义各种异常的枚举类:

enum class HttpError(val code: Int, val message: String){UNKNOWN(-100,"未知错误"),NETWORK_ERROR(1000, "网络连接超时,请检查网络"),JSON_PARSE_ERROR(1001, "Json 解析失败")//······
}

创建一个文件,在里面定义一个全局方法,用于处理各种异常:

fun handleException(throwable: Throwable) = when (throwable) {is UnknownHostException -> RequestException(HttpError.NETWORK_ERROR, throwable.message)is HttpException -> {val errorModel = throwable.response()?.errorBody()?.string()?.run {Gson().fromJson(this, ErrorBodyModel::class.java)} ?: ErrorBodyModel()RequestException(errorMsg = errorModel.message, error = errorModel.error)}is JsonParseException -> RequestException(HttpError.JSON_PARSE_ERROR, throwable.message)is RequestException -> throwableelse -> RequestException(HttpError.UNKNOWN, throwable.message)
}

实际项目中遇到的异常当然不止这几个,这里只是作为举例写了少部分,实际开放中把它丰富完善即可。

2.4 回调状态监听

回调状态一共有四种:

  • onStart():请求开始(可在此展示加载动画)
  • onSuccess():请求成功
  • onEmpty():请求成功,但datanull或者data是集合类型但为空
  • onFailure():请求失败
  • onFinish():请求结束(可在此关闭加载动画)

这里要注意onSuccess的标准:并不仅仅是 Http 请求的结果码(status code)等于 200,而且要达到Api请求成功的标准,以玩安卓的Api 为例,errorCode 为 0时,发起的请求才是执行成功;否则,都应该归为onFailure()的情况(可以参考文章附带的思维导图)。

理清楚有几种回调状态后,就可以实施监听了。那么在哪里监听呢?LiveDataobserve()方法的第二个函数可以传入Observer参数。Observer是一个接口,我们继承它自定义一个Oberver,借此我们就可以监听LiveData的值的变化。

interface IStateObserver<T> : Observer<BaseResponse<T>> {override fun onChanged(response: BaseResponse<T>?) {when (response) {is StartResponse -> {//onStart()回调后不能直接就调用onFinish(),必须等待请求结束onStart()return}is SuccessResponse -> onSuccess(response.data)is EmptyResponse -> onEmpty()is FailureResponse -> onFailure(response.exception)}onFinish()}/*** 请求开始*/fun onStart()/*** 请求成功,且 data 不为 null*/fun onSuccess(data: T)/*** 请求成功,但 data 为 null 或者 data 是集合类型但为空*/fun onEmpty()/*** 请求失败*/fun onFailure(e: RequestException)/*** 请求结束*/fun onFinish()
}

接下来我们准备一个HttpRequestCallback类,用于实现DSL的回调形式:

typealias OnSuccessCallback<T> = (data: T) -> Unit
typealias OnFailureCallback = (e: RequestException) -> Unit
typealias OnUnitCallback = () -> Unitclass HttpRequestCallback<T> {var startCallback: OnUnitCallback? = nullvar successCallback: OnSuccessCallback<T>? = nullvar emptyCallback: OnUnitCallback? = nullvar failureCallback: OnFailureCallback? = nullvar finishCallback: OnUnitCallback? = nullfun onStart(block: OnUnitCallback) {startCallback = block}fun onSuccess(block: OnSuccessCallback<T>) {successCallback = block}fun onEmpty(block: OnUnitCallback) {emptyCallback = block}fun onFailure(block: OnFailureCallback) {failureCallback = block}fun onFinish(block: OnUnitCallback) {finishCallback = block}
}

然后声明新的监听方法,考虑到某些时候需要自定义的LiveData(比如为了解决数据倒灌的问题),这里采用扩展函数的写法,便于扩展。

/*** 监听 LiveData 的值的变化,回调为 DSL 的形式*/
inline fun <T> LiveData<BaseResponse<T>>.observeState(owner: LifecycleOwner,crossinline callback: HttpRequestCallback<T>.() -> Unit
) {val requestCallback = HttpRequestCallback<T>().apply(callback)observe(owner, object : IStateObserver<T> {override fun onStart() {requestCallback.startCallback?.invoke()}override fun onSuccess(data: T) {requestCallback.successCallback?.invoke(data)}override fun onEmpty() {requestCallback.emptyCallback?.invoke()}override fun onFailure(e: RequestException) {requestCallback.failureCallback?.invoke(e)}override fun onFinish() {requestCallback.finishCallback?.invoke()}})
}/*** 监听 LiveData 的值的变化*/
inline fun <T> LiveData<BaseResponse<T>>.observeResponse(owner: LifecycleOwner,crossinline onStart: OnUnitCallback = {},crossinline onEmpty: OnUnitCallback = {},crossinline onFailure: OnFailureCallback = { e: RequestException -> },crossinline onFinish: OnUnitCallback = {},crossinline onSuccess: OnSuccessCallback<T>
) {observe(owner, object : IStateObserver<T> {override fun onStart() {onStart()}override fun onSuccess(data: T) {onSuccess(data)}override fun onEmpty() {onEmpty()}override fun onFailure(e: RequestException) {onFailure(e)}override fun onFinish() {onFinish()}})
}

2.5 Repository 层的封装

Repository层作为数据的来源,有个两个渠道:网络请求和数据库。这里暂时只处理了网络请求。

基类Repository

abstract class BaseRepository {protected fun <T> fire(context: CoroutineContext = Dispatchers.IO,block: suspend () -> BaseResponse<T>): LiveData<BaseResponse<T>> = liveData(context) {this.runCatching {emit(StartResponse())block()}.onSuccess {//status code 为200,继续判断 errorCode 是否为 0emit(when (it.success) {true -> checkEmptyResponse(it.data)false -> FailureResponse(handleException(RequestException(it)))})}.onFailure { throwable ->emit(FailureResponse(handleException(throwable)))}}/*** data 为 null,或者 data 是集合类型,但是集合为空都会进入 onEmpty 回调*/private fun <T> checkEmptyResponse(data: T?): ApiResponse<T> =if (data == null || (data is List<*> && (data as List<*>).isEmpty())) {EmptyResponse()} else {SuccessResponse(data)}
}

子类Repository:

object Repository : BaseRepository() {fun login(pwd: String) = fire {NetworkDataSource.login(pwd)}}

网络请求数据源,在这里调用网络接口:

object NetworkDataSource {private val apiService = RetrofitBuilder.create<ApiService>()suspend fun login(pwd: String) = apiService.login(password = pwd)
}

2.6 ViewModel层的封装

ViewModel基本遵循了《第一行代码》中的写法,创建了两个LiveData。用户点击按钮时,loginAction的值就会发生改变,触发switchMap中的代码,从而达到请求数据的目的。

class MainViewModel : ViewModel() {private val loginAction = MutableLiveData<Boolean>()/*** loginAction 在这里只传递布尔值,不传递密码,在实际项目中,会使用 DataBinding 绑定 xml 布局和 ViewModel,* 不需要从 Activity 或者 Fragment 中把密码传入 ViewModel*/val loginLiveData = loginAction.switchMap {if (it) {Repository.login("PuKxVxvMzBp2EJM")} else {Repository.login("123456")}}/*** 点击登录*/fun login() {loginAction.value = true}fun loginWithWrongPwd() {loginAction.value = false}
}

注意:这种写法通常不从View向ViewModel层传递数据,是需要搭配DataBinding 的。如果你不想这样写,可以修改BaseRepository中的返回值,直接返回BaseResponse

3、思维导图及源码

最后,用一张思维导图总结本文:

源码地址:GitHub (注意分支要选择 dev1.0)

4、参考

  • JetpackMvvm
  • FastJetpack

Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试相关推荐

  1. Kotlin+Retrofit + MVVM 的网络请求框架的封装

    Kotlin 代码我上传了,里面注释写的很详细,大家有什么不懂的可以私信我, 因为文章内容实在是太多了,不想写 代码地址: https://wwp.lanzoum.com/iDOvK0eb0ude 密 ...

  2. Android中网络请求框架的封装-Retrofit+RxJava+OkHttp

    Retrofit注解 请求方法 注解代码 请求格式 @GET GET请求 @POST POST请求 @DELETE DELETE请求 @HEAD HEAD请求 @OPTIONS OPTIONS请求 @ ...

  3. android网络请求框架_2020,最新APP重构:网络请求框架

    在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点.在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请 ...

  4. ios-APP重构之路(一) 网络请求框架

    前言 在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点.在这篇文章中我所提出的架构,并不是所谓的 最好 的 ...

  5. Kotlin使用Coroutine+ViewModel+retrofit构建一个网络请求框架

    Kotlin使用Coroutine+ViewModel+retrofit构建一个网络请求框架 公司里的老代码用的网络请求框架技术都比较老,为了快速搭建一个网络请求框架,提高工作效率,记录一下用jetp ...

  6. Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架

    目录 1.什么是MVP? 2.什么是Retrofit? 3.RxJava 4.实践 之前公司的项目用到了MVP+Retrofit+RxJava的框架进行网络请求,所以今天特此写一篇文章以做总结.相信很 ...

  7. Retrofit网络请求框架使用简析——Android网络请求框架(四)

    题记:-- 很累,累到想要放弃,但是放弃之后将会是一无所有,又不能放弃, 唯有坚持,唯有给自忆打气,才能更勇敢的走下去,因为无路可退,只能前行, 时光一去不复返,每一天都不可追回,所以要更珍惜每一存光 ...

  8. Android肝帝战纪之网络请求框架封装(Retrofit的封装)

    网络请求框架封装(OkHttp3+Retrofit+loading的封装) Retrofit的Github链接 点此链接到Github AVLoadingIndicatorView的Github链接( ...

  9. Android网络请求框架之Retrofit(二)

    前面一篇文章介绍了Retrofit的基本用法,没有看过的童鞋可以移步:Android网络请求框架之Retrofit(一),现在我们来继续介绍Retrofit配合RxJava.RxAndroid的用法. ...

最新文章

  1. bseg---faglflexta
  2. git pull命令执行时,命令框不可操作的问题
  3. 实验7-3-7 字符转换 (15分)
  4. 如何在ASP.NET Core 中快速构建PDF文档
  5. linux系统中使用pycharn,在pycharm中使用linux控制台
  6. 【转】ABP源码分析二十:ApplicationService
  7. (二十一)TCPIP面试宝典-进入大厂必备总结(下)
  8. 为什么阿里全面推动 K8S 落地,咬紧牙关也要搞云原生?
  9. 作者:陈威,电子科技大学互联网科学中心硕士生。
  10. Jdk动态代理 底层源码分析
  11. 怎么调出全局搜索_局部静态变量只能初始化一次?它是怎么实现的
  12. NOIp2018集训test-9-17(pm)
  13. 【转载】聪明说话35招
  14. android 常用依赖库
  15. C3:Unity3D制作智能家居设计软件——绘制户型(二)
  16. 国际大牌在中国的故事
  17. 客户个性分析 聚类 大数据
  18. TOM邮箱怎么样 TOM邮箱品牌测评分析
  19. Java 微信小程序笔记 三、 微信小程序邀请码生成
  20. Jump Server

热门文章

  1. 码农翻身——Java帝国之动态代理
  2. 基于Centos7.5源码编译搭建LAMP架构
  3. bomblab 拆炸弹
  4. E2Echallenge参赛模型汇总
  5. codeforces:C. Another Array Problem【分类讨论 + 找规律】
  6. JAVA--Map集合详解
  7. 再谈:互联网基本思维就是免费,本质就是资本运作
  8. 操作系统和网络(八):linux基础运维之服务器性能查看
  9. Android Studio 解决手机无法识别问题
  10. 【5G NR】3GPP常用协议整理