安卓进阶

更优雅的在 Kotlin 中封装 Retrofit (去掉 Ca

2022-03-06  本文已影响0人  minminaya

如果可以我想改名成《看完不会在 Kotlin 中封装 Retrofit就砍我》,嘿嘿........


Retrofit 是一个设计相当精良的框架,特别是其可扩展性上。官方提供的协程的使用方式和 API 实现在一些情况下不大优雅,本文主要是 bb 对其的相关扩展,让项目代码变得更傻瓜式和对 Retrofit 协程方式编写代码方式更加优雅。

基于下述思路封装的网络框架已经在线上持续稳定使用 1 年多了,适合各种牛(qi)逼(pa)的场景,本篇各个环节会涉及到奇奇怪怪的想法.....

Retrofit 对协程的支持

Retrofit 从 2.4 版本开始对协程的使用正式做了支持,可以让我们顺序式的编写代码,减少回调。巴拉巴拉一大堆,但是这里最重要的一点是让我们可以不用回调式的写代码,记住这一点,后面会重新提到。

Retrofit 协程的基本用法

下面省略 Retrofit.Builder 类相关的各种初始化操作(适配器,转换器等,默认认为有 Gson/Moshi 适配器做数据转换)

用法一

用法二

这样使用正常情况下是可以正常拿到数据的,log 也是正常输出的,程序也不会崩溃

制造一个异常

java.net.ConnectException: Failed to connect to /192.168.1.108:8888
        at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:297)
        at okhttp3.internal.connection.RealConnection.connectTunnel(RealConnection.kt:261)
        at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:201)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        ...
        省略若干行...大家一起想象下...

这样确实好了,但是我们一开始的目的不是想非回调式的写代码,并且尽量减少闭包,内部类的出现麽,如果每个接口都要用 catch 包住,那基本上算是没有解决根本问题。

还是得找办法解决这个问题【网络接口抛出异常需要外部使用 try catch 包裹】,那么 Retrofit 挂起式使用要怎么样操作才能真正的“优雅”呢,下面会一步一步的揭开谜题....


Retrofit 是怎么支持协程的

异常怎么产生的

不难思考,想解决异常抛出到业务代码的问题其实本质上是看这个异常从哪里来的,废话不多说,其实就是接口请求的某个环节产生的异常嘛

对 Retrofit 异步回调式使用熟悉的朋友肯定会想到这个用法:

                    AcRetrofit.get().notificationApi.loadSettings()
                        .enqueue(object : Callback<NotificationParams> {
                            override fun onResponse(
                                call: Call<NotificationParams>,
                                response: Response<NotificationParams>
                            ) {
                               //数据回调
                            }

                            override fun onFailure(call: Call<NotificationParams>, throwable: Throwable) {
                               //异常回调
                            }

                        })

onFailure 回调出来的 throwable 也就是同步式 【Retrofit 调用 execute()请求接口】 或者协程挂起式用法获取数据时抛出来的异常。

怎么支持的协程

解决问题

基本思路

从上面的流程分析来看,最终发现原因是因为网络请求执行遇到异常时,将异常通过 resumeWithException(e)恢复挂起导致,那能不能让它不执行 resumeWithException 呢,从源码上看只需要屏蔽 Retrofit Call 类的 void enqueue(Callback<T> callback)callback 实现或者避免其调用 resumeWithException方法,将所有的数据通过 onResponse 返回,并且对 response.body()= null的时候做容错即可。

刚好 Retrofit 的设计里面有一个实现可以自定义 CallAdapterFactory 来定制请求行为。这里我们可以通过自定义 CallAdapterFactory ,从而代理 Retrofit Call 类,进而控制 Callback 的接口调用来达到我们的最终目的。

关于网络层的一些封装分析

从网络层封装的角度来看,网络层单纯给业务方一个响应数据是不够的,因为业务方有时候想要知道更详细的详情数据来决定交互行为,这里列举一些业务层想要的数据:

数据包装类定义

Kotlin 里面try catch 的扩展函数 runCatching()如下

public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

自定义 CallAdapterFactory 来定制请求行为

从上面分析我们知道,我们只要代理 retrofit2.KotlinExtensions#await 中 Callback 接口被回调的方法始终为 onResponse() 和容错response.body() 为 null 的情况即可解决程序闪退和 try catch 的问题,当然因为我们上面重新定义了数据封装类为 HttpResult ,导致我们这里必须得自定义 CallAdapterFactory 才能干扰 Retrofit.Call 实现类的行为。

    @GET("xxxx/get-notification-settings")
    suspend fun loadSettings(): HttpResult<Repo<NotificationData>>
    
    /**
     * Repo 为统一的数据包装格式
     */
    public class Repo<T> {

          @Nullable
          @SerializedName("meta")
          private Meta meta;

          @Nullable
          @SerializedName("data")
          private T data;
          
          ...
    }

自定义 CallAdapterFactory 来定制请求行为(真的上货了)


Flow扩展

上面讲了 Retrofit 怎么样在协程中去掉 try catch 跟其他代码流式编程,但其实如果结合 Flow 来定义 API Method 调用会更加的优雅,那么怎么样快速切换到 Flow 接口呢

转换为 Flow 用法的分析

一开始我们转换为 HttpResult 时,我们的做法是在外部再包装一层 HttpResult,让 API 返回值变为 HttpResult<DataObject>,并且自定义 Factory 和 Adapter 与 Call 改变Retrofit 原始的请求行为。

现在我们要变为 Flow,就是再包装一层 Flow 咯,还有 Factory 那些也重新给自定义下。【本质上 Flow 的用法和 Rx 的用法差不多】

    @GET("setting/get-notification-settings")
    fun loadSettings(): Flow<HttpResult<NotificationData>>

思考


参考

上一篇 下一篇

猜你喜欢

热点阅读