前言:由于框架本身也在不断地迭代,因此文章中的部分代码可能存在更新或者过时,如果你想阅读源码或者查看代码的在项目中的实际使用方法,可以查看笔者目前在维护的compose项目:Spacecraft: 《Spacecraft - 我的安卓技术实践平台》-查看代码请进入develop分支 (gitee.com)
关于上一篇的一些补全
在上一篇文章中,笔者提出的NetworkResult缺乏了部分关键代码,首先我们重新回顾这部分代码。
sealed class NetworkResult<T> { /** * 网络请求成功 */ class Success<T>(private val response: Response<T>) : NetworkResult<T>(){} /** * 网络请求失败 */ sealed class Failure<T> : NetworkResult<T>() { /** * 服务器内部错误 */ data class ServerError<T>(private val response: Response<T>) : Failure<T>() {} /** * 网络请求出现异常 */ data class Exception<T> constructor( val exception: Throwable ) : Failure<T>() {} } }
可以看到,Response转成NetworkResult的过程,实际上就是用NetworkResult将Response一部分或者全部包裹起来的过程,当然我们并不希望将原始的Response暴露给调用者,我们希望做一些接口上的屏蔽,把对Response的取值交给接口属性,接下来定义一套接口。
private interface ResponseGetter { //http状态码 val code: Int //响应头 val headers: Headers //请求Url(部分场景用于判断) val url: String }
注:可以扩充你希望得到的属性
接下来,让Result子类实现这个接口,都用lazy来委任取值(避免反复调用取值函数降低性能),需要注意的是Success和ServerError分别拥有一个单独的属性:responseBody和ResponseErrorMessage。
responseBody是接口的返回值对应的泛型类型,responseErrorMessage则是服务器内部错误的信息,一般是一个HTML(具体看后台框架)
/** * 网络请求成功 */ class Success<T>(private val response: Response<T>) : NetworkResult<T>(), ResponseGetter { val responseBody by lazy { response.body()!! } override val code by lazy { response.code() } override val headers: Headers by lazy { response.headers() } override val url by lazy { response.raw().request.url.toString() } } /** * HTTP协议错误 */ data class ServerError<T>(val response: Response<T>) : Failure<T>(), ResponseGetter { val responseErrorMessage: String by lazy { response.errorBody()?.string().orEmpty() } override val code by lazy { response.code() } override val headers: Headers by lazy { response.headers() } override val url by lazy { response.raw().request.url.toString() } }
&nmsp; 因为Exception并没有Response,所以我们不需要实现接口方法,独立给他增加一个异常信息。所谓的“异常信息”并不是指异常本身的错误堆栈,这些堆栈是给程序员阅读的,用户并不知道是什么含义,所以我们需要针对特定的异常去翻译一套用户能够识别的错误信息,例如将网络中断异常翻译成“Network Error”。至于如何实现我们下文讲解。
/** * 网络请求出现异常 */ data class Exception<T>(val exception: Throwable) : Failure<T>() { //分析异常类型,返回自然语言错误信息 val exceptionMessage:String by lazy { //TODO 下文讲解 } }
好了,上一篇的坑已经补完了,我们接下来继续讲解实际开发中会遇到的问题。
实际开发中会遇到的问题
这些问题的解决过程肯定离不开okhttp(毕竟框架底层就是okhttp),因此笔者希望你对okhttp有一定的理解,特别是拦截器的层面。
公共参数
几乎所有的项目都会遇到添加公参的问题,这里以POST请求为例,讲解一下如何添加公参:众所周知,POST请求的请求体是放在body中的,因此我们只需要以下几步:
- 新增一个okhttp拦截器
- 通过chain获取request,通过request获取到原始的body,将body中的字节流转成字符串或者其他格式(例如JSON,这个具体看你们公司项目)
- 对转换后的对象进行添加公参操作,例如JSON就是添加一些字段
- 将对象转回request,通过chain传递到下一个拦截器中
依然是废话不多说,看代码!
/** * 公参基类,重写方法来增加公参 */ abstract class BaseCommonParamsInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originRequest = chain.request() //1.body转成json,如果body为空则构建空json,只填充公参 val jsonBody = (originRequest.body?.toJson() ?: JSONObject()).apply { //2.添加公参 addCommonParams() } //3.构建新的requestBody,传递给下一个拦截器 return chain.proceed(originRequest.newBuilder().apply { method( originRequest.method, jsonBody.toString().toRequestBody(originRequest.body?.contentType()) ) }.build()) } //公参 protected abstract fun getCommonParams(): Map<String, String> //给JSON添加参数 private fun JSONObject.addCommonParams() { getCommonParams().forEach { entry -> put(entry.key, entry.value) } } } //将RequestBody中的字节流转成字符串 fun RequestBody.string(): String { val buffer = Buffer() writeTo(buffer) return buffer.readUtf8() } fun RequestBody.toJson(): JSONObject { return string().toJSONObject() }
originRequest会被转成Json对象,然后对json对象进行添加公参,逻辑非常简单。需要注意的是RequestBody.string()这个扩展方法,通过它转成字符串后,你就可以根据你们公司的请求格式来转成其他实体类了,笔者演示的是转成json。
接下来重写,然后添加到okhttp的构造方法链中就好了。
class MyCommonParamsInterceptor : BaseCommonParamsInterceptor() { override fun getCommonParams(): Map<String, String> = CommHttpParams.getInstance().urlParamsMap }
实际上大部分的定制逻辑(接近99%),都可以通过添加拦截器来实现,okhttp的拦截器真的是神器。