Retrofit 妙用,拒绝重复代码!

作者: 像程序那样去思考 | 来源:发表于2022-05-16 22:14 被阅读0次

作者:ChengTao

由于后台返回统一数据结构,比如 code, data, message; 使用过 Retrofit 的同学一定定义过类似 BaseResponse 这种类,但是 BaseResponse 的处理逻辑都大同小异, 每次都写着实让人很烦,有没有什么好的方式解决这一痛点呢?本文讲介绍一种优雅的方式 来解决这一问题。

背景

当我们打开 Retrofit 的官方文档时,官方的例子是这样的:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

而到了我们自己的项目,大多情况确实这样的:

public interface UserService {
  @GET("/users")
  Call<BaseResponse<List<User>>> getAllUsers();
}

而不同的公司,有不同的数据结构,不过都是大同小异,比如 code, data, message 或者 status, data, message, 每次都要写什么 code == 0 之类的代码,无聊不说,主要一点 技术含量都没有。。。

如果我们要是能把 BaseResponse 去掉,岂不美哉?就像下面的定义一样:

public interface UserService {
  @GET("/users")
  Call<List<User>> getAllUsers();
}

如果是 Kotlin 就更爽了,直接 suspend 方法走起。

interface UserService {
  @GET("/users")
  suspend fun getAllUsers() : List<User>
}

一、Convert.Factory 中被忽略的参数

public interface Converter<F, T> {
  abstract class Factory {
    // 参数 annotations 是 Method 上的注解
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) {
      return null;
    }
  }
}

public final class GsonConverterFactory extends Converter.Factory {
  // Retrofit 官方的 Converter.Factory 并没有使用 annotations 这个参数
  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] annotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }
}

从上面的代码不难看出,在实现 Convert.Factory 的时候, Retrofit 官方的实现并没有使用annotations 这个参数,而这个参数恰恰是移除 BaseResponse的关键。

二、实现方式

其实实现方案很简单,就是给 Method 再加一个自定义注解,然后对应实现一个Converter.Factory, 再这个 Converter.Factory 类中判断 Method 是否存在我们自定义的 注解,如果有就将数据进行转换,最后交给 Gson 进行解析。

项目地址:<u style="margin: 0px; padding: 0px; border: 0px;">Convex</u>

https://github.com/ParadiseHell/convex

实现细节

数据转换接口:

interface ConvexTransformer {
    // 将原始的 InputStream 转换成具体业务数据的 InputStream
    // 相当于获取 code, data, message 的数据流转换成只有 data 的数据流
    @Throws(IOException::class)
    fun transform(original: InputStream): InputStream
}

自定义注解:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Transformer(val value: KClass<out ConvexTransformer>)

// 有了这个自定义注解,我们就可以给 Method 添加这个注解了
interface UserService {
    @GET("xxx")
    @Transformer(XXXTransformer::class)
    fun xxxMethod() : Any
}

Convert.Factory 实现类:

class ConvexConverterFactory : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        // 获取 Method 上的 Transformer 注解
        // 从而确定使用那个具体的 ConvexTransformer 类
        return annotations
            .filterIsInstance<Transformer>()
            .firstOrNull()?.value
            ?.let { transformerClazz ->
                // 获取可以处理当前返回值的 Converter
                // ConvexConverterFactory 只做数据流转,具体数据序列化
                // 交给类似 GsonConverterFactory
                retrofit.nextResponseBodyConverter<Any>(
                    this, type, annotations
                )?.let { converter ->
                    // 如果有能处理返回值的 Converter 创建代理的 ConvexConveter
                    ConvexConverter<Any>(transformerClazz, converter)
                }
            }
    }
}

class ConvexConverter<T> constructor(
    private val transformerClazz: KClass<out ConvexTransformer>,
    private val candidateConverter: Converter<ResponseBody, *>
) : Converter<ResponseBody, T?> {

    @Suppress("UNCHECKED_CAST")
    @Throws(IOException::class)
    override fun convert(value: ResponseBody): T? {
        // 从 Convex 中获取 ConvexTransformer
        // Convex 是一个 ServiceRegistry, 用于存储 ConvexTransformer
        // 如果没有 ConvexTransformer 便会通过反射创建对应的 ConvexTransformer
        // 并保存进 Convex 中等待下次使用
        return Convex.getConvexTransformer(transformerClazz.java)
            .let { transformer ->
                // 转换数据流,这里就是将 code, data, message 数据流转换成 data 数据流
                transformer.transform(value.byteStream()).let { responseStream ->
                    ResponseBody.create(value.contentType(), responseStream.readBytes())
                }
            }?.let { responseBody ->
                // 使用具体 Converter 将 data 数据流转换成具体的 data
                candidateConverter.convert(responseBody) as T?
            }
    }
}

