优雅使用Retrofit,在协程时代遨游安卓网络请求(二)上

简介: 优雅使用Retrofit,在协程时代遨游安卓网络请求(二)

前言:由于框架本身也在不断地迭代,因此文章中的部分代码可能存在更新或者过时,如果你想阅读源码或者查看代码的在项目中的实际使用方法,可以查看笔者目前在维护的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的过程,实际上就是用NetworkResultResponse一部分或者全部包裹起来的过程,当然我们并不希望将原始的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中的,因此我们只需要以下几步:

  1. 新增一个okhttp拦截器
  2. 通过chain获取request,通过request获取到原始的body,将body中的字节流转成字符串或者其他格式(例如JSON,这个具体看你们公司项目)
  3. 对转换后的对象进行添加公参操作,例如JSON就是添加一些字段
  4. 将对象转回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的拦截器真的是神器。

image.png



相关文章
|
开发工具 Android开发
Mac 安卓(Android) 配置adb路径
Mac 安卓(Android) 配置adb路径
1636 0
|
Android开发
mac下配置adb环境变量
在终端中输入adb命令时,会提示 command not found ,这是是因为mac电脑下没有配置Android环境变量或者环境变量配置错误。
|
存储 开发工具 git
Git 远程仓库地址管理:添加、修改和验证
Git 远程仓库地址管理:添加、修改和验证
1498 4
|
算法 搜索推荐 UED
C 端试用期考核指标
本文详细介绍了C端产品经理试用期的考核指标,涵盖产品成功、开发效率、用户体验、市场研究、团队协作和创新能力等方面。考核方式包括自评、上级评估、同事评估、用户评估和项目评估。通过定量与定性相结合的方法,确保全面客观地评估产品经理的工作表现,并附有实际案例供参考。
570 0
|
资源调度 监控 供应链
ERP、APS、MES 三者之间的关系
ERP、APS、MES三者之间的关系是相互补充的。ERP系统是企业内部管理的总体解决方案,负责统一管理各种资源和流程。APS系统是ERP系统的补充,负责优化企业的生产计划和调度。MES系统则是在生产过程中对实时数据进行监督、跟踪和控制的系统。三者共同协作,帮助企业提高生产效率、提升质量、降低成本,并实现企业的数字化转型。
1159 0
ERP、APS、MES 三者之间的关系
|
Ubuntu 网络协议 Linux
在Linux中,发行版和内核有什么区别?
在Linux中,发行版和内核有什么区别?
|
XML 算法 Java
Android Studio App开发之利用图像解码器ImageDecoder播放GIF动图、Webp、HEIF图片(附源码 简单实用)
Android Studio App开发之利用图像解码器ImageDecoder播放GIF动图、Webp、HEIF图片(附源码 简单实用)
1362 0
Mac配置adb环境
Mac配置adb环境
325 0
一劳永逸,wsl2出现“参考的对象类型不支持尝试的操作”的解决办法
wsl在使用是会出现“参考的对象类型不支持尝试的操作”的故障导致无法使用。
3025 1
一劳永逸,wsl2出现“参考的对象类型不支持尝试的操作”的解决办法
|
JavaScript API
vue3项目配置代理
在 Vue3 项目中使用代理有两种方式: 使用 Vue CLI 的 devServer.proxy 配置
698 0

热门文章

最新文章

下一篇
开通oss服务