如果可以我想改名成《看完不會(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") }
- 定義 suspend 方法,返回值聲明為 DataObject梗肝,只獲取必要的數(shù)據(jù)榛瓮,并且在協(xié)程里使用【這里只是一個(gè)簡(jiǎn)單的例子,不建議大家在 View 層使用lifecycleScope 來做接口請(qǐng)求】
-
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 接口定義愕把。
- 這里沒有定制的情況下,使用的
CallAdapter
是DefaultCallAdapterFactory
森爽,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;
...
}
-
參考
DefaultCallAdapterFactory
和RxJava2CallAdapterFactory
的實(shí)現(xiàn)思路,我們定義一個(gè)SuspendCallAdapterFactory
實(shí)現(xiàn)CallAdapter.Factory
接口,如果對(duì)不熟悉 Factory 的自定義流程辟灰,還是可以打斷點(diǎn)調(diào)試下DefaultCallAdapterFactory
和RxJava2CallAdapterFactory
个榕,另外配合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)系湿颅。 - 還是最原始的方法拾氓,如果不知道發(fā)生什么事(對(duì)源碼不熟),那么直接在
-
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 代理類
- 首先看下 DefaultCallAdapterFactory 和其主要方法
這個(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搀玖、自定義
SuspendCallAdapterFactory
和SupendCallAdatper
余境,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#parseAnnotations
中isKotlinSuspendFunction
為 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ù)方拋出異常馍忽。
- 模仿 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ì)鸽素,將四種密封類職能分別包裝褒繁,最后用
-
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() } } } }
中途代理
SuspendHttpCall
的enqueue
操作后涣脚,發(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)生的