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

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


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

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

Retrofit 对协程的支持

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

Retrofit 协程的基本用法

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

用法一

  • 1、定义 suspend 方法,返回值声明为 Response,可以清楚知道响应情况

                    @GET("xxxx/get-notification-settings")
                    suspend fun loadSettings(): Response>
    
    
  • 2、使用

                    lifecycleScope.launch {
                        val repoResponse: Response> =
                            AcRetrofit.get().notificationApi.loadSettings()
                        Log.d("okHttp", "data:" + repoResponse.body())
                    }
    

用法二

  • 1、定义 suspend 方法,返回值声明为 DataObject,只获取必要的数据

                    @GET("xxxx/get-notification-settings")
                    suspend fun loadSettings(): Repo
    
    
  • 2、使用

                    lifecycleScope.launch {
                        val repo: Repo =
                            AcRetrofit.get().notificationApi.loadSettings()
                        Log.d("okHttp", "data:$repo")
                    }
    

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

制造一个异常

  • 正常情况下上述用法都是没问题的,接下来我把手机网络断了,会发现程序闪退并且闪退栈的起始位置是 loadSettings() 这一行
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 下不就好了????


                    lifecycleScope.launch {
                        try {
                            val repoResponse: Response> =
                                AcRetrofit.get().notificationApi.loadSettings()
                            ALog.d("okHttp", "data:" + repoResponse.body())
                        } catch (e: Exception) {
                            ALog.e("okHttp", e)
                        }
                    }
    

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

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


Retrofit 是怎么支持协程的

异常怎么产生的

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

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

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

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

                        })

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

怎么支持的协程

  • 面对上面的问题,一开始应该是没有思路的,至于为什么要先看 Retrofit 对协程的支持怎么实现的,无非也是因为真的是没有太大的思路解决这个问题(如果不熟悉 Retrofit 源码实现的情况下),只能先去源码里面找解决方案

  • 相信大家应该清楚 Retrofit 的核心实现思路【动态代理反射 API 定义方法,将相关方法表示提取为参数塞给 okHttp 去请求】,如果不熟悉的话,2022 年了,Google 下 Retrofit 相关的流程源码分析,也会有很多优秀的文章可以浏览

  • 1、首先写一个接口请求

    • 定义 suspend 方法,返回值声明为 DataObject,只获取必要的数据,并且在协程里使用【这里只是一个简单的例子,不建议大家在 View 层使用lifecycleScope 来做接口请求】
                      @GET("xxxx/get-notification-settings")
                      suspend fun loadSettings(): Repo
                      
                      lifecycleScope.launch {
                          val repo: Repo =
                              AcRetrofit.get().notificationApi.loadSettings()
                          Log.d("okHttp", "data:$repo")
                      }                
      
      
  • 2、直接来到 Retrofit 动态代理的函数入口,InvocationHandler 类的 invoke 方法中打上断点

      public  T create(final Class service) {
        validateServiceInterface(service);
        return (T)
            Proxy.newProxyInstance(
                service.getClassLoader(),
                new Class[] {service},
                new InvocationHandler() {
                  private final Platform platform = Platform.get();
                  private final Object[] emptyArgs = new Object[0];
    
                  @Override
                  public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                      throws Throwable {
                    // If the method is a method from Object then defer to normal invocation.
                    if (method.getDeclaringClass() == Object.class) {
                      return method.invoke(this, args);
                    }
                    args = args != null ? args : emptyArgs;
                    return platform.isDefaultMethod(method)
                        ? platform.invokeDefaultMethod(method, service, proxy, args)
                        : loadServiceMethod(method).invoke(args);
                  }
                });
      }
    
  • 3、loadServiceMethod(method) 会被调用,可以看到是一个按需调用 ServiceMethod.parseAnnotations(this, method) 的逻辑

      ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
    
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = ServiceMethod.parseAnnotations(this, method);
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
    
  • 4、兜兜转转,最终 HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory) 会被调用,这个方法本质上是解析 API 定义方法的相关参数来构造 HTTP 服务使用的,这里会选取各种适配器,转换器来操作当前使用的 API 接口定义。

    • 这里没有定制的情况下,使用的 CallAdapterDefaultCallAdapterFactoryDefaultCallAdapterFactory 对协程的实现没有什么特别的帮助,内部主要实现是看API 接口方法定义有没有含有 SkipCallbackExecutor 注解,如果含有该注解就将回调返回使用定义的CallbackExecutor 线程池执行。可以展开折叠块看下具体定义,省略 n 多无关实现。
      final class DefaultCallAdapterFactory extends CallAdapter.Factory {
         ...
      
        @Override
        public @Nullable CallAdapter get(
            Type returnType, Annotation[] annotations, Retrofit retrofit) {
            ...
           
            final Executor executor =
            Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
                  ? null
                  : callbackExecutor;
      
          return new CallAdapter>() {
            @Override
            public Type responseType() {
              return responseType;
            }
      
            @Override
            public Call adapt(Call call) {
              return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
            }
          };
        }
      
        static final class ExecutorCallbackCall implements Call {
          final Executor callbackExecutor;
          final Call delegate;
      
          ExecutorCallbackCall(Executor callbackExecutor, Call delegate) {
            this.callbackExecutor = callbackExecutor;
            this.delegate = delegate;
          }
      
          @Override
          public void enqueue(final Callback callback) {
            Objects.requireNonNull(callback, "callback == null");
      
            delegate.enqueue(
                new Callback() {
                  @Override
                  public void onResponse(Call call, final Response response) {
                    callbackExecutor.execute(
                        () -> {
                          if (delegate.isCanceled()) {
                            // Emulate OkHttp's behavior of throwing/delivering an IOException on
                            // cancellation.
                            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                          } else {
                            callback.onResponse(ExecutorCallbackCall.this, response);
                          }
                        });
                  }
      
                  @Override
                  public void onFailure(Call call, final Throwable t) {
                    callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
                  }
                });
          }
        
           
        
       
      • 5、来到最关键的地方,Retrofit 内部要选中最终的 HttpServiceMethod,因为 API 方法定义,这里直接是 DataObject 返回值的,后面会返回 SuspendForBody 的实现

      • 6、SuspendForBody 的 adapt 方法,最终会看到 KotlinExtensions 的相关方法,这里我定义的 API 方法返回值是非空的,所以最终回调用 KotlinExtensions.await(call, continuation) 方法

            @Override
            protected Object adapt(Call call, Object[] args) {
              call = callAdapter.adapt(call);
        
              //noinspection unchecked Checked by reflection inside RequestFactory.
              Continuation continuation = (Continuation) args[args.length - 1];
        
              // Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes
              // invoke the supplied callback with an exception before the invoking stack frame can return.
              // Coroutines will intercept the subsequent invocation of the Continuation and throw the
              // exception synchronously. A Java Proxy cannot throw checked exceptions without them being
              // declared on the interface method. To avoid the synchronous checked exception being wrapped
              // in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will
              // force suspension to occur so that it can be instead delivered to the continuation to
              // bypass this restriction.
              try {
                return isNullable
                    ? KotlinExtensions.awaitNullable(call, continuation)
                    : KotlinExtensions.await(call, continuation);
              } catch (Exception e) {
                return KotlinExtensions.suspendAndThrow(e, continuation);
              }
            }
        
      • 7、await() 中,会直接调用 Call.enqueue() 方法发起请求,最终通过协程挂起恢复的操作 resumeWithException(e)resume(body) 将结果分发到协程发起端,也就是业务方使用端会受到结果(成功或者失败)

        suspend fun  Call.await(): T {
          return suspendCancellableCoroutine { continuation ->
            continuation.invokeOnCancellation {
              cancel()
            }
            enqueue(object : Callback {
              override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                  val body = response.body()
                  if (body == null) {
                    val invocation = call.request().tag(Invocation::class.java)!!
                    val method = invocation.method()
                    val e = KotlinNullPointerException("Response from " +
                        method.declaringClass.name +
                        '.' +
                        method.name +
                        " was null but response body type was declared as non-null")
                    continuation.resumeWithException(e)
                  } else {
                    continuation.resume(body)
                  }
                } else {
                  continuation.resumeWithException(HttpException(response))
                }
              }
        
              override fun onFailure(call: Call, t: Throwable) {
                continuation.resumeWithException(t)
              }
            })
          }
        }
        
        
        
        
        业务方调用======
        
        val repo: Repo = AcRetrofit.get().notificationApi.loadSettings()
        
        

        这里抛出异常其实就是因为 resumeWithException 被调用的原因

      解决问题

      基本思路

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

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

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

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

      • Header 的数据
      • 响应码:有时候会通过响应码来区分界面的错误弹窗、业务类型
      • 具体错误类型
      • 接口数据

      数据包装类定义

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

      public inline fun  T.runCatching(block: T.() -> R): Result {
          return try {
              Result.success(block())
          } catch (e: Throwable) {
              Result.failure(e)
          }
      }
      
      • 以看到它是将闭包函数 try catch 后包装成 Result 密封类返回

      • kotlin.Result 类是一个设计和扩展方法很全面的类,在 kotlin 里面各种特性的 API 之间起了至关重要的作用,其实现如下

        @JvmInline
        public value class Result @PublishedApi internal constructor(
            @PublishedApi
            internal val value: Any?
        ) : Serializable {
            // discovery
        
            /**
             * Returns `true` if this instance represents a successful outcome.
             * In this case [isFailure] returns `false`.
             */
            public val isSuccess: Boolean get() = value !is Failure
        
            /**
             * Returns `true` if this instance represents a failed outcome.
             * In this case [isSuccess] returns `false`.
             */
            public val isFailure: Boolean get() = value is Failure
        
            // value & exception retrieval
        
            /**
             * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or `null`
             * if it is [failure][Result.isFailure].
             *
             * This function is a shorthand for `getOrElse { null }` (see [getOrElse]) or
             * `fold(onSuccess = { it }, onFailure = { null })` (see [fold]).
             */
            @InlineOnly
            public inline fun getOrNull(): T? =
                when {
                    isFailure -> null
                    else -> value as T
                }
        
            /**
             * Returns the encapsulated [Throwable] exception if this instance represents [failure][isFailure] or `null`
             * if it is [success][isSuccess].
             *
             * This function is a shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]).
             */
            public fun exceptionOrNull(): Throwable? =
                when (value) {
                    is Failure -> value.exception
                    else -> null
                }
        
            /**
             * Returns a string `Success(v)` if this instance represents [success][Result.isSuccess]
             * where `v` is a string representation of the value or a string `Failure(x)` if
             * it is [failure][isFailure] where `x` is a string representation of the exception.
             */
            public override fun toString(): String =
                when (value) {
                    is Failure -> value.toString() // "Failure($exception)"
                    else -> "Success($value)"
                }
        
            // companion with constructors
        
            /**
             * Companion object for [Result] class that contains its constructor functions
             * [success] and [failure].
             */
            public companion object {
                /**
                 * Returns an instance that encapsulates the given [value] as successful value.
                 */
                @Suppress("INAPPLICABLE_JVM_NAME")
                @InlineOnly
                @JvmName("success")
                public inline fun  success(value: T): Result =
                    Result(value)
        
                /**
                 * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
                 */
                @Suppress("INAPPLICABLE_JVM_NAME")
                @InlineOnly
                @JvmName("failure")
                public inline fun  failure(exception: Throwable): Result =
                    Result(createFailure(exception))
            }
        
            internal class Failure(
                @JvmField
                val exception: Throwable
            ) : Serializable {
                override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception
                override fun hashCode(): Int = exception.hashCode()
                override fun toString(): String = "Failure($exception)"
            }
        }
        
      • 这里模仿 kotlin.Result,针对上述的封装需求分析,按需定义一个针对 Http 请求结果的 HttpResult 密封类,用于保存 Retrofit + OKHTTP处理过后的各种数据
        主要实现是 HttpResult 的四个密封子类 Success ApiError NetworkError UnknownError

        • HttpResult :密封类,保存了基础数据 Headers,T 是 API 方法定义的请求返回值类型
        sealed class HttpResult(open val responseHeader: Headers?) : Serializable {
        ...
        }
        
        • Success:接口请求成功,保存接口请求的 Header 原始数据和适配器解析后的接口 body 数据,T 是 API 方法定义的请求返回值类型
            /**
             * Success response with body
             */
            data class Success(val value: T, override val responseHeader: Headers?) :
                HttpResult(responseHeader) {
                override fun toString(): String {
                    return "Success($value)"
                }
        
                override fun exceptionOrNull(): Throwable? = null
            }
        
        • ApiError :通常是接口返回错误,其中 message 是我们接口定义通用结构中含有的固定类型,大家可以根据具体业务来定义
            /**
             * Failure response with body,通常是接口返回错误
             * @property code Int 错误码,默认是-1
             * @property message message 接口错误信息
             * @property throwable 原始错误类型
             * @constructor
             */
            data class ApiError(
                val code: Int = -1,
                val message: String? = null,
                val throwable: Throwable,
                override val responseHeader: Headers? = null,
            ) :
                HttpResult(responseHeader) {
                override fun toString(): String {
                    return "ApiError(message:$message,code:$code)"
                }
        
                override fun exceptionOrNull(): Throwable = throwable
            }
        
        • NetworkError:通常是断网了
            /**
             * For example, json parsing error
             */
            data class UnknownError(
                val throwable: Throwable?,
                override val responseHeader: Headers? = null,
            ) : HttpResult(responseHeader) {
                override fun toString(): String {
                    super.toString()
                    return "UnknownError(throwable:${throwable?.message})"
                }
        
                override fun exceptionOrNull(): Throwable? = throwable
            }
        
        • UnknownError :除上述错误外的其他错误,比如解析错误等,具体错误会通过 throwable 给出,并且按照接口请求的行为,可能 throwable 会为 null
            /**
             * For example, json parsing error
             */
            data class UnknownError(
                val throwable: Throwable?,
                override val responseHeader: Headers? = null,
            ) : HttpResult(responseHeader) {
                override fun toString(): String {
                    super.toString()
                    return "UnknownError(throwable:${throwable?.message})"
                }
        
                override fun exceptionOrNull(): Throwable? = throwable
            }
        
        • 综上所述,HttpResult整体实现如下
          sealed class HttpResult(open val responseHeader: Headers?) : Serializable {
              // discovery
          
              /**
               * Returns `true` if this instance represents a successful outcome.
               * In this case [isFailure] returns `false`.
               */
              val isSuccess: Boolean get() = this is Success
          
              /**
               * Returns `true` if this instance represents a failed outcome.
               * In this case [isSuccess] returns `false`.
               */
              val isFailure: Boolean get() = this !is Success
          
          
              /**
               * Success response with body
               */
              data class Success(val value: T, override val responseHeader: Headers?) :
                  HttpResult(responseHeader) {
                  override fun toString(): String {
                      return "Success($value)"
                  }
          
                  override fun exceptionOrNull(): Throwable? = null
              }
          
              /**
               * Failure response with body,通常是接口返回错误
               * @property code Int 错误码,默认是-1
               * @property message message 接口错误信息
               * @property throwable 原始错误类型
               * @constructor
               */
              data class ApiError(
                  val code: Int = -1,
                  val message: String? = null,
                  val throwable: Throwable,
                  override val responseHeader: Headers? = null,
              ) :
                  HttpResult(responseHeader) {
                  override fun toString(): String {
                      return "ApiError(message:$message,code:$code)"
                  }
          
                  override fun exceptionOrNull(): Throwable = throwable
              }
          
              /**
               * Network error 通常是断网了
               */
              data class NetworkError(
                  val error: Throwable,
                  override val responseHeader: Headers? = null,
              ) : HttpResult(responseHeader) {
                  override fun toString(): String {
                      return "NetworkError(error:${error.message})"
                  }
          
                  override fun exceptionOrNull(): Throwable = error
              }
          
              /**
               * For example, json parsing error
               */
              data class UnknownError(
                  val throwable: Throwable?,
                  override val responseHeader: Headers? = null,
              ) : HttpResult(responseHeader) {
                  override fun toString(): String {
                      super.toString()
                      return "UnknownError(throwable:${throwable?.message})"
                  }
          
                  override fun exceptionOrNull(): Throwable? = throwable
              }
          
              fun getOrNull(): T? = (this as? Success)?.value
          
              /**
               * Returns the encapsulated [Throwable] exception if this instance represents [failure][isFailure] or `null`
               * if it is [success][isSuccess].
               *
               * This function is a shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]).
               */
              open fun exceptionOrNull(): Throwable? = null
          
              companion object {
                  fun  success(result: T, responseHeader: Headers?): HttpResult =
                      Success(result, responseHeader)
          
                  fun apiError(
                      code: Int = -1,
                      message: String? = null,
                      throwable: Throwable,
                      responseHeader: Headers?
                  ): HttpResult =
                      ApiError(code, message, throwable, responseHeader)
          
                  fun  networkError(
                      error: Throwable, responseHeader: Headers?
                  ): HttpResult =
                      NetworkError(error, responseHeader)
          
                  fun  unknownError(
                      throwable: Throwable?, responseHeader: Headers?
                  ): HttpResult =
                      UnknownError(throwable, responseHeader)
              }
          
          }
          

      自定义 CallAdapterFactory 来定制请求行为

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

      • 自定义数据封装类为 HttpResult 后 API 方法定义会变为
          @GET("xxxx/get-notification-settings")
          suspend fun loadSettings(): HttpResult>
          
          /**
           * Repo 为统一的数据包装格式
           */
          public class Repo {
      
                @Nullable
                @SerializedName("meta")
                private Meta meta;
      
                @Nullable
                @SerializedName("data")
                private T data;
                
                ...
          }
      
      • 参考 DefaultCallAdapterFactoryRxJava2CallAdapterFactory的实现思路,我们定义一个 SuspendCallAdapterFactory 实现 CallAdapter.Factory 接口,如果对不熟悉 Factory 的自定义流程,还是可以打断点调试下 DefaultCallAdapterFactoryRxJava2CallAdapterFactory ,另外配合 SuspendCallAdapterFactory 的实现,我们还要实现一个 CallAdapter 用于 Call 和实际数据的转换。

        class SuspendCallAdapterFactory : CallAdapter.Factory() {
        
            override fun get(
                returnType: Type,
                annotations: Array,
                retrofit: Retrofit
            ): CallAdapter<*, *>? {
        
                // 返回一个 CallAdapter
            }
            
            
        class SuspendCallAdapter, R : Any>(
            private val successType: Type
          ) : CallAdapter>> {
        
            override fun responseType(): Type = successType
        
            override fun adapt(call: Call): Call> {
                return SuspendHttpCall(call, needCloseGeneralExceptionHandler)
            }
        }
        
      • 基本思路:重写 SuspendCallAdapterFactory ,在某个适合的条件下返回 SuspendCallAdapter 【代表使用该适配器来处理数据和 retrofit2.Call 类的转换】,下面看下 Retrofit 默认适配器是如何工作的【Rx 适配器有兴趣的小伙伴自己调试下,原理其实差不多的】

      • DefaultCallAdapterFactory 的流程分析

        • 首先看下 DefaultCallAdapterFactory 和其主要方法 get(Type returnType, Annotation[] annotations, Retrofit retrofit) 的实现【省略部分实现】
        final class DefaultCallAdapterFactory extends CallAdapter.Factory {
          private final @Nullable Executor callbackExecutor;
        
          DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
            this.callbackExecutor = callbackExecutor;
          }
        
          @Override
          public @Nullable CallAdapter get(
              Type returnType, Annotation[] annotations, Retrofit retrofit) {
            //返回值如果不是 Call 类,那么返回 null,表示不选择该 Factory 对应的 CallAdapter
            if (getRawType(returnType) != Call.class) {
              return null;
            }
            //方法返回值是否为参数化类型,如果不为参数化类型,那么会抛出一个异常,这里有固定的含义:也就是 Call 类必须包含范型
            if (!(returnType instanceof ParameterizedType)) {
              throw new IllegalArgumentException(
                  "Call return type must be parameterized as Call or Call");
            }
            //获取返回值类包含的第一个范型类型
            final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
        
            final Executor executor =
                Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
                    ? null
                    : callbackExecutor;
        
            return new CallAdapter>() {
              @Override
              public Type responseType() {
                //将 Call 类包含的第一个范型类型返回,代表当前这个 CallAdapter 需要转换为目标数据的类型,也就是说 Retrofit 最终会将数据解析为这个类型
                return responseType;
              }
        
              @Override
              public Call adapt(Call call) {
                // callbackExecutor 为 null 则不代理原始 Call,直接返回,否则将 callbackExecutor 和 原始 call 传递给 ExecutorCallbackCall 让其代理原始 call 的功能。
                return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
              }
            };
          }
         
           
        • DefaultCallAdapterFactory 构造函数接受一个 callbackExecutor ,其主要用于给后续判断 API 方法定义是否含有 SkipCallbackExecutor 注解,如果有 SkipCallbackExecutor 注解那么 callbackExecutor 为 null ,其会传递给 CallAdapter 返回原始的 Call 对象(相当于未代理 Call 类)

        • 其他情况上面的代码注释应该比较清楚了,另外这里和后面我们自定义的实现里面需要用到的比较关键的 ParameterizedType 类,它代表的是参数化类型,简单的说就是包含范型(包含<>括号)的类声明,比如 Dog

        • 有的小伙伴就说了,我有些时候并没有定义 API 的方法返回值是 Call 啊,为何这里还要判断 returnType 为 Call 呢?

          • 还是最原始的方法,如果不知道发生什么事(对源码不熟),那么直接在 DefaultCallAdapterFactory 的 get 函数里面打断点,看看是从哪里传递过来的,一直向前看,总会发现发生了什么(小声 bb)
          • 我们来到 retrofit2.HttpServiceMethod#parseAnnotations 的方法
            static  HttpServiceMethod parseAnnotations(
                Retrofit retrofit, Method method, RequestFactory requestFactory) {
              boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
              boolean continuationWantsResponse = false;
              boolean continuationBodyNullable = false;
          
              Annotation[] annotations = method.getAnnotations();
              Type adapterType;
              if (isKotlinSuspendFunction) {
                Type[] parameterTypes = method.getGenericParameterTypes();
                Type responseType =
                    Utils.getParameterLowerBound(
                        0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
                if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
                  // Unwrap the actual body type from Response.
                  responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
                  continuationWantsResponse = true;
                } else {
                  // TODO figure out if type is nullable or not
                  // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
                  // Find the entry for method
                  // Determine if return type is nullable or not
                }
          
                adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
                annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
              } else {
                adapterType = method.getGenericReturnType();
              }
          
              CallAdapter callAdapter =
                  createCallAdapter(retrofit, method, adapterType, annotations);
          

          关键就是这一行 adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
          responseType 本来为 API 方法定义的返回值类型,被 Call 包了一下,变成了新的参数化类型,这一招是相当的骚,具体实现在 Utils.ParameterizedTypeImpl 类中,有兴趣的朋友可以看看它是怎么做到包装范型的,大致思路是将实现了 ParameterizedType 接口,实现相关方法,重定义类型和范型的关系。

        • ExecutorCallbackCall 实现

            static final class ExecutorCallbackCall implements Call {
              final Executor callbackExecutor;
              final Call delegate;
          
              ExecutorCallbackCall(Executor callbackExecutor, Call delegate) {
                //接受线程池和待代理的 Call 对象
                this.callbackExecutor = callbackExecutor;
                this.delegate = delegate;
              }
          
              @Override
              public void enqueue(final Callback callback) {
                Objects.requireNonNull(callback, "callback == null");
          
                //代理发起异步 API 请求
                delegate.enqueue(
                    new Callback() {
                      @Override
                      public void onResponse(Call call, final Response response) {
                        //使用 callbackExecutor 回调结果
                        callbackExecutor.execute(
                            () -> {
                              if (delegate.isCanceled()) {
                                // Emulate OkHttp's behavior of throwing/delivering an IOException on
                                // cancellation.
                                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                              } else {
                                callback.onResponse(ExecutorCallbackCall.this, response);
                              }
                            });
                      }
          
                      @Override
                      public void onFailure(Call call, final Throwable t) {
                        callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
                      }
                    });
              }
          

          总体来说 ExecutorCallbackCall 只做了使用 callbackExecutor 回调结果的操作。我们可以模仿它实现自己的 Call 代理类

      • 这个时候,又有小伙伴就说了,你呀的啥时候自定义啊,我等不及啦,掏出货来啊,马上安排啦....


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

        • 1、上面说了那么多,总结下现在 API 定义变成什么样了,如下,原来的 Repo 被 HttpResult 接管了

              @GET("xxx/get-notification-settings")
              suspend fun loadSettings(): HttpResult>
              
              /**
               * Repo 为统一的数据包装格式
               */
              public class Repo {
          
                    @Nullable
                    @SerializedName("meta")
                    private Meta meta;
          
                    @Nullable
                    @SerializedName("data")
                    private T data;
                    
                    ...
              }
          
        • 2、自定义 SuspendCallAdapterFactorySupendCallAdatperSuspendHttpCall 暂时不列出

          class SuspendCallAdapterFactory : CallAdapter.Factory() {
          
              override fun get(
                  returnType: Type,
                  annotations: Array,
                  retrofit: Retrofit
              ): CallAdapter<*, *>? {
          
                  //第一个泛型就是HttpResult类,这种情况可能是api接口没有声明协程suspend符号,抛出异常提醒 
                  if (getRawType(returnType) == HttpResult::class.java) {
                      throw IllegalArgumentException("Method must be declare suspend, please check function declaration at API interface")
                  }
          
                  //协程挂起函数默认返回值是Call<*>,如果不满足该条件,那么返回null让retrofit选择其他家伙来Py
                  if (Call::class.java != getRawType(returnType)) {
                      return null
                  }
          
                  //检查Call内部的泛型是否包含了其他泛型
                  check(returnType is ParameterizedType) {
                      "return type must be HttpResult<*> or HttpResult for Call<*> check"
                  }
          
                  //获取Call类包裹的第一个泛型
                  val responseType = getParameterUpperBound(0, returnType)
          
                  //Call类包裹的第一个泛型不是HttpResult类,那么返回null,让retrofit选择其他 CallAdapter.Factory
                  if (getRawType(responseType) != HttpResult::class.java) {
                      return null
                  }
          
                  //确保HttpResult内部包的泛型其还包裹另外一层泛型,比如 HttpResult<*>
                  check(responseType is ParameterizedType) { "return type must be HttpResult<*> or HttpResult for HttpResult<*> check" }
          
                  //获取HttpResult类包裹的第一个泛型
                  val successBodyType = getParameterUpperBound(0, responseType)
          
                  return SuspendCallAdapter, Any>(
                      successBodyType
                  )
              }
              
              companion object {
          
                  @JvmStatic
                  fun create(): SuspendCallAdapterFactory {
                      return SuspendCallAdapterFactory()
                  }
              }
          }
              
              
          class SuspendCallAdapter, R : Any>(
              private val successType: Type,
          ) : CallAdapter>> {
          
              override fun responseType(): Type = successType
          
              override fun adapt(call: Call): Call> {
              
                  return SuspendHttpCall(call)
              }
          }
          
          • 上面注释已经很清晰了,下面提下比较重要的几点

          • 第一个就是判断泛型是否为HttpResult类,这种情况可能是api接口没有声明协程suspend符号,抛出异常提醒,至于为何有这个结论,大家可以看下 retrofit2.HttpServiceMethod#parseAnnotationsisKotlinSuspendFunction 为 false 的声明,returnType 直接取值为 API Method 定义的值,这种情况是不符合我们目前定义的协程这一套逻辑的,我们直接排除,返回 null 让 Retrofit 选择其它 Factory

          • 最后我们获取HttpResult类包裹的第一个泛型类型传递给了SupendCallAdatper 作为 responseType() 的返回值,不清楚这个环节的可以看看上面 DefaultCallAdapterFactory 的流程分析中有提到,responseType() 的返回值决定 Retrofit 最终解析的数据类型(反序列化)

          • 应该大家还记得 parseAnnotations 方法中,API Method 定义的返回值类型被 Call 包裹的操作,因为接口有一个通用的包装格式,也就是数据类型永远为 Repo 类似的情况,这里也用包裹范型的操作将 HttpResult> 换为 HttpResult 减少业务方要声明的范型层级

            • API 方法实现变为
                @GET("xxx/get-notification-settings")
                suspend fun loadSettings(): HttpResult>
                
                变为
                
                @GET("xxx/get-notification-settings")
                suspend fun loadSettings(): HttpResult
            
            • 修改 SuspendCallAdapterFactory 的get 方法,主要就是将HttpResult<>中的泛型<>包裹为Repo<*>,具体变化如下
            class SuspendCallAdapterFactory : CallAdapter.Factory() {
            
                override fun get(
                    returnType: Type,
                    annotations: Array,
                    retrofit: Retrofit
                ): CallAdapter<*, *>? {
            
                    //第一个泛型就是HttpResult类,这种情况可能是api接口没有声明协程suspend符号,抛出异常提醒
                    if (getRawType(returnType) == HttpResult::class.java) {
                        throw IllegalArgumentException("Method must be declare suspend, please check function declaration at API interface")
                    }
            
                    //协程挂起函数默认返回值是Call<*>,如果不满足该条件,那么返回null让retrofit选择其他家伙来Py
                    if (Call::class.java != getRawType(returnType)) {
                        return null
                    }
            
                    //检查Call内部的泛型是否包含了其他泛型
                    check(returnType is ParameterizedType) {
                        "return type must be HttpResult<*> or HttpResult for Call<*> check"
                    }
            
                    //获取Call类包裹的第一个泛型
                    val responseType = getParameterUpperBound(0, returnType)
            
                    //Call类包裹的第一个泛型不是HttpResult类,那么返回null,让retrofit选择其他 CallAdapter.Factory
                    if (getRawType(responseType) != HttpResult::class.java) {
                        return null
                    }
            
                    //确保HttpResult内部包的泛型其还包裹另外一层泛型,比如 HttpResult<*>
                    check(responseType is ParameterizedType) { "return type must be HttpResult<*> or HttpResult for HttpResult<*> check" }
            
                    //获取HttpResult类包裹的第一个泛型
                    val successBodyType = getParameterUpperBound(0, responseType)
            
                    //整块注释,下面将动态配置泛型变为Repo<*>,不需要再手动声明为Repo<*>泛型================改动点
            //        check(Repo::class.java == getRawType(successBodyType)) {
                    //如果待处理的类型不是Repo类,那么报异常
            //            "return type must be HttpResult> or HttpResult>> for Repo<*> check"
            //        }
            
                    //将HttpResult<*>中的泛型<*>包裹为Repo<*>,方便解析
                    val repoParameterizedType =
                        Utils.ParameterizedTypeImpl(null, Repo::class.java, successBodyType)
            
                    return SuspendCallAdapter, Any>(
                        repoParameterizedType
                    )
                }
            
        • 3、自定义 SuspendHttpCall

          internal class SuspendHttpCall, R : Any>(
              private val delegate: Call,
          ) : Call> {
          
              override fun enqueue(callback: Callback>) {
          
                  return delegate.enqueue(object : Callback {
                      override fun onResponse(call: Call, response: Response) {
                          val body = response.body()
                          var httpResult: HttpResult? = null
          
                          //================================================
                          //================================================
                          //===================1、响应成功===================
                          //================================================
                          //================================================
                          if (response.isSuccessful) {
                              body?.data?.apply {
                                  //Repo.data不为空
                                  httpResult = HttpResult.Success(this, response.headers())
                              } ?: run {
                                  //响应body是null或者Repo的data为空的时候
                                  httpResult = HttpResult.UnknownError(
                                      IllegalArgumentException("response data is invalid"),
                                      null
                                  )
                              }
          
                              callback.onResponse(
                                  this@SuspendHttpCall,
                                  Response.success(httpResult)
                              )
                              return
                          }
          
                          //================================================
                          //================================================
                          //===================2、响应失败===================
                          //================================================
                          //================================================
                          onFailure(call, HttpException(response))
                      }
          
                      override fun onFailure(call: Call, throwable: Throwable) {
          
                          var meta: Meta? = null
                          var statusCode = -1
                          if (isHttpException(throwable)) {
                              val exception = throwable as HttpException
                              //从 exception 中解析 Repo.Meta 数据
                              meta = parseMetaData(exception)
                              statusCode = exception.code()
                          }
          
                          val result: HttpResult = generateHttpResult(throwable, meta, statusCode)
                          callback.onRespo nse(this@SuspendHttpCall, Response.success(result))
                      }
                  })
              }
          
              override fun isExecuted() = delegate.isExecuted
          
              override fun clone() = SuspendHttpCall(
                  delegate.clone(),
              )
          
              override fun isCanceled() = delegate.isCanceled
          
              override fun cancel() = delegate.cancel()
          
              override fun execute(): Response> {
                  throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")
              }
          
              override fun request(): Request = delegate.request()
          
              override fun timeout(): Timeout = delegate.timeout()
          
          }
          
          fun generateHttpResult(
                  t: Throwable,
                  meta: Meta?,
                  statusCode: Int
              ): HttpResult {
                  if (isApiError(t, meta, statusCode)) {
                      return HttpResult.ApiError(
                          meta?.code ?: statusCode,
                          meta?.message,
                          t,
                          parseHeader(t),
                      )
                  }
                  if (isNonNetwork(t)) {
                      return HttpResult.NetworkError(t, parseHeader(t))
                  }
                  return HttpResult.UnknownError(t, parseHeader(t))
              }
              
          fun generateHttpResult(
                  t: Throwable,
                  meta: Meta?,
                  statusCode: Int
              ): HttpResult {
                  if (isApiError(t, meta, statusCode)) {
                      return HttpResult.ApiError(
                          meta?.code ?: statusCode,
                          meta?.message,
                          t,
                          parseHeader(t),
                      )
                  }
                  if (isNonNetwork(t)) {
                      return HttpResult.NetworkError(t, parseHeader(t))
                  }
                  return HttpResult.UnknownError(t, parseHeader(t))
              }
          
          
          • 模仿 ExecutorCallbackCall 的实现,因为我们这里只用到异步请求 API,我们只需要实现 enqueue 方法接口,回到我们最初的目的,我们代理 Retrofit.Call 是为了容错 response.body()=null 和 callback.onFailure,这里实现上我们要做一些处理,结合 HttpResult 的设计,将四种密封类职能分别包装,最后用 callback.onResponse() 将结果返回,防止业务方抛出异常。
        • 4、最后我们在 Retrofit.Builder 中引入 Factory 即可

                          new Retrofit.Builder()
                                  .baseUrl(asBase)
                                  .addCallAdapterFactory(SuspendCallAdapterFactory.create())
          
        • 5、使用方法,由于使用了密封类,用 when 来展开,编译器会帮忙补全,或者可以直接写成 Template 模版,使用代码补全填充

                       viewmodelScope.launch {
                              val httpResult =
                                  AcRetrofit.get().notificationApi.loadSettings()
                              when (httpResult) {
                                  is HttpResult.ApiError -> {
                                      //API 异常,比如 403,503,等等
                                      //httpResult.code
                                      //httpResult.message
                                      //httpResult.headers
                                  }
                                  is HttpResult.NetworkError -> {
                                      //网络问题
                                  }
                                  is HttpResult.Success -> {
                                      val notificationData = httpResult.value
                                  }
                                  is HttpResult.UnknownError -> {
                                      //其他异常
                                      //httpResult.throwable
                                  }
                              }
                          }
          
        • 6、真的没了,封装就告一段落了



        Flow扩展

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

        转换为 Flow 用法的分析

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

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

        • 1、就是 API 方法定义会变为如下,要注意 Flow 类型不需要挂起函数声明 ,这里只要将返回值修改即可
            @GET("setting/get-notification-settings")
            fun loadSettings(): Flow>
        
        • 2、定义 FlowCallAdapterFactory,相关解释可以看下注释,主要是判断第一个范型是 Flow 类型

          import com.aftership.framework.http.retrofits.Repo
          import com.aftership.framework.http.retrofits.suspend.HttpResult
          import com.aftership.framework.http.retrofits.suspend.Utils
          import kotlinx.coroutines.flow.Flow
          import retrofit2.CallAdapter
          import retrofit2.Retrofit
          import java.lang.reflect.ParameterizedType
          import java.lang.reflect.Type
          
          /**
           *
           * Flow 请求方式 Retrofit CallAdapter,主要是将请求转换为 Flow>,并且包裹请求的所有结果填充到 Flow 中返回
           *
           * @author: minminaya
           * @email: [email protected]
           * @date: 2020/8/30 14:59
           */
          class FlowCallAdapterFactory : CallAdapter.Factory() {
          
              /**
               *
               * @param returnType Type Flow>
               * @param annotations Array
               * @param retrofit Retrofit
               * @return CallAdapter<*, *>?
               */
              override fun get(
                  returnType: Type,
                  annotations: Array,
                  retrofit: Retrofit
              ): CallAdapter<*, *>? {
          
                  val firstGenericType = getRawType(returnType)
          
                  //第一个泛型不是 Flow 类,让 retrofit 选择其它 Factory
                  if (firstGenericType != Flow::class.java) {
                      return null
                  }
          
                  //检查 Flow 内部的泛型是否包含了其他泛型
                  check(returnType is ParameterizedType) {
                      "return type must be Flow> or Flow>"
                  }
          
                  //获取 Flow 类包裹的泛型(第二个范型)
                  val secondGenericType = getParameterUpperBound(0, returnType)
          
                  //第二个范型不是 HttpResult 类,那么报错
                  check(secondGenericType != HttpResult::class.java) {
                      "Flow generic type must be HttpResult"
                  }
          
                  //确保 HttpResult 内部包的泛型其还包裹另外一层泛型,比如 HttpResult<*> or Flow>
                  check(secondGenericType is ParameterizedType) { "HttpResult generic type must be not null" }
          
                  //获取 HttpResult<*> 类包裹的泛型(数据)
                  val thirdGenericType = getParameterUpperBound(0, secondGenericType)
          
                  //将 HttpResult<*> 中的泛型 <*> 包裹为 Repo<*>,方便解析
                  val repoParameterizedType =
                      Utils.ParameterizedTypeImpl(null, Repo::class.java, thirdGenericType)
          
                  return FlowCallAdapter, Any>(
                      repoParameterizedType,
                  )
              }
          
              companion object {
          
                  @JvmStatic
                  fun create(): FlowCallAdapterFactory {
                      return FlowCallAdapterFactory()
                  }
              }
          }
          
          
        • 3、实现 FlowCallAdapter ,代理 Retrofit.Call 的行为,这里很关键的是 callbackFlow{} 的使用,它是异步回调转同步使用的魔法,类似 suspendCancellableCoroutine{} 的协程挂起操作

          class FlowCallAdapter, R : Any>(
              private val successType: Type,
          ) : CallAdapter>> {
          
              override fun responseType(): Type = successType
          
              @OptIn(ExperimentalCoroutinesApi::class)
              override fun adapt(call: Call): Flow> {
                  return callbackFlow {
                      //异步转同步,为了保证项目内都使用 HttpResult 做响应数据包装类,这里还是复用 SuspendHttpCall
                      val suspendHttpCall =
                          SuspendHttpCall(call).also {
                              it.enqueue(
                                  object : Callback> {
                                      override fun onResponse(
                                          call: Call>,
                                          response: Response>
                                      ) {
                                          response.body()?.let{ httpResult->
                                              trySend(httpResult)
                                          }
                                      }
          
                                      override fun onFailure(call: Call>, t: Throwable) {
                                          //SuspendHttpCall 不会回调 onFailure(),这里不用做实现
                                          //do nothing here
                                      }
          
                                  })
                          }
                      //必须声明关闭 call,类似 suspendCancellableCoroutine 的使用 
                      awaitClose {
                          suspendHttpCall.cancel()
                      }
                  }
              }
          }
          
          

          中途代理 SuspendHttpCallenqueue 操作后,发射 httpResult 的结果给 callbackFlow 完成 flow 的发射端调用

        • 4、引入 Factory

                          new Retrofit.Builder()
                                  .baseUrl(asBase)
                                  .addCallAdapterFactory(FlowCallAdapterFactory.create())
          
        • 5、使用 Flow API 请求,类似上面协程的调用,这里还是会包装为 HttpResult

          val settingsFlow = AcRetrofit.get().notificationAPI.loadSettings()
            settingFlow
                    .collect {
                            when (it) {
                                  is HttpResult.ApiError -> {
                                      //API 异常,比如 403,503,等等
                                      //httpResult.code
                                      //httpResult.message
                                      //httpResult.headers
                                  }
                                  is HttpResult.NetworkError -> {
                                      //网络问题
                                  }
                                  is HttpResult.Success -> {
                                      val notificationData = httpResult.value
                                  }
                                  is HttpResult.UnknownError -> {
                                      //其他异常
                                      //httpResult.throwable
                                  }
                              }
                            }
          

        思考

        • 不要遇到问题就 Google,更好的做法是先看看框架怎么实现的
        • 源码里面有黄金书,包裹范型的思路就是在 Retrofit 里面发现从而优化 API Method 的调用
        • 不要单纯的看源码和看大佬们的源码分析,要自己写 Demo 断点调试源码,看看数据是怎么流转和产生的

        参考

        • Retrofit
        • Flow
        • Channels
        • callbackFlow

        你可能感兴趣的:(更优雅的在 Kotlin 中封装 Retrofit (去掉 Catch))