三、Convex 使用

添加依赖

dependencies {
    implementation "org.paradisehell.convex:convex:1.0.0"
}

实现 ConvexTransformer

private class TestConvexTransformer : ConvexTransformer {
    @Throws(IOException::class)
    override fun transform(original: InputStream): InputStream {
        TODO("Return the business data InputStream.")
    }
}

将 ConvexConverterFactory 加入 Retrofit 中

Retrofit.Builder()
    .baseUrl("https://test.com/")
    // ConvexConverterFactory 一定放在最前面
    .addConverterFactory(ConvexConverterFactory())
    .addConverterFactory(GsonConverterFactory.create())
    .build()

定义 Service 接口

interface XXXService {
    @GET("/users")
    // 使用 Transformer 注解,添加具体的 ConvexTransformer 实现类
    @Transformer(TestConvexTransformer::class)
    suspend fun getAllUsers() : List<User>
}

四、例子

首先非常感谢 <u style="margin: 0px; padding: 0px; border: 0px;">WanAndroid</u> 提供的免费 API。

https://www.wanandroid.com/blog/show/2

// 1\. 定义 BaseResponse
// 用于处理后台返回的数据进行反序列化,拿到最终的 data 数据
data class BaseResponse<T>(
    @SerializedName("errorCode")
    val errorCode: Int = 0,
    @SerializedName("errorMsg")
    val errorMsg: String? = null,
    @SerializedName("data")
    val data: T? = null
)

// 2\. 实现 ConvexTransformer
// 用户将后台返回的数据流转为具体的 data 数据
class WanAndroidConvexTransformer : ConvexTransformer {
    private val gson = Gson()

    @Throws(IOException::class)
    override fun transform(original: InputStream): InputStream {
        // 先反序列化为 BaseResponse
        val response = gson.fromJson<BaseResponse<JsonElement>>(
            original.reader(),
            object : TypeToken<BaseResponse<JsonElement>>() {
            }.type
        )
        // 判断 Response 是否成功
        // 成功则将 data 数据转换成 InputStream, 最后由具体 Converter 处理
        if (response.errorCode == 0 && response.data != null) {
            return response.data.toString().byteInputStream()
        }
        throw IOException(
            "errorCode : " + response.errorCode + " ; errorMsg : " + response.errorMsg
        )
    }
}

// 3\. 定义模型数据
data class Article(
    @SerializedName("id")
    val id: Int = 0,
    @SerializedName("link")
    val link: String? = null,
    @SerializedName("author")
    val author: String? = null,
    @SerializedName("superChapterName")
    val superChapterName: String? = null
)

// 4\. 定义 Service
interface WanAndroidService {
    @GET("/article/top/json")
    // 为改方法指定 ConvexTransformer, 这样就可以将 BaseResponse 转换成 data 了
    @Transformer(WanAndroidConvexTransformer::class)
    suspend fun getTopArticles(): List<Article>
}

// 5\. 定义 Retrofit
private val retrofit by lazy {
    Retrofit.Builder()
        .baseUrl("https://wanandroid.com/")
        // 一定将 ConvexConverterFactory 放在所有 Converter.Factory 的前面
        .addConverterFactory(ConvexConverterFactory())
        .addConverterFactory(GsonConverterFactory.create())
        .build()
}

private val wanAndroidService by lazy {
    retrofit.create(WanAndroidService::class.java)
}

// 6\. 执行 Service 方法
lifecycleScope.launch(Main) {
    val result = withContext(IO) {
        // 需要进行 try catch 操作, 因为请求失败会抛出异常
        runCatching {
            wanAndroidService.getTopArticles()
        }.onSuccess {
            return@withContext it
        }.onFailure {
            it.printStackTrace()
            return@withContext it
        }
    }
    // 打印数据
    // 成功打印数据列表,失败打印 Exeception
    printlin(result)
}

更多

更多的使用方法和更便捷的使用方法可以具体看 <u style="margin: 0px; padding: 0px; border: 0px;">Convex</u> 的 README。

https://github.com/ParadiseHell/convex

小结

完美,以后定义 Method 再也不用写 BaseResponse 了,BaseResponse终于可以统一处理了。

而且这种方案还支持多种不同的数据类型,因为不同的 Method 可以指定不同的ConvexTransformer, 而到具体的业务处理根本不用关系 BaseResponse是如何处理的, 因为具体的业务代码拿到的都是具体的 Data 数据。

不得不佩服 Retrofit 的设计,Converter.Factory 留出了 annotations 这个参数,可扩展 性简直强到爆,致敬 Square, Salute~~

这里也给大家推荐一套学习路线,并附有相关《Android开发核心知识点笔记》,相信可以给大家提供一些帮助,有需要的朋友们也可以下载下来随时查漏补缺。

如果需要的话,可以顺手帮我点赞评论一下,直接前往公号:Android开发之家,自行领取。

01.Android必备底层技术:

  • Java序列化:Serializable原理、Parcelable接口原理、Json、XML
  • 注解、泛型与反射:自定义注解、注解的使用、泛型擦除机制、泛型边界、Java方法与Arm指令、Method反射源码、invoke方法执行原理
  • 虚拟机:JVM垃圾回收器机制、JVM内存分配策略、Android虚拟机与JVM底层区别、虚拟机底层Odex本地指令缓存机制、虚拟机如何分别加载class与object、虚拟机类加载模型
  • 并发:Java线程本质讲解、线程原理、线程通信、UnSafe类、线程池
  • 编译时技术:OOP面向切面之AspectJ、字节码手术刀JavaSSit实战、字节码插桩技术(ASM)实战
  • 动态代理:动态代理实现原理、动态代理在虚拟机中运行时动态拼接Class字节码分析、ProxyGenerator生成字节码流程
  • 高级数据结构与算法:HashMap源码、ArrayList源码、排序算法
  • Java IO:Java IO体系、IO文件操作

02.Framework:

  • Binder:Linux内存基础、Binder四层源码分析、Binder机制、Binder进程通信原理
  • Handler:Loop消息泵机制、Message解析
  • Zygote:init进程与Zygote进程、Zygote启动流程、Socket通信模式、APP启动过程
  • AMS:ActivityThread源码分析、AMS与ActivityThread通信原理、Activity启动机制
  • PMS:PMS源码、APK安装过程分析、PMS对安装包的解析原理
  • WMS:PhoneWindow实例化流程、DecorView创建过程、ViewRootImpl渲染机制

03.Android常用组件:

  • Activty:Activity管理栈与Activity的启动模式、Activity生命周期源码分析
  • Fragment:Fragment生命周期深入详解、Fragment事务管理机制详解、性能优化相关方案
  • Service:Service启动模式分析、Service管理与通信方案、Service生命周期底层详解

04.高级UI:

  • UI绘制原理:setContentView()方法下到底做了什么、AppCompatActivity与Activity的区别、UI测量、布局、绘制的底层执行流程
  • 插件换肤:LayoutInflater加载布局分析、Android资源的加载机制、Resource与AssetManager
  • 事件分发机制原理:事件执行U形链与L形链、事件拦截原理
  • 属性动画:VSYNC刷新机制、ObjectAnimator与ValueAnimator源码讲解、Android属性动画:插值器与估值器
  • RecycleView:布局管理器LayoutManager详解、回收池设计思想、适配器模式原理
  • 高阶贝塞尔曲线

05.Jetpack:

  • Lifecycle:Lifecycle源码、Lifecycle高阶应用
  • ViewModel:ViewModel源码、ViewModel应用技巧
  • LiveData:LiveData源码
  • Navigation:Navigation源码
  • Room:Room源码、Room+LiveData监听数据库数据变更刷新页面原理
  • WorkManager内核
  • Pagging原理
  • DataBinding:单向绑定、双向绑定、如何与RecyclerView的配合使用、底层原理

06.性能优化:

  • 启动优化:系统启动原理、Trace工具分析启动卡顿、类重排机制、资源文件重排机制
  • 内存优化
  • UI渲染优化:UI层级规范及对UI加载的影响、UI卡顿原因及修复、UI绘制、布局、测量原因以及处理方案
  • 卡顿优化:造成卡顿的原因分析、内存抖动与GC回收、回收算法
  • 耗电优化
  • 崩溃优化:项目崩溃异常捕获、优雅的异常处理方案、如何避免异常弹框
  • 安全优化:APP加固实现(防反编译,dex加固)、https防抓包机制(数据传输加载,客户端服务器端双向加密校验)
  • 网络优化:serializable原理、parcelable接口原理、http与https原理详解、protbuffer网络IO详解、gzip压缩方案
  • 大图加载优化:Glide巨图加载机制原理分析、大图多级缓存实现方案
  • 多线程并发优化
  • 储存优化:Android文件系统-sdcard与内存存储、Shared Preference原理、MMAP内存映射
  • 安装包优化:shrinkResources去除无用资源、合理设置多语言、webp实现图片瘦身、合理配置armable-v7的so库、Lint检查工具实践

如果需要的话,可以顺手帮我点赞评论一下,直接前往公号:Android开发之家,自行领取。

07.音视频:

  • C/C++:数据类型、数组、内存布局、指针、函数、预处理器、结构体、共用体、容器、类型转换、异常、文件流操作、线程
  • H.265/H.265:音视频格式封装原理、编码原理、视频流H264的组装原理切片NAL单元、视频流H264码流分析、切片与宏快,运动矢量、信源编码器、高频滤波、帧间拆分与帧内预测、CTU,PU TU编码结构、DSP芯片解码流程、MediaPlayer与DSP芯片交互机制、投屏架构、MediaProjection与MeidiaCodec交互机制、H265码流交换
  • MediaCodec:dsp芯片、编解码器的生命周期、解码器中输入队列与解析队列设计思想、MediaCodec中平缓解码解析、MediaExtractor 多路复用、MediaMuxer合成器、MediaFormat格式
  • 音视频剪辑:视频剪辑、音频剪辑、音频合成、音谱显示、视频倒放
  • 音视频直播:硬编码、软编码、native实现rtmp推流、摄像头预览帧编码NV21转YUV、视频画面封装拼接Packet包、音频流数据拼接Packet包、RtmpDump实时同步发送音视频数据、MediaProjection、Medicodec编码H264码流、rtmp推流
  • OpenGL与音视频解码:OpenGL绘制流程、矩阵、Opencv详解、人脸识别效果实现
  • OpenGL特效:CPU与GPU运行机制详解、世界坐标,布局坐标,与FBO坐标系、图像镜像与旋转处理、人脸定位与关键点定位、大眼效果、贴纸效果、美颜效果
  • FFmpeg万能播放器:FFmpeg结构体、声音播放原理、Surface的渲染、像素绘制原理与对齐机制、音视频同步原理、视频播放器整体架构
  • Webrtc音视频通话:WebRtc服务端环境搭建与Webrtc编译、1v1视频通话实现方案、群聊视频通话实现思路、多对多视频会议实现、1V1音视频通话实现

08.开源框架原理:

  • Okhttp
  • Retrofit
  • RxJava
  • Glide
  • Hilt
  • Dagger2
  • EventBus
  • 组件化、插件化、热修复等

09.Gradle:

  • Groovy语法
  • Gradle Android插件配置
  • Gradle实践等

10.kotlin:

  • Kotlin语法
  • 扩展使用
  • 进阶使用
  • 实践等

11.Flutter:

  • Dart语法
  • UI
  • 进阶使用
  • 优化
  • 实践等

12.鸿蒙:

  • Ability组件
  • 分布式任务
  • 事件总线
  • 鸿蒙线程
  • UI自定义控件等

如果需要的话,可以顺手帮我点赞评论一下,直接前往公号:Android开发之家,自行领取。

Android路漫漫,共勉!

相关文章

  • Retrofit 妙用,拒绝重复代码!

    作者:ChengTao 由于后台返回统一数据结构,比如 code, data, message; 使用过 Retr...

  • 《网络(四):Retrofit2 + Rxjava2 +Okht

    在使用Retrofit/Okhttp时会发现有一部分代码是重复出现的,所以对重复的这部分代码进行简单封装,提高开发...

  • 非常规思维

    拒绝重复且很慢的人工,找程序员编简单代码,提高办事效率

  • 源码解析Retrofit

    1.Retrofit创建过程 首先 创建一个Retrofit 代码如下: retrofit是通过建造者模式构建出来...

  • Rxjava2+okhttp3+Retrofit2封装

    这里是Retrofit构造接口的方式,发现重复代码太多,我在网上找了个库,封装了一下,这是改造前部分接口,上次封装...

  • Android Retrofit Http加密(无证书加密)

    前提:本文基于Retrofit + okhttp(下面代码可以直接复制使用!) 一、Retrofit常规使用如下:...

  • Retrofit源码解析

    Retrofit在代码中的构建方式 根据构建方式,我们先来看一下Retrofit类源码 (1)Retrofit中的...

  • 拒绝重复

    拒绝重复 最近工作学习几乎填满了生活,今天读到了一篇心理学课文章,是关于焦虑的,不由得想到自己的焦虑,仔细...

  • Retrofit源码学习

    基本用法 上面代码主要创建了Retrofit对象,并且为Retrofit对象分别设置了OkhttpClient对象...

  • 设计原则

    1、拒绝重复代码/设计,重复的地方抽离作为独立函数或库 2、简单即是美,简单的东西意味这容易理解,容易修改。保持模...

网友评论

    本文标题:Retrofit 妙用,拒绝重复代码!

    本文链接:https://www.haomeiwen.com/subject/mlxgurtx.html