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

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


Retrofit 是一個(gè)設(shè)計(jì)相當(dāng)精良的框架放钦,特別是其可擴(kuò)展性上瘸羡。官方提供的協(xié)程的使用方式和 API 實(shí)現(xiàn)在一些情況下不大優(yōu)雅,本文主要是 bb 對(duì)其的相關(guān)擴(kuò)展搓茬,讓項(xiàng)目代碼變得更傻瓜式和對(duì) Retrofit 協(xié)程方式編寫代碼方式更加優(yōu)雅蔫骂。

基于下述思路封裝的網(wǎng)絡(luò)框架已經(jīng)在線上持續(xù)穩(wěn)定使用 1 年多了,適合各種牛(qi)逼(pa)的場(chǎng)景族吻,本篇各個(gè)環(huán)節(jié)會(huì)涉及到奇奇怪怪的想法.....

Retrofit 對(duì)協(xié)程的支持

Retrofit 從 2.4 版本開始對(duì)協(xié)程的使用正式做了支持,可以讓我們順序式的編寫代碼珠增,減少回調(diào)超歌。巴拉巴拉一大堆,但是這里最重要的一點(diǎn)是讓我們可以不用回調(diào)式的寫代碼蒂教,記住這一點(diǎn)握础,后面會(huì)重新提到。

Retrofit 協(xié)程的基本用法

下面省略 Retrofit.Builder 類相關(guān)的各種初始化操作(適配器悴品,轉(zhuǎn)換器等禀综,默認(rèn)認(rèn)為有 Gson/Moshi 適配器做數(shù)據(jù)轉(zhuǎn)換)

用法一

  • 1简烘、定義 suspend 方法,返回值聲明為 Response<DataObject>定枷,可以清楚知道響應(yīng)情況

                    @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覆旭,只獲取必要的數(shù)據(jù)

                    @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")
                    }
    

這樣使用正常情況下是可以正常拿到數(shù)據(jù)的岖妄,log 也是正常輸出的型将,程序也不會(huì)崩潰

制造一個(gè)異常

  • 正常情況下上述用法都是沒問題的,接下來我把手機(jī)網(wǎng)絡(luò)斷了荐虐,會(huì)發(fā)現(xiàn)程序閃退并且閃退棧的起始位置是 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)
        ...
        省略若干行...大家一起想象下...
  • 那有同學(xué)就說了噢七兜,不就是異常嘛,我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)
                        }
                    }
    

這樣確實(shí)好了狠裹,但是我們一開始的目的不是想非回調(diào)式的寫代碼,并且盡量減少閉包汽烦,內(nèi)部類的出現(xiàn)麼涛菠,如果每個(gè)接口都要用 catch 包住,那基本上算是沒有解決根本問題撇吞。

還是得找辦法解決這個(gè)問題【網(wǎng)絡(luò)接口拋出異常需要外部使用 try catch 包裹】碗暗,那么 Retrofit 掛起式使用要怎么樣操作才能真正的“優(yōu)雅”呢,下面會(huì)一步一步的揭開謎題....


Retrofit 是怎么支持協(xié)程的

異常怎么產(chǎn)生的

不難思考梢夯,想解決異常拋出到業(yè)務(wù)代碼的問題其實(shí)本質(zhì)上是看這個(gè)異常從哪里來的言疗,廢話不多說,其實(shí)就是接口請(qǐng)求的某個(gè)環(huán)節(jié)產(chǎn)生的異常嘛

對(duì) Retrofit 異步回調(diào)式使用熟悉的朋友肯定會(huì)想到這個(gè)用法:

                    AcRetrofit.get().notificationApi.loadSettings()
                        .enqueue(object : Callback<NotificationParams> {
                            override fun onResponse(
                                call: Call<NotificationParams>,
                                response: Response<NotificationParams>
                            ) {
                               //數(shù)據(jù)回調(diào)
                            }

                            override fun onFailure(call: Call<NotificationParams>, throwable: Throwable) {
                               //異乘淘遥回調(diào)
                            }

                        })

onFailure 回調(diào)出來的 throwable 也就是同步式 【Retrofit 調(diào)用 execute()請(qǐng)求接口】 或者協(xié)程掛起式用法獲取數(shù)據(jù)時(shí)拋出來的異常噪奄。

怎么支持的協(xié)程

  • 面對(duì)上面的問題,一開始應(yīng)該是沒有思路的人乓,至于為什么要先看 Retrofit 對(duì)協(xié)程的支持怎么實(shí)現(xiàn)的勤篮,無非也是因?yàn)檎娴氖菦]有太大的思路解決這個(gè)問題(如果不熟悉 Retrofit 源碼實(shí)現(xiàn)的情況下),只能先去源碼里面找解決方案

  • 相信大家應(yīng)該清楚 Retrofit 的核心實(shí)現(xiàn)思路【動(dòng)態(tài)代理反射 API 定義方法色罚,將相關(guān)方法表示提取為參數(shù)塞給 okHttp 去請(qǐng)求】碰缔,如果不熟悉的話,2022 年了戳护,Google 下 Retrofit 相關(guān)的流程源碼分析金抡,也會(huì)有很多優(yōu)秀的文章可以瀏覽

  • 1瀑焦、首先寫一個(gè)接口請(qǐng)求

    • 定義 suspend 方法,返回值聲明為 DataObject梗肝,只獲取必要的數(shù)據(jù)榛瓮,并且在協(xié)程里使用【這里只是一個(gè)簡(jiǎn)單的例子,不建議大家在 View 層使用lifecycleScope 來做接口請(qǐng)求】
                      @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 動(dòng)態(tài)代理的函數(shù)入口禀晓,InvocationHandler 類的 invoke 方法中打上斷點(diǎn)

      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) 會(huì)被調(diào)用坝锰,可以看到是一個(gè)按需調(diào)用 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粹懒、兜兜轉(zhuǎn)轉(zhuǎn),最終 HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory) 會(huì)被調(diào)用顷级,這個(gè)方法本質(zhì)上是解析 API 定義方法的相關(guān)參數(shù)來構(gòu)造 HTTP 服務(wù)使用的凫乖,這里會(huì)選取各種適配器,轉(zhuǎn)換器來操作當(dāng)前使用的 API 接口定義愕把。

    • 這里沒有定制的情況下,使用的 CallAdapterDefaultCallAdapterFactory 森爽,DefaultCallAdapterFactory 對(duì)協(xié)程的實(shí)現(xiàn)沒有什么特別的幫助恨豁,內(nèi)部主要實(shí)現(xiàn)是看API 接口方法定義有沒有含有 SkipCallbackExecutor 注解,如果含有該注解就將回調(diào)返回使用定義的CallbackExecutor 線程池執(zhí)行爬迟¢倜郏可以展開折疊塊看下具體定義,省略 n 多無關(guān)實(shí)現(xià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计福、來到最關(guān)鍵的地方,Retrofit 內(nèi)部要選中最終的 HttpServiceMethod徽职,因?yàn)?API 方法定義象颖,這里直接是 DataObject 返回值的,后面會(huì)返回 SuspendForBody 的實(shí)現(xiàn)

  • 6姆钉、SuspendForBody 的 adapt 方法说订,最終會(huì)看到 KotlinExtensions 的相關(guān)方法,這里我定義的 API 方法返回值是非空的潮瓶,所以最終回調(diào)用 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() 中,會(huì)直接調(diào)用 Call.enqueue() 方法發(fā)起請(qǐng)求毯辅,最終通過協(xié)程掛起恢復(fù)的操作 resumeWithException(e)resume(body) 將結(jié)果分發(fā)到協(xié)程發(fā)起端埂伦,也就是業(yè)務(wù)方使用端會(huì)受到結(jié)果(成功或者失敗)

    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)
          }
        })
      }
    }
    
    
    
    
    業(yè)務(wù)方調(diào)用======
    
    val repo: Repo<NotificationData> = AcRetrofit.get().notificationApi.loadSettings()
    
    

    這里拋出異常其實(shí)就是因?yàn)?resumeWithException 被調(diào)用的原因

解決問題

基本思路

從上面的流程分析來看思恐,最終發(fā)現(xiàn)原因是因?yàn)榫W(wǎng)絡(luò)請(qǐng)求執(zhí)行遇到異常時(shí)沾谜,將異常通過 resumeWithException(e)恢復(fù)掛起導(dǎo)致膊毁,那能不能讓它不執(zhí)行 resumeWithException 呢,從源碼上看只需要屏蔽 Retrofit Call 類的 void enqueue(Callback<T> callback)callback 實(shí)現(xiàn)或者避免其調(diào)用 resumeWithException方法类早,將所有的數(shù)據(jù)通過 onResponse 返回媚媒,并且對(duì) response.body()= null的時(shí)候做容錯(cuò)即可。

剛好 Retrofit 的設(shè)計(jì)里面有一個(gè)實(shí)現(xiàn)可以自定義 CallAdapterFactory 來定制請(qǐng)求行為涩僻。這里我們可以通過自定義 CallAdapterFactory 缭召,從而代理 Retrofit Call 類,進(jìn)而控制 Callback 的接口調(diào)用來達(dá)到我們的最終目的逆日。

關(guān)于網(wǎng)絡(luò)層的一些封裝分析

從網(wǎng)絡(luò)層封裝的角度來看嵌巷,網(wǎng)絡(luò)層單純給業(yè)務(wù)方一個(gè)響應(yīng)數(shù)據(jù)是不夠的,因?yàn)闃I(yè)務(wù)方有時(shí)候想要知道更詳細(xì)的詳情數(shù)據(jù)來決定交互行為室抽,這里列舉一些業(yè)務(wù)層想要的數(shù)據(jù):

  • Header 的數(shù)據(jù)
  • 響應(yīng)碼:有時(shí)候會(huì)通過響應(yīng)碼來區(qū)分界面的錯(cuò)誤彈窗搪哪、業(yè)務(wù)類型
  • 具體錯(cuò)誤類型
  • 接口數(shù)據(jù)

數(shù)據(jù)包裝類定義

Kotlin 里面try catch 的擴(kuò)展函數(shù) runCatching()如下

public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}
  • 以看到它是將閉包函數(shù) try catch 后包裝成 Result 密封類返回

  • kotlin.Result 類是一個(gè)設(shè)計(jì)和擴(kuò)展方法很全面的類,在 kotlin 里面各種特性的 API 之間起了至關(guān)重要的作用坪圾,其實(shí)現(xiàn)如下

    @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晓折,針對(duì)上述的封裝需求分析,按需定義一個(gè)針對(duì) Http 請(qǐng)求結(jié)果的 HttpResult 密封類兽泄,用于保存 Retrofit + OKHTTP處理過后的各種數(shù)據(jù)
    主要實(shí)現(xiàn)是 HttpResult 的四個(gè)密封子類 Success ApiError NetworkError UnknownError

    • HttpResult :密封類漓概,保存了基礎(chǔ)數(shù)據(jù) Headers,T 是 API 方法定義的請(qǐng)求返回值類型
    sealed class HttpResult<out T>(open val responseHeader: Headers?) : Serializable {
    ...
    }
    
    • Success:接口請(qǐng)求成功病梢,保存接口請(qǐng)求的 Header 原始數(shù)據(jù)和適配器解析后的接口 body 數(shù)據(jù)胃珍,T 是 API 方法定義的請(qǐng)求返回值類型
        /**
         * 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 :通常是接口返回錯(cuò)誤,其中 message 是我們接口定義通用結(jié)構(gòu)中含有的固定類型蜓陌,大家可以根據(jù)具體業(yè)務(wù)來定義
        /**
         * Failure response with body觅彰,通常是接口返回錯(cuò)誤
         * @property code Int 錯(cuò)誤碼,默認(rèn)是-1
         * @property message message 接口錯(cuò)誤信息
         * @property throwable 原始錯(cuò)誤類型
         * @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:通常是斷網(wǎng)了
        /**
         * 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 :除上述錯(cuò)誤外的其他錯(cuò)誤钮热,比如解析錯(cuò)誤等填抬,具體錯(cuò)誤會(huì)通過 throwable 給出,并且按照接口請(qǐng)求的行為隧期,可能 throwable 會(huì)為 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整體實(shí)現(xiàn)如下
      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,通常是接口返回錯(cuò)誤
           * @property code Int 錯(cuò)誤碼厌秒,默認(rèn)是-1
           * @property message message 接口錯(cuò)誤信息
           * @property throwable 原始錯(cuò)誤類型
           * @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 通常是斷網(wǎng)了
           */
          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 來定制請(qǐng)求行為

從上面分析我們知道读拆,我們只要代理 retrofit2.KotlinExtensions#await 中 Callback 接口被回調(diào)的方法始終為 onResponse() 和容錯(cuò)response.body() 為 null 的情況即可解決程序閃退和 try catch 的問題,當(dāng)然因?yàn)槲覀兩厦嬷匦露x了數(shù)據(jù)封裝類為 HttpResult 鸵闪,導(dǎo)致我們這里必須得自定義 CallAdapterFactory 才能干擾 Retrofit.Call 實(shí)現(xiàn)類的行為檐晕。

  • 自定義數(shù)據(jù)封裝類為 HttpResult 后 API 方法定義會(huì)變?yōu)?/li>
    @GET("xxxx/get-notification-settings")
    suspend fun loadSettings(): HttpResult<Repo<NotificationData>>
    
    /**
     * Repo 為統(tǒng)一的數(shù)據(jù)包裝格式
     */
    public class Repo<T> {

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

          @Nullable
          @SerializedName("data")
          private T data;
          
          ...
    }
  • 參考 DefaultCallAdapterFactoryRxJava2CallAdapterFactory的實(shí)現(xiàn)思路,我們定義一個(gè) SuspendCallAdapterFactory 實(shí)現(xiàn) CallAdapter.Factory 接口,如果對(duì)不熟悉 Factory 的自定義流程辟灰,還是可以打斷點(diǎn)調(diào)試下 DefaultCallAdapterFactoryRxJava2CallAdapterFactory 个榕,另外配合 SuspendCallAdapterFactory 的實(shí)現(xiàn),我們還要實(shí)現(xiàn)一個(gè) CallAdapter 用于 Call 和實(shí)際數(shù)據(jù)的轉(zhuǎn)換芥喇。

    class SuspendCallAdapterFactory : CallAdapter.Factory() {
    
        override fun get(
            returnType: Type,
            annotations: Array<Annotation>,
            retrofit: Retrofit
        ): CallAdapter<*, *>? {
    
            // 返回一個(gè) 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 西采,在某個(gè)適合的條件下返回 SuspendCallAdapter 【代表使用該適配器來處理數(shù)據(jù)和 retrofit2.Call 類的轉(zhuǎn)換】,下面看下 Retrofit 默認(rèn)適配器是如何工作的【Rx 適配器有興趣的小伙伴自己調(diào)試下继控,原理其實(shí)差不多的】

  • DefaultCallAdapterFactory 的流程分析

    • 首先看下 DefaultCallAdapterFactory 和其主要方法 get(Type returnType, Annotation[] annotations, Retrofit retrofit) 的實(shí)現(xiàn)【省略部分實(shí)現(xiàn)】
    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 對(duì)應(yīng)的 CallAdapter
        if (getRawType(returnType) != Call.class) {
          return null;
        }
        //方法返回值是否為參數(shù)化類型武通,如果不為參數(shù)化類型霹崎,那么會(huì)拋出一個(gè)異常,這里有固定的含義:也就是 Call 類必須包含范型
        if (!(returnType instanceof ParameterizedType)) {
          throw new IllegalArgumentException(
              "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
        }
        //獲取返回值類包含的第一個(gè)范型類型
        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 類包含的第一個(gè)范型類型返回冶忱,代表當(dāng)前這個(gè) CallAdapter 需要轉(zhuǎn)換為目標(biāo)數(shù)據(jù)的類型尾菇,也就是說 Retrofit 最終會(huì)將數(shù)據(jù)解析為這個(gè)類型
            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 構(gòu)造函數(shù)接受一個(gè) callbackExecutor ,其主要用于給后續(xù)判斷 API 方法定義是否含有 SkipCallbackExecutor 注解链沼,如果有 SkipCallbackExecutor 注解那么 callbackExecutor 為 null 默赂,其會(huì)傳遞給 CallAdapter 返回原始的 Call 對(duì)象(相當(dāng)于未代理 Call 類)

    • 其他情況上面的代碼注釋應(yīng)該比較清楚了,另外這里和后面我們自定義的實(shí)現(xiàn)里面需要用到的比較關(guān)鍵的 ParameterizedType 類忆植,它代表的是參數(shù)化類型放可,簡(jiǎn)單的說就是包含范型(包含<>括號(hào))的類聲明谒臼,比如 Dog<HotDog>

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

      • 還是最原始的方法拾氓,如果不知道發(fā)生什么事(對(duì)源碼不熟),那么直接在 DefaultCallAdapterFactory 的 get 函數(shù)里面打斷點(diǎn)底哥,看看是從哪里傳遞過來的咙鞍,一直向前看,總會(huì)發(fā)現(xiàn)發(fā)生了什么(小聲 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);
      

      關(guān)鍵就是這一行 adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      responseType 本來為 API 方法定義的返回值類型趾徽,被 Call 包了一下续滋,變成了新的參數(shù)化類型,這一招是相當(dāng)?shù)尿}孵奶,具體實(shí)現(xiàn)在 Utils.ParameterizedTypeImpl 類中疲酌,有興趣的朋友可以看看它是怎么做到包裝范型的,大致思路是將實(shí)現(xiàn)了 ParameterizedType 接口,實(shí)現(xiàn)相關(guān)方法朗恳,重定義類型和范型的關(guān)系湿颅。

    • ExecutorCallbackCall 實(shí)現(xiàn)

        static final class ExecutorCallbackCall<T> implements Call<T> {
          final Executor callbackExecutor;
          final Call<T> delegate;
      
          ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
            //接受線程池和待代理的 Call 對(duì)象
            this.callbackExecutor = callbackExecutor;
            this.delegate = delegate;
          }
      
          @Override
          public void enqueue(final Callback<T> callback) {
            Objects.requireNonNull(callback, "callback == null");
      
            //代理發(fā)起異步 API 請(qǐng)求
            delegate.enqueue(
                new Callback<T>() {
                  @Override
                  public void onResponse(Call<T> call, final Response<T> response) {
                    //使用 callbackExecutor 回調(diào)結(jié)果
                    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 回調(diào)結(jié)果的操作。我們可以模仿它實(shí)現(xiàn)自己的 Call 代理類

  • 這個(gè)時(shí)候粥诫,又有小伙伴就說了油航,你呀的啥時(shí)候自定義啊,我等不及啦怀浆,掏出貨來啊谊囚,馬上安排啦....


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

  • 1、上面說了那么多揉稚,總結(jié)下現(xiàn)在 API 定義變成什么樣了秒啦,如下,原來的 Repo<NotificationData> 被 HttpResult 接管了

        @GET("xxx/get-notification-settings")
        suspend fun loadSettings(): HttpResult<Repo<NotificationData>>
        
        /**
         * Repo 為統(tǒng)一的數(shù)據(jù)包裝格式
         */
        public class Repo<T> {
    
              @Nullable
              @SerializedName("meta")
              private Meta meta;
    
              @Nullable
              @SerializedName("data")
              private T data;
              
              ...
        }
    
  • 2搀玖、自定義 SuspendCallAdapterFactorySupendCallAdatper余境,SuspendHttpCall 暫時(shí)不列出

    class SuspendCallAdapterFactory : CallAdapter.Factory() {
    
        override fun get(
            returnType: Type,
            annotations: Array<Annotation>,
            retrofit: Retrofit
        ): CallAdapter<*, *>? {
    
            //第一個(gè)泛型就是HttpResult類,這種情況可能是api接口沒有聲明協(xié)程suspend符號(hào)灌诅,拋出異常提醒 
            if (getRawType(returnType) == HttpResult::class.java) {
                throw IllegalArgumentException("Method must be declare suspend, please check function declaration at API interface")
            }
    
            //協(xié)程掛起函數(shù)默認(rèn)返回值是Call<*>芳来,如果不滿足該條件,那么返回null讓retrofit選擇其他家伙來Py
            if (Call::class.java != getRawType(returnType)) {
                return null
            }
    
            //檢查Call內(nèi)部的泛型是否包含了其他泛型
            check(returnType is ParameterizedType) {
                "return type must be HttpResult<*> or HttpResult<out *> for Call<*> check"
            }
    
            //獲取Call類包裹的第一個(gè)泛型
            val responseType = getParameterUpperBound(0, returnType)
    
            //Call類包裹的第一個(gè)泛型不是HttpResult類猜拾,那么返回null即舌,讓retrofit選擇其他 CallAdapter.Factory
            if (getRawType(responseType) != HttpResult::class.java) {
                return null
            }
    
            //確保HttpResult內(nèi)部包的泛型其還包裹另外一層泛型,比如 HttpResult<*>
            check(responseType is ParameterizedType) { "return type must be HttpResult<*> or HttpResult<out *> for HttpResult<*> check" }
    
            //獲取HttpResult類包裹的第一個(gè)泛型
            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)
        }
    }
    
    • 上面注釋已經(jīng)很清晰了挎袜,下面提下比較重要的幾點(diǎn)

    • 第一個(gè)就是判斷泛型是否為HttpResult類顽聂,這種情況可能是api接口沒有聲明協(xié)程suspend符號(hào),拋出異常提醒盯仪,至于為何有這個(gè)結(jié)論紊搪,大家可以看下 retrofit2.HttpServiceMethod#parseAnnotationsisKotlinSuspendFunction 為 false 的聲明,returnType 直接取值為 API Method 定義的值全景,這種情況是不符合我們目前定義的協(xié)程這一套邏輯的耀石,我們直接排除,返回 null 讓 Retrofit 選擇其它 Factory

    • 最后我們獲取HttpResult類包裹的第一個(gè)泛型類型傳遞給了SupendCallAdatper 作為 responseType() 的返回值爸黄,不清楚這個(gè)環(huán)節(jié)的可以看看上面 DefaultCallAdapterFactory 的流程分析中有提到滞伟,responseType() 的返回值決定 Retrofit 最終解析的數(shù)據(jù)類型(反序列化)

    • 應(yīng)該大家還記得 parseAnnotations 方法中,API Method 定義的返回值類型被 Call 包裹的操作炕贵,因?yàn)榻涌谟幸粋€(gè)通用的包裝格式梆奈,也就是數(shù)據(jù)類型永遠(yuǎn)為 Repo<DataObject> 類似的情況,這里也用包裹范型的操作將 HttpResult<Repo<NotificationData>> 換為 HttpResult<NotificationData> 減少業(yè)務(wù)方要聲明的范型層級(jí)

      • API 方法實(shí)現(xiàn)變?yōu)?/li>
          @GET("xxx/get-notification-settings")
          suspend fun loadSettings(): HttpResult<Repo<NotificationData>>
          
          變?yōu)?    
          @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<*, *>? {
      
              //第一個(gè)泛型就是HttpResult類,這種情況可能是api接口沒有聲明協(xié)程suspend符號(hào),拋出異常提醒
              if (getRawType(returnType) == HttpResult::class.java) {
                  throw IllegalArgumentException("Method must be declare suspend, please check function declaration at API interface")
              }
      
              //協(xié)程掛起函數(shù)默認(rèn)返回值是Call<*>径荔,如果不滿足該條件督禽,那么返回null讓retrofit選擇其他家伙來Py
              if (Call::class.java != getRawType(returnType)) {
                  return null
              }
      
              //檢查Call內(nèi)部的泛型是否包含了其他泛型
              check(returnType is ParameterizedType) {
                  "return type must be HttpResult<*> or HttpResult<out *> for Call<*> check"
              }
      
              //獲取Call類包裹的第一個(gè)泛型
              val responseType = getParameterUpperBound(0, returnType)
      
              //Call類包裹的第一個(gè)泛型不是HttpResult類,那么返回null总处,讓retrofit選擇其他 CallAdapter.Factory
              if (getRawType(responseType) != HttpResult::class.java) {
                  return null
              }
      
              //確保HttpResult內(nèi)部包的泛型其還包裹另外一層泛型狈惫,比如 HttpResult<*>
              check(responseType is ParameterizedType) { "return type must be HttpResult<*> or HttpResult<out *> for HttpResult<*> check" }
      
              //獲取HttpResult類包裹的第一個(gè)泛型
              val successBodyType = getParameterUpperBound(0, responseType)
      
              //整塊注釋,下面將動(dòng)態(tài)配置泛型變?yōu)镽epo<*>鹦马,不需要再手動(dòng)聲明為Repo<*>泛型================改動(dòng)點(diǎn)
      //        check(Repo::class.java == getRawType(successBodyType)) {
              //如果待處理的類型不是Repo類胧谈,那么報(bào)異常
      //            "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菱肖、響應(yīng)成功===================
                    //================================================
                    //================================================
                    if (response.isSuccessful) {
                        body?.data?.apply {
                            //Repo.data不為空
                            httpResult = HttpResult.Success(this, response.headers())
                        } ?: run {
                            //響應(yīng)body是null或者Repo的data為空的時(shí)候
                            httpResult = HttpResult.UnknownError(
                                IllegalArgumentException("response data is invalid"),
                                null
                            )
                        }
    
                        callback.onResponse(
                            this@SuspendHttpCall,
                            Response.success(httpResult)
                        )
                        return
                    }
    
                    //================================================
                    //================================================
                    //===================2辽俗、響應(yīng)失敗===================
                    //================================================
                    //================================================
                    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 數(shù)據(jù)
                        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 的實(shí)現(xiàn)乾蓬,因?yàn)槲覀冞@里只用到異步請(qǐng)求 API,我們只需要實(shí)現(xiàn) enqueue 方法接口间涵,回到我們最初的目的和悦,我們代理 Retrofit.Call 是為了容錯(cuò) response.body()=null 和 callback.onFailure退疫,這里實(shí)現(xiàn)上我們要做一些處理,結(jié)合 HttpResult 的設(shè)計(jì)鸽素,將四種密封類職能分別包裝褒繁,最后用 callback.onResponse() 將結(jié)果返回,防止業(yè)務(wù)方拋出異常馍忽。
  • 4棒坏、最后我們?cè)?Retrofit.Builder 中引入 Factory 即可

                    new Retrofit.Builder()
                            .baseUrl(asBase)
                            .addCallAdapterFactory(SuspendCallAdapterFactory.create())
    
  • 5、使用方法遭笋,由于使用了密封類坝冕,用 when 來展開,編譯器會(huì)幫忙補(bǔ)全坐梯,或者可以直接寫成 Template 模版徽诲,使用代碼補(bǔ)全填充

                 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 -> {
                                //網(wǎng)絡(luò)問題
                            }
                            is HttpResult.Success -> {
                                val notificationData = httpResult.value
                            }
                            is HttpResult.UnknownError -> {
                                //其他異常
                                //httpResult.throwable
                            }
                        }
                    }
    
  • 6偷溺、真的沒了蹋辅,封裝就告一段落了



Flow擴(kuò)展

上面講了 Retrofit 怎么樣在協(xié)程中去掉 try catch 跟其他代碼流式編程,但其實(shí)如果結(jié)合 Flow 來定義 API Method 調(diào)用會(huì)更加的優(yōu)雅挫掏,那么怎么樣快速切換到 Flow 接口呢

轉(zhuǎn)換為 Flow 用法的分析

一開始我們轉(zhuǎn)換為 HttpResult 時(shí)侦另,我們的做法是在外部再包裝一層 HttpResult,讓 API 返回值變?yōu)?HttpResult<DataObject>,并且自定義 Factory 和 Adapter 與 Call 改變Retrofit 原始的請(qǐng)求行為褒傅。

現(xiàn)在我們要變?yōu)?Flow弃锐,就是再包裝一層 Flow 咯,還有 Factory 那些也重新給自定義下殿托∨眨【本質(zhì)上 Flow 的用法和 Rx 的用法差不多】

  • 1、就是 API 方法定義會(huì)變?yōu)槿缦轮е瘢⒁?Flow 類型不需要掛起函數(shù)聲明 旋廷,這里只要將返回值修改即可
    @GET("setting/get-notification-settings")
    fun loadSettings(): Flow<HttpResult<NotificationData>>
  • 2、定義 FlowCallAdapterFactory礼搁,相關(guān)解釋可以看下注釋饶碘,主要是判斷第一個(gè)范型是 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 請(qǐng)求方式 Retrofit CallAdapter,主要是將請(qǐng)求轉(zhuǎn)換為 Flow<HttpResult<*>>馒吴,并且包裹請(qǐng)求的所有結(jié)果填充到 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)
    
            //第一個(gè)泛型不是 Flow 類扎运,讓 retrofit 選擇其它 Factory
            if (firstGenericType != Flow::class.java) {
                return null
            }
    
            //檢查 Flow 內(nèi)部的泛型是否包含了其他泛型
            check(returnType is ParameterizedType) {
                "return type must be Flow<HttpResult<*>> or Flow<HttpResult<out *>>"
            }
    
            //獲取 Flow 類包裹的泛型(第二個(gè)范型)
            val secondGenericType = getParameterUpperBound(0, returnType)
    
            //第二個(gè)范型不是 HttpResult 類,那么報(bào)錯(cuò)
            check(secondGenericType != HttpResult::class.java) {
                "Flow generic type must be HttpResult"
            }
    
            //確保 HttpResult 內(nèi)部包的泛型其還包裹另外一層泛型饮戳,比如 HttpResult<*> or Flow<HttpResult<*>>
            check(secondGenericType is ParameterizedType) { "HttpResult generic type must be not null" }
    
            //獲取 HttpResult<*> 類包裹的泛型(數(shù)據(jù))
            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、實(shí)現(xiàn) FlowCallAdapter 莹捡,代理 Retrofit.Call 的行為鬼吵,這里很關(guān)鍵的是 callbackFlow{} 的使用,它是異步回調(diào)轉(zhuǎn)同步使用的魔法篮赢,類似 suspendCancellableCoroutine{} 的協(xié)程掛起操作

    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 {
                //異步轉(zhuǎn)同步齿椅,為了保證項(xiàng)目?jī)?nèi)都使用 HttpResult 做響應(yīng)數(shù)據(jù)包裝類,這里還是復(fù)用 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 不會(huì)回調(diào) onFailure(),這里不用做實(shí)現(xiàn)
                                    //do nothing here
                                }
    
                            })
                    }
                //必須聲明關(guān)閉 call启泣,類似 suspendCancellableCoroutine 的使用 
                awaitClose {
                    suspendHttpCall.cancel()
                }
            }
        }
    }
    
    

    中途代理 SuspendHttpCallenqueue 操作后涣脚,發(fā)射 httpResult 的結(jié)果給 callbackFlow 完成 flow 的發(fā)射端調(diào)用

  • 4、引入 Factory

                    new Retrofit.Builder()
                            .baseUrl(asBase)
                            .addCallAdapterFactory(FlowCallAdapterFactory.create())
    
  • 5寥茫、使用 Flow API 請(qǐng)求遣蚀,類似上面協(xié)程的調(diào)用,這里還是會(huì)包裝為 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 -> {
                                //網(wǎng)絡(luò)問題
                            }
                            is HttpResult.Success -> {
                                val notificationData = httpResult.value
                            }
                            is HttpResult.UnknownError -> {
                                //其他異常
                                //httpResult.throwable
                            }
                        }
                      }
    

思考

  • 不要遇到問題就 Google弄喘,更好的做法是先看看框架怎么實(shí)現(xiàn)的
  • 源碼里面有黃金書玖喘,包裹范型的思路就是在 Retrofit 里面發(fā)現(xiàn)從而優(yōu)化 API Method 的調(diào)用
  • 不要單純的看源碼和看大佬們的源碼分析,要自己寫 Demo 斷點(diǎn)調(diào)試源碼蘑志,看看數(shù)據(jù)是怎么流轉(zhuǎn)和產(chǎn)生的

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末累奈,一起剝皮案震驚了整個(gè)濱河市贬派,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澎媒,老刑警劉巖搞乏,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異戒努,居然都是意外死亡查描,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門柏卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冬三,“玉大人,你說我怎么就攤上這事缘缚」窗剩” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵桥滨,是天一觀的道長(zhǎng)窝爪。 經(jīng)常有香客問我,道長(zhǎng)齐媒,這世上最難降的妖魔是什么蒲每? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮喻括,結(jié)果婚禮上邀杏,老公的妹妹穿的比我還像新娘。我一直安慰自己唬血,他們只是感情好望蜡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拷恨,像睡著了一般脖律。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腕侄,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天小泉,我揣著相機(jī)與錄音,去河邊找鬼冕杠。 笑死微姊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拌汇。 我是一名探鬼主播柒桑,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼弊决,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼噪舀!你這毒婦竟也來了魁淳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤与倡,失蹤者是張志新(化名)和其女友劉穎界逛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纺座,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡息拜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了净响。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片少欺。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖馋贤,靈堂內(nèi)的尸體忽然破棺而出赞别,到底是詐尸還是另有隱情,我是刑警寧澤配乓,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布仿滔,位于F島的核電站,受9級(jí)特大地震影響犹芹,放射性物質(zhì)發(fā)生泄漏崎页。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一腰埂、第九天 我趴在偏房一處隱蔽的房頂上張望飒焦。 院中可真熱鬧,春花似錦屿笼、人聲如沸荒给。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽志电。三九已至,卻和暖如春蛔趴,著一層夾襖步出監(jiān)牢的瞬間挑辆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工孝情, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鱼蝉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓箫荡,卻偏偏與公主長(zhǎng)得像魁亦,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羔挡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容