更優雅的在 Kotlin 中封裝 Retrofit (去掉 Catch)

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


Retrofit 是一個設計相當精良的框架,特別是其可擴展性上。官方提供的協程的使用方式和 API 實現在一些情況下不大優雅,本文主要是 bb 對其的相關擴展,讓項目代碼變得更傻瓜式和對 Retrofit 協程方式編寫代碼方式更加優雅。

基于下述思路封裝的網絡框架已經在線上持續穩定使用 1 年多了,適合各種牛(qi)逼(pa)的場景,本篇各個環節會涉及到奇奇怪怪的想法.....

Retrofit 對協程的支持

Retrofit 從 2.4 版本開始對協程的使用正式做了支持,可以讓我們順序式的編寫代碼,減少回調。巴拉巴拉一大堆,但是這里最重要的一點是讓我們可以不用回調式的寫代碼,記住這一點,后面會重新提到。

Retrofit 協程的基本用法

下面省略 Retrofit.Builder 類相關的各種初始化操作(適配器,轉換器等,默認認為有 Gson/Moshi 適配器做數據轉換)

用法一

  • 1、定義 suspend 方法,返回值聲明為 Response<DataObject>,可以清楚知道響應情況

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

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

用法二

  • 1、定義 suspend 方法,返回值聲明為 DataObject,只獲取必要的數據

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

                    lifecycleScope.launch {
                        val repo: Repo<NotificationData> =
                            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<Repo<NotificationParams>> =
                                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<NotificationParams> {
                            override fun onResponse(
                                call: Call<NotificationParams>,
                                response: Response<NotificationParams>
                            ) {
                               //數據回調
                            }

                            override fun onFailure(call: Call<NotificationParams>, 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<NotificationData>
                      
                      lifecycleScope.launch {
                          val repo: Repo<NotificationData> =
                              AcRetrofit.get().notificationApi.loadSettings()
                          Log.d("okHttp", "data:$repo")
                      }                
      
      
  • 2、直接來到 Retrofit 動態代理的函數入口,InvocationHandler 類的 invoke 方法中打上斷點

      public <T> T create(final Class<T> 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<Object, Call<?>>() {
            @Override
            public Type responseType() {
              return responseType;
            }
      
            @Override
            public Call<Object> adapt(Call<Object> call) {
              return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
            }
          };
        }
      
        static final class ExecutorCallbackCall<T> implements Call<T> {
          final Executor callbackExecutor;
          final Call<T> delegate;
      
          ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
            this.callbackExecutor = callbackExecutor;
            this.delegate = delegate;
          }
      
          @Override
          public void enqueue(final Callback<T> callback) {
            Objects.requireNonNull(callback, "callback == null");
      
            delegate.enqueue(
                new Callback<T>() {
                  @Override
                  public void onResponse(Call<T> call, final Response<T> 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<T> 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<ResponseT> call, Object[] args) {
          call = callAdapter.adapt(call);
    
          //noinspection unchecked Checked by reflection inside RequestFactory.
          Continuation<ResponseT> continuation = (Continuation<ResponseT>) 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 <T : Any> Call<T>.await(): T {
      return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
          cancel()
        }
        enqueue(object : Callback<T> {
          override fun onResponse(call: Call<T>, response: Response<T>) {
            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>, t: Throwable) {
            continuation.resumeWithException(t)
          }
        })
      }
    }
    
    
    
    
    業務方調用======
    
    val repo: Repo<NotificationData> = AcRetrofit.get().notificationApi.loadSettings()
    
    

    這里拋出異常其實就是因為 resumeWithException 被調用的原因

解決問題

基本思路

從上面的流程分析來看,最終發現原因是因為網絡請求執行遇到異常時,將異常通過 resumeWithException(e)恢復掛起導致,那能不能讓它不執行 resumeWithException 呢,從源碼上看只需要屏蔽 Retrofit Call 類的 void enqueue(Callback<T> callback)callback 實現或者避免其調用 resumeWithException方法,將所有的數據通過 onResponse 返回,并且對 response.body()= null的時候做容錯即可。

剛好 Retrofit 的設計里面有一個實現可以自定義 CallAdapterFactory 來定制請求行為。這里我們可以通過自定義 CallAdapterFactory ,從而代理 Retrofit Call 類,進而控制 Callback 的接口調用來達到我們的最終目的。

關于網絡層的一些封裝分析

從網絡層封裝的角度來看,網絡層單純給業務方一個響應數據是不夠的,因為業務方有時候想要知道更詳細的詳情數據來決定交互行為,這里列舉一些業務層想要的數據:

  • Header 的數據
  • 響應碼:有時候會通過響應碼來區分界面的錯誤彈窗、業務類型
  • 具體錯誤類型
  • 接口數據

數據包裝類定義

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)
    }
}
  • 以看到它是將閉包函數 try catch 后包裝成 Result 密封類返回

  • kotlin.Result 類是一個設計和擴展方法很全面的類,在 kotlin 里面各種特性的 API 之間起了至關重要的作用,其實現如下

    @JvmInline
    public value class Result<out T> @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 <T> success(value: T): Result<T> =
                Result(value)
    
            /**
             * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
             */
            @Suppress("INAPPLICABLE_JVM_NAME")
            @InlineOnly
            @JvmName("failure")
            public inline fun <T> failure(exception: Throwable): Result<T> =
                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<out T>(open val responseHeader: Headers?) : Serializable {
    ...
    }
    
    • Success:接口請求成功,保存接口請求的 Header 原始數據和適配器解析后的接口 body 數據,T 是 API 方法定義的請求返回值類型
        /**
         * Success response with body
         */
        data class Success<T : Any>(val value: T, override val responseHeader: Headers?) :
            HttpResult<T>(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<Nothing>(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<Nothing>(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<Nothing>(responseHeader) {
            override fun toString(): String {
                super.toString()
                return "UnknownError(throwable:${throwable?.message})"
            }
    
            override fun exceptionOrNull(): Throwable? = throwable
        }
    
    • 綜上所述,HttpResult整體實現如下
      sealed class HttpResult<out T>(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<T : Any>(val value: T, override val responseHeader: Headers?) :
              HttpResult<T>(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<Nothing>(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<Nothing>(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<Nothing>(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 <T : Any> success(result: T, responseHeader: Headers?): HttpResult<T> =
                  Success(result, responseHeader)
      
              fun apiError(
                  code: Int = -1,
                  message: String? = null,
                  throwable: Throwable,
                  responseHeader: Headers?
              ): HttpResult<Nothing> =
                  ApiError(code, message, throwable, responseHeader)
      
              fun <Nothing> networkError(
                  error: Throwable, responseHeader: Headers?
              ): HttpResult<Nothing> =
                  NetworkError(error, responseHeader)
      
              fun <Nothing> unknownError(
                  throwable: Throwable?, responseHeader: Headers?
              ): HttpResult<Nothing> =
                  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<NotificationData>>
    
    /**
     * Repo 為統一的數據包裝格式
     */
    public class Repo<T> {

          @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<Annotation>,
            retrofit: Retrofit
        ): CallAdapter<*, *>? {
    
            // 返回一個 CallAdapter
        }
        
        
    class SuspendCallAdapter<T : Repo<R>, R : Any>(
        private val successType: Type
      ) : CallAdapter<T, Call<HttpResult<R>>> {
    
        override fun responseType(): Type = successType
    
        override fun adapt(call: Call<T>): Call<HttpResult<R>> {
            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<Foo> or Call<? extends Foo>");
        }
        //獲取返回值類包含的第一個范型類型
        final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
    
        final Executor executor =
            Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
                ? null
                : callbackExecutor;
    
        return new CallAdapter<Object, Call<?>>() {
          @Override
          public Type responseType() {
            //將 Call 類包含的第一個范型類型返回,代表當前這個 CallAdapter 需要轉換為目標數據的類型,也就是說 Retrofit 最終會將數據解析為這個類型
            return responseType;
          }
    
          @Override
          public Call<Object> adapt(Call<Object> 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<HotDog>

    • 有的小伙伴就說了,我有些時候并沒有定義 API 的方法返回值是 Call<xxx> 啊,為何這里還要判斷 returnType 為 Call<xxx> 呢?

      • 還是最原始的方法,如果不知道發生什么事(對源碼不熟),那么直接在 DefaultCallAdapterFactory 的 get 函數里面打斷點,看看是從哪里傳遞過來的,一直向前看,總會發現發生了什么(小聲 bb)
      • 我們來到 retrofit2.HttpServiceMethod#parseAnnotations 的方法
        static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> 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<T>.
              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<ResponseT, ReturnT> 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<T> implements Call<T> {
          final Executor callbackExecutor;
          final Call<T> delegate;
      
          ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
            //接受線程池和待代理的 Call 對象
            this.callbackExecutor = callbackExecutor;
            this.delegate = delegate;
          }
      
          @Override
          public void enqueue(final Callback<T> callback) {
            Objects.requireNonNull(callback, "callback == null");
      
            //代理發起異步 API 請求
            delegate.enqueue(
                new Callback<T>() {
                  @Override
                  public void onResponse(Call<T> call, final Response<T> 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<T> call, final Throwable t) {
                    callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
                  }
                });
          }
      

      總體來說 ExecutorCallbackCall 只做了使用 callbackExecutor 回調結果的操作。我們可以模仿它實現自己的 Call 代理類

  • 這個時候,又有小伙伴就說了,你呀的啥時候自定義啊,我等不及啦,掏出貨來啊,馬上安排啦....


自定義 CallAdapterFactory 來定制請求行為(真的上貨了)

  • 1、上面說了那么多,總結下現在 API 定義變成什么樣了,如下,原來的 Repo<NotificationData> 被 HttpResult 接管了

        @GET("xxx/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;
              
              ...
        }
    
  • 2、自定義 SuspendCallAdapterFactorySupendCallAdatperSuspendHttpCall 暫時不列出

    class SuspendCallAdapterFactory : CallAdapter.Factory() {
    
        override fun get(
            returnType: Type,
            annotations: Array<Annotation>,
            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<out *> 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<out *> for HttpResult<*> check" }
    
            //獲取HttpResult類包裹的第一個泛型
            val successBodyType = getParameterUpperBound(0, responseType)
    
            return SuspendCallAdapter<Repo<Any>, Any>(
                successBodyType
            )
        }
        
        companion object {
    
            @JvmStatic
            fun create(): SuspendCallAdapterFactory {
                return SuspendCallAdapterFactory()
            }
        }
    }
        
        
    class SuspendCallAdapter<T : Repo<R>, R : Any>(
        private val successType: Type,
    ) : CallAdapter<T, Call<HttpResult<R>>> {
    
        override fun responseType(): Type = successType
    
        override fun adapt(call: Call<T>): Call<HttpResult<R>> {
        
            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<DataObject> 類似的情況,這里也用包裹范型的操作將 HttpResult<Repo<NotificationData>> 換為 HttpResult<NotificationData> 減少業務方要聲明的范型層級

      • API 方法實現變為
          @GET("xxx/get-notification-settings")
          suspend fun loadSettings(): HttpResult<Repo<NotificationData>>
          
          變為
          
          @GET("xxx/get-notification-settings")
          suspend fun loadSettings(): HttpResult<NotificationData>
      
      • 修改 SuspendCallAdapterFactory 的get 方法,主要就是將HttpResult<>中的泛型<>包裹為Repo<*>,具體變化如下
      class SuspendCallAdapterFactory : CallAdapter.Factory() {
      
          override fun get(
              returnType: Type,
              annotations: Array<Annotation>,
              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<out *> 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<out *> for HttpResult<*> check" }
      
              //獲取HttpResult類包裹的第一個泛型
              val successBodyType = getParameterUpperBound(0, responseType)
      
              //整塊注釋,下面將動態配置泛型變為Repo<*>,不需要再手動聲明為Repo<*>泛型================改動點
      //        check(Repo::class.java == getRawType(successBodyType)) {
              //如果待處理的類型不是Repo類,那么報異常
      //            "return type must be HttpResult<Repo<*>> or HttpResult<out Repo<*>>> for Repo<*> check"
      //        }
      
              //將HttpResult<*>中的泛型<*>包裹為Repo<*>,方便解析
              val repoParameterizedType =
                  Utils.ParameterizedTypeImpl(null, Repo::class.java, successBodyType)
      
              return SuspendCallAdapter<Repo<Any>, Any>(
                  repoParameterizedType
              )
          }
      
  • 3、自定義 SuspendHttpCall

    internal class SuspendHttpCall<T : Repo<R>, R : Any>(
        private val delegate: Call<T>,
    ) : Call<HttpResult<R>> {
    
        override fun enqueue(callback: Callback<HttpResult<R>>) {
    
            return delegate.enqueue(object : Callback<T> {
                override fun onResponse(call: Call<T>, response: Response<T>) {
                    val body = response.body()
                    var httpResult: HttpResult<R>? = 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<T>, 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<R> = 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<HttpResult<R>> {
            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<Nothing> {
            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<Nothing> {
            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<DataObject>,并且自定義 Factory 和 Adapter 與 Call 改變Retrofit 原始的請求行為。

現在我們要變為 Flow,就是再包裝一層 Flow 咯,還有 Factory 那些也重新給自定義下。【本質上 Flow 的用法和 Rx 的用法差不多】

  • 1、就是 API 方法定義會變為如下,要注意 Flow 類型不需要掛起函數聲明 ,這里只要將返回值修改即可
    @GET("setting/get-notification-settings")
    fun loadSettings(): Flow<HttpResult<NotificationData>>
  • 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<HttpResult<*>>,并且包裹請求的所有結果填充到 Flow<HttpResult> 中返回
     *
     * @author: minminaya
     * @email: minminaya@gmail.com
     * @date: 2020/8/30 14:59
     */
    class FlowCallAdapterFactory : CallAdapter.Factory() {
    
        /**
         *
         * @param returnType Type Flow<HttpResult<Data>>
         * @param annotations Array<Annotation>
         * @param retrofit Retrofit
         * @return CallAdapter<*, *>?
         */
        override fun get(
            returnType: Type,
            annotations: Array<Annotation>,
            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<HttpResult<*>> or Flow<HttpResult<out *>>"
            }
    
            //獲取 Flow 類包裹的泛型(第二個范型)
            val secondGenericType = getParameterUpperBound(0, returnType)
    
            //第二個范型不是 HttpResult 類,那么報錯
            check(secondGenericType != HttpResult::class.java) {
                "Flow generic type must be HttpResult"
            }
    
            //確保 HttpResult 內部包的泛型其還包裹另外一層泛型,比如 HttpResult<*> or Flow<HttpResult<*>>
            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<Repo<Any>, Any>(
                repoParameterizedType,
            )
        }
    
        companion object {
    
            @JvmStatic
            fun create(): FlowCallAdapterFactory {
                return FlowCallAdapterFactory()
            }
        }
    }
    
    
  • 3、實現 FlowCallAdapter ,代理 Retrofit.Call 的行為,這里很關鍵的是 callbackFlow{} 的使用,它是異步回調轉同步使用的魔法,類似 suspendCancellableCoroutine{} 的協程掛起操作

    class FlowCallAdapter<T : Repo<R>, R : Any>(
        private val successType: Type,
    ) : CallAdapter<T, Flow<HttpResult<R>>> {
    
        override fun responseType(): Type = successType
    
        @OptIn(ExperimentalCoroutinesApi::class)
        override fun adapt(call: Call<T>): Flow<HttpResult<R>> {
            return callbackFlow {
                //異步轉同步,為了保證項目內都使用 HttpResult 做響應數據包裝類,這里還是復用 SuspendHttpCall
                val suspendHttpCall =
                    SuspendHttpCall(call).also {
                        it.enqueue(
                            object : Callback<HttpResult<R>> {
                                override fun onResponse(
                                    call: Call<HttpResult<R>>,
                                    response: Response<HttpResult<R>>
                                ) {
                                    response.body()?.let{ httpResult->
                                        trySend(httpResult)
                                    }
                                }
    
                                override fun onFailure(call: Call<HttpResult<R>>, 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 斷點調試源碼,看看數據是怎么流轉和產生的

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容