导语
为什么会选择这样一个话题来展开讨论?其实从Square开源了这两个优秀的网络库到现在已经有很长时间了,各种源码解析和踩坑指南到处都是,而本篇的出发点是:通过几个有趣的实战问题开始,挖掘Retrofit和OkHttp的设计理念,并挑选一些代码进行阅读,相信读完这一篇,大家对Retrofit 和 Okhttp的细节会熟稔于心
2020-09-02 21:19:48补充:
考虑到写这么长的文章,而且代码很多,读起来会很累,又补充了Demo github.com/leobert-lan…; 如果您觉得这些内容还不错,烦请给文章和项目点个赞支持下,苦兮兮的。
如何为请求设置独立的超时时间
okhttp中提供了一些设置超时时间的API,例如:
后三个是严格遵守HTTP协议中的超时含义的,callTimeout是okhttp给客户端程序提供的一个全局超时时间,一个HTTP或者HTTPS请求,从DNS解析开始,到连接,到发请求体,到服务端响应,到服务端返回数据;以及可能出现的重定向,整个过程都需要在这个时间内完成,否则就算超时了,客户端会主动放弃或者断开连接。一般来说,我们不会去动态的约定这个时间(按照okhttp的默认设定是无限长),我们先看后三个。这三个超时具体的含义简单带过下:
- 连接超时,获知目标ip(如果直接指定了ip那就不需要dns解析)后,按照三次握手建立TCP连接(SSL或者TLS先搁置不谈),如果这个连接握手环节超时了那就gg了,
- 写超时,这个写是朝服务端写,即发送TCP报文,我们知道报文是封装成数据帧(frame)发送的,接收方接收后会有ack,这个写超时时间是对于每一帧而言的,虽然发送失败了会有重发机制,但超时了也会gg,
- 读超时,和写超时类似,不过是对方(服务端)往自己(客户端)发送数据的时候。
因为业务中,业务的重要程度不同,以及服务端处理不同业务的复杂度不同,我们需要为不同级别的业务设定灵活的超时时间。
以往我们可能会看到一种不太负责任的做法,就是创建多个okhttp client实例和retrofitClient实例,对应到不同的超时时间,这个做法就不多评价了。
阅读过代码,我们发现retrofit的切面设计中,是可以为每个请求单独设置这三个时间的:
interface Chain {
fun request(): Request
fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain
fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain
fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
//无关代码略去
}
具体做法自然就是大家熟知的拦截器:
class OverrideTimeoutInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
var newChain = chain
originalRequest.header(Consts.OVERRIDE_CONNECT_TIMEOUT)?.toIntOrNull()?.let {
if (it > 0)
newChain = newChain.withConnectTimeout(it, TimeUnit.MILLISECONDS)
}
originalRequest.header(Consts.OVERRIDE_WRITE_TIMEOUT)?.toIntOrNull()?.let {
if (it > 0)
newChain = newChain.withWriteTimeout(it, TimeUnit.MILLISECONDS)
}
originalRequest.header(Consts.OVERRIDE_READ_TIMEOUT)?.toIntOrNull()?.let {
if (it > 0)
newChain = newChain.withReadTimeout(it, TimeUnit.MILLISECONDS)
}
return newChain.proceed(originalRequest)
}
}
然后根据我们熟知的Retrofit工作原理,会通过反射方式创建动态代理,对我们定义的网络接口信息是一清二楚,并且在创建出可执行对象的过程中,提供了拦截器让我们对创建过程进行“干涉”,所以我们可以直接利用这个机制,将超时时间设置被Retrofit读取到,从上面我们看到,我们是将这些信息放在Header中的,如果没有特殊的要求,我们可以不用读取后移除,如果放在QueryString或者Field中,那还是要处理掉的,避免出现问题。
我们再往下看一看源码,调用链的类是 okhttp3.internal.http.RealInterceptorChain
设置的超时时间最终被应用在这几处:
- okhttp3.internal.connection.RealConnection#newCodec
- okhttp3.internal.connection.ExchangeFinder
- okhttp3.internal.http2.Http2ExchangeCodec#writeRequestHeaders
那么前面提到的CallTimeout,如果铁了心非要干他丫的,源码是否给了我们这个机会呢,其实这个问题来自于我参与的Okhttp项目中一个issue的讨论:链接。
这个全局超时的设计实现,在不同的Okhttp版本中可能有所不同,在4.3.1版本中,他是这样的:
class Transmitter(
private val client: OkHttpClient,
private val call: Call
) {
private val connectionPool: RealConnectionPool = client.connectionPool.delegate
private val eventListener: EventListener = client.eventListenerFactory.create(call)
private val timeout = object : AsyncTimeout() {
override fun timedOut() {
cancel()
}
}.apply {
timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
}
//...无关代码略去
}
而这个控制超时的控制器被RealCall所依赖:
internal class RealCall private constructor(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
/**
* There is a cycle between the [Call] and [Transmitter] that makes this awkward.
* This is set after immediately after creating the call instance.
*/
private lateinit var transmitter: Transmitter
//... 无关代码略去
}
这个RealCall就是Okhttp中描述请求的类。
阅读代码会发现,okhttp在编码上并没有留给我们直接修改RealCall的机会,哪怕是这个类对象的实例创建也是相对封闭的,哪怕继承OkhttpClient并重现实现工厂,也只能针对request做一下小修改,并不能大动干戈:
static class OkHttpClient2 extends OkHttpClient {
public OkHttpClient2(@NotNull Builder builder) {
super(builder);
}
// Prepares the [request] to be executed at some point in the future.
//
// override fun newCall(request: Request): Call {
// return RealCall.newRealCall(this, request, forWebSocket = false)
// }
@NotNull
@Override
public Call newCall(@NotNull Request request) {
//拷贝了父类是如何创建RealCall的,见上,因为internal修饰,
//并不允许我们自行创建RealCall
return super.newCall(request);
}
static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) {
return new OkHttpClient2(client.newBuilder());
}
}
最终我们得出结论:OkHttp并没有提供一个正常的方式让我们去“动态的”修改这个全局的超时时间,也许这背离了他们的设计。
square的 Jesse Wilson也给了官方解释
Yep. We don’t have a mechanism to adjust a timeout that’s already started. By the time the interceptor runs we’re already subject to the call timeout.
并没有提供这样一个机制,可以在计时开始后动态调整计时。并且一定走进了拦截器逻辑,其实计时已经开始了。
这一点我们可以直接在RealCall的代码中得出结论:
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
所以,如果非要修改,要么修改Okhttp的源码,要么像我在issue讨论中说的,用反射处理。
补,2020-10-28 09:18:20:
又重新阅读了一下部分代码和issue中的内容。之前存在一些误解,okhttp并不提供一个机制去修改一个已经开始处理的请求的"总超时",因为它是从开始就计算的。
而对于一个没有开始处理的请求,还是可以动态修改的。API中暴露了修改call的timeout的方式,我们只需要在请求开始处理前得到call对象和对call配置的timeout时长。目前有两个比较安全的做法:
callAdapter机制,可以直接根据注解处理,哪怕是自定义的注解
OkhttpClient生成call阶段处理,需要将timeout写入header之类
网络层优化的判断依据--更加精细的统计工具
相信经过上一个问题,大家又仔细温习了一下Okhttp的源码,这时候应该又一次记住了:okhttp中如何创建Request、如何创建RealCall、如果利用Chain去描述切面并做职责划分、封装Response,并如何对请求做管理的。
这时候我们遇到了一个新的问题:业务线需要从技术层面对网络层进行优化,口说无凭,我们需要有一套机制来收集:请求到底花了多少事件,自身的处理业务花了多少时间;而服务端也需要采集业务响应所耗费的时间。
于是我们需要一套机制去采集网络层花费时间的关键信息。
我们可以想象一下,从用户触发一个用户行为,到最终按照接口返回数据在UI层反馈给用户,大体会经历以下几步:
- 前置业务:用户输入和场景校验
- 创建API请求
- 切换到工作线程执行API请求
- 解析API返回结果并切换到UI线程反馈结果给用户
这里前置业务我们基本认为它和网络层没啥关系,也不算啥太耗时的业务,姑且认为它不需要针对时间做优化,也不做时长采集;
创建API请求这一块,如果忽略掉反射耗费的时间以及认为Retrofit是不可替换了话,那么我们也不需要太过于严苛的对待它;
切换工作线程并执行API请求,这里可能会遇到线程池等待,域名或者IP最大连接数量达到上限而等待;以及我们解析dns,创建连接,tls握手验证等环节的耗时也是需要关心的。
返回数据解析和结果反馈,是需要考量下json数据(应该不会有xml了吧)是否耗时较多,并且严格审视下是否有不恰当的主线程耗时行为(例如数据写入磁盘做缓存,主线程进行大量的对象创建和属性copy等)
我们先看和网络请求直接相关的部分
在上一个问题中,我们简单接触了一个类:Transmitter
,并且发现他持有一个类的对象依赖:EventListener
。直接看一下源码,篇幅较长,我把注释都删了:
abstract class EventListener {
open fun callStart(call: Call) {
}
open fun proxySelectStart(call: Call,url: HttpUrl) {
}
open fun proxySelectEnd(call: Call,url: HttpUrl,proxies: List<@JvmSuppressWildcards Proxy>) {
}
open fun dnsStart(
call: Call,
domainName: String
) {
}
open fun dnsEnd(
call: Call,
domainName: String,
inetAddressList: List<@JvmSuppressWildcards InetAddress>
) {
}
open fun connectStart(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy
) {
}
open fun secureConnectStart(
call: Call
) {
}
open fun secureConnectEnd(
call: Call,
handshake: Handshake?
) {
}
open fun connectEnd(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy,
protocol: Protocol?
) {
}
open fun connectFailed(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy,
protocol: Protocol?,
ioe: IOException
) {
}
open fun connectionAcquired(
call: Call,
connection: Connection
) {
}
open fun connectionReleased(
call: Call,
connection: Connection
) {
}
open fun requestHeadersStart(
call: Call
) {
}
open fun requestHeadersEnd(call: Call, request: Request) {
}
open fun requestBodyStart(
call: Call
) {
}
open fun requestBodyEnd(
call: Call,
byteCount: Long
) {
}
open fun requestFailed(
call: Call,
ioe: IOException
) {
}
open fun responseHeadersStart(
call: Call
) {
}
open fun responseHeadersEnd(
call: Call,
response: Response
) {
}
open fun responseBodyStart(
call: Call
) {
}
open fun responseBodyEnd(
call: Call,
byteCount: Long
) {
}
open fun responseFailed(
call: Call,
ioe: IOException
) {
}
open fun callEnd(
call: Call
) {
}
open fun callFailed(
call: Call,
ioe: IOException
) {
}
interface Factory {
fun create(call: Call): EventListener
}
companion object {
@JvmField
val NONE: EventListener = object : EventListener() {
}
}
}
这里呢,我们先关注两个时间,一是EventListener对象的创建时间,二是callStart方法被调用的时间。
我们还有印象,Transmitter对象创建的时候,通过工厂创建了EventListener对象:
class Transmitter(private val client: OkHttpClient,private val call: Call) {
private val eventListener: EventListener = client.eventListenerFactory.create(call)
//...
}
在Transmitter#callStart时,调用了EventListener的callStart:
fun callStart() {
this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(call)
}
我们前面也看到源码,他是RealCall被调用execute()或者enqueue()时被立即调用的,那么线程池等待时间、连接上限等待等时间只会发生在callStart之后,而dns解析等时间,可以从EventListener中计算得出。
而Transmitter是在RealCall创建时一同创建的,如果没有啥“意外”情况,EventListener创建的时间和callStart的时间应该非常接近,因为Retrofit的使用中,仅仅是做了一些简单的前置业务,创建RealCall之后随即调用了execute或者enqueue;如果这个时间差较大,就值得分析下是否存在“魔改”实现的业务。
而为请求添加自定义的EventListener,只需要实现工厂并利用API注册:
okhttp3.OkHttpClient.Builder#eventListenerFactory()
假定我们已经实现好了一个细致的EventListener,继续往下,假定一切顺利,我们得到了服务端的返回数据,业务处理事件如何优雅的处理呢,我们分三种情况简单看下
- 仅使用Retrofit的Callback机制,这个没啥好说的,从基类动手脚
- 使用了RxJava,可以从Adapter入手,从Observable切入
- 使用了kotlin协程(Retrofit内部的支持),可以从Flow切入
这里我提供一下我们项目中使用RxJava的处理情况,改写了Square提供的Adapter,仅供参考一二:
注意 RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())
即可
internal class CallEnqueueObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>?>() {
override fun subscribeActual(observer: Observer<in Response<T>?>) {
// Since Call is a one-shot type, clone it for each new observer.
val call = originalCall.clone()
val callback = CallCallback(call, observer)
observer.onSubscribe(callback)
if (!callback.isDisposed) {
call.enqueue(callback)
}
}
private class CallCallback<T> internal constructor(private val call: Call<*>, private val observer: Observer<in Response<T>?>)
: Disposable, Callback<T> {
@Volatile
private var disposed = false
var terminated = false
override fun onResponse(call: Call<T>, response: Response<T>) {
if (disposed) return
try {
val body = response.body()
if (body is BaseData) {
body.callHolder = call
}
observer.onNext(response)
if (!disposed) {
terminated = true
observer.onComplete()
}
} catch (t: Throwable) {
if (terminated) {
RxJavaPlugins.onError(t)
} else if (!disposed) {
try {
observer.onError(t)
} catch (inner: Throwable) {
Exceptions.throwIfFatal(inner)
RxJavaPlugins.onError(CompositeException(t, inner))
}
}
}
try {
RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())
} catch (e: Exception) {
L.e(e)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
if (call.isCanceled) return
try {
observer.onError(t)
} catch (inner: Throwable) {
Exceptions.throwIfFatal(inner)
RxJavaPlugins.onError(CompositeException(t, inner))
}
}
override fun dispose() {
disposed = true
call.cancel()
}
override fun isDisposed(): Boolean {
return disposed
}
}
}
internal class CallExecuteObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>?>() {
override fun subscribeActual(observer: Observer<in Response<T>?>) {
// Since Call is a one-shot type, clone it for each new observer.
val call = originalCall.clone()
val disposable = CallDisposable(call)
observer.onSubscribe(disposable)
if (disposable.isDisposed) {
return
}
var terminated = false
try {
val response = call.execute()
if (!disposable.isDisposed) {
val body = response.body()
if (body is BaseData) {
body.callHolder = originalCall //这个originalCall在没有使用时内部的call和request都不会创建,
// 为了避免重新测原有业务,不做修改了,但是如果要使用它的request和call信息时注意其时效性
}
observer.onNext(response)
}
if (!disposable.isDisposed) {
terminated = true
observer.onComplete()
}
} catch (t: Throwable) {
Exceptions.throwIfFatal(t)
if (terminated) {
RxJavaPlugins.onError(t)
} else if (!disposable.isDisposed) {
try {
observer.onError(t)
} catch (inner: Throwable) {
Exceptions.throwIfFatal(inner)
RxJavaPlugins.onError(CompositeException(t, inner))
}
}
}
try {
RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())
} catch (e: Exception) {
L.e(e)
}
}
private class CallDisposable internal constructor(private val call: Call<*>) : Disposable {
@Volatile
private var disposed = false
override fun dispose() {
disposed = true
call.cancel()
}
override fun isDisposed(): Boolean {
return disposed
}
}
}
ok,这时候我们思考一个问题:出于实际情况,我们被迫将业务耗时的采集和请求开始的时间已经分开了,如何将他们再结合起来呢?,直白点说,如何在执行时获知请求开始时间? RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())
这又开始考验我们对源码的熟悉程度了。我们都使用过Square为Retrofit提供的日志拦截器吧,是不是很好奇,这个日志拦截器里面为什么可以打印出调用的方法原型,类似以下形式:
[Method:get] (url),tag=[package.UserApi#getUserInfo(String:uid)]
其实这里打印的是okhttp的Request信息,我们看下这个类:
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl,
@get:JvmName("method") val method: String,
@get:JvmName("headers") val headers: Headers,
@get:JvmName("body") val body: RequestBody?,
internal val tags: Map<Class<*>, Any>
) {
private var lazyCacheControl: CacheControl? = null
val isHttps: Boolean
get() = url.isHttps
fun header(name: String): String? = headers[name]
fun headers(name: String): List<String> = headers.values(name)
/**
* Returns the tag attached with `Object.class` as a key, or null if no tag is attached with
* that key.
*
* Prior to OkHttp 3.11, this method never returned null if no tag was attached. Instead it
* returned either this request, or the request upon which this request was derived with
* [newBuilder].
*/
fun tag(): Any? = tag(Any::class.java)
fun <T> tag(type: Class<out T>): T? = type.cast(tags[type])
//...略去无关部分
override fun toString() = buildString {
append("Request{method=")
append(method)
append(", url=")
append(url)
if (headers.size != 0) {
append(", headers=[")
headers.forEachIndexed { index, (name, value) ->
if (index > 0) {
append(", ")
}
append(name)
append(':')
append(value)
}
append(']')
}
if (tags.isNotEmpty()) {
append(", tags=")
append(tags)
}
append('}')
}
没错,就是创建RealCall的那个Request,它可以设置一些tag,而Retrofit创建ok的Request时,往里面加入了tag信息,参考:retrofit2.RequestFactory#create(Object[] args)
,加入了一个Invocation
的tag:
public final class Invocation {
public static Invocation of(Method method, List<?> arguments) {
checkNotNull(method, "method == null");
checkNotNull(arguments, "arguments == null");
return new Invocation(method, new ArrayList<>(arguments)); // Defensive copy.
}
private final Method method;
private final List<?> arguments;
/** Trusted constructor assumes ownership of {@code arguments}. */
Invocation(Method method, List<?> arguments) {
this.method = method;
this.arguments = Collections.unmodifiableList(arguments);
}
public Method method() {
return method;
}
public List<?> arguments() {
return arguments;
}
@Override public String toString() {
return String.format("%s.%s() %s",
method.getDeclaringClass().getName(), method.getName(), arguments);
}
}
不过可惜,retrofit的RequestFactory是Final修饰的,并且不是实现的接口,所以我们无法直接借助Retrofit添加我们需要的tag,回想一下,我们上面提到了okhttp的RealCall创建,他是有一个Factory的,而且OkHttpClient实现了这个工厂,我们覆写一下即可:
static class OkHttpClient2 extends OkHttpClient {
public OkHttpClient2(@NotNull Builder builder) {
super(builder);
}
@NotNull
@Override
public Call newCall(@NotNull Request request) {
// 看了下代码没有享元模式, 这样改没啥风险,但是也没啥用,
// 底层基本都是retrofit创建的Proxy模式的Call,真正使用时才通过RequestFactory创建request、继而创建ok的call,
// 即取出来的tag应该都是null
NetLoggingEventListener.RetrofitRequest tag = null;
try {
tag = request.tag(NetLoggingEventListener.RetrofitRequest.class);
} catch (Exception e) {
L.e(e);
}
if (tag == null)
request = request.newBuilder().tag(NetLoggingEventListener.RetrofitRequest.class,
new NetLoggingEventListener.RetrofitRequest(System.currentTimeMillis())).build();
return super.newCall(request);
}
static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) {
return new OkHttpClient2(client.newBuilder());
}
}
class RetrofitRequest(val timeMillis: Long) {
override fun toString(): String {
return "RetrofitRequest(timeMillis=$timeMillis)"
}
}
我们定义这样一个类来描述我们目前需要以及以后可能需要的信息,并将其作为tag写入okhttp的Request中。并且可以通过fun <T> tag(type: Class<out T>): T? = type.cast(tags[type])
API将其取出。
其实看到这里,大家已经完全可以自己动手完成这样一个时长采集工具了
这里还想再补充一点,笔者当时没有注意的一个细节:
我们看过代码都知道,Retrofit中,又定义了Call接口、OkHttpCall实现类,这个OkHTTPCall的实现类可以认为是遵循Facade模式以及Proxy模式设计思想的,在没有真正用于请求时,不会创建OkHttp的call,创建的关键代码:
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
而这个方法,仅在三处调用:获取Request实例,execute,enqueue; 后两个都是真实要发起请求的,第一处,如果在请求前调用,往往是打算做一些修改。
而配合RxJava使用时,我们注意这种情况:CallExecuteObservable
,代码上面有贴过,这里贴关键的:
// Since Call is a one-shot type, clone it for each new observer.
val call = originalCall.clone()
val disposable = CallDisposable(call)
担心流处理中可能存在的重试以及复用问题,对originalCall进行了clone,所以我们必须使用clone出来的call对象去获取Request再获取tag,否则,使用originalCall则会遇到一个问题,Request是真正使用到时才开始创建,并没有对应到请求开始的时间😂。
全栈支持人机校验,Square给我们提供的 RxJava&协程 Adapter丢弃了什么
这次我们又遇到了一个新问题:某些接口被单ip访问频次过高,可能是恶意行为,需要添加人机校验环节,并且要求在校验完成后携带参与重发原请求。
经过和服务端的一番商榷后,我们确定用统一的错误码来表达需要进行人机校验。
其实这个问题看起来很清晰,发现response了特定的错误码,即弹出人机校验,注册回调,完成后得到相关参数,添加到原先的Request中,重新发起请求。
在处理这个问题时,我们没有像上一个问题中那样,直接到Observable的处进行处理,因为这属于业务级,所以我们在请求的响应回调基类中处理,当然,如果你使用的是官方的Adapter,也并没有办法直接处理Observable。
此时我们先将这个问题搁置,回到标题,Square给我们提供的 RxJava&协程 Adapter丢弃了什么?
按照我们对RxJava和协程flow的了解,这两者都是基于流的响应式订阅处理框架,而流的源,其实都是Okhttp的Response,或者更直白的说,是将response的body解析成的对象。因为业务特性,绝大多数情况下我们只需要关心返回的结果,而不再需要关心请求本身。我们再看一次这段代码:
internal class CallExecuteObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>?>() {
override fun subscribeActual(observer: Observer<in Response<T>?>) {
// Since Call is a one-shot type, clone it for each new observer.
val call = originalCall.clone()
val disposable = CallDisposable(call)
observer.onSubscribe(disposable)
if (disposable.isDisposed) {
return
}
var terminated = false
try {
val response = call.execute()
if (!disposable.isDisposed) {
val body = response.body()
if (body is BaseData) {
body.callHolder = originalCall //这个originalCall在没有使用时内部的call和request都不会创建,
// 为了避免重新测原有业务,不做修改了,但是如果要使用它的request和call信息时注意其时效性
}
observer.onNext(response)
}
if (!disposable.isDisposed) {
terminated = true
observer.onComplete()
}
} catch (t: Throwable) {
Exceptions.throwIfFatal(t)
if (terminated) {
RxJavaPlugins.onError(t)
} else if (!disposable.isDisposed) {
try {
observer.onError(t)
} catch (inner: Throwable) {
Exceptions.throwIfFatal(inner)
RxJavaPlugins.onError(CompositeException(t, inner))
}
}
}
try {
RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())
} catch (e: Exception) {
L.e(e)
}
}
}
如果和官方的Adapter对比一下的话,我们增加了一段内容:
if (body is BaseData) {
body.callHolder = originalCall //这个originalCall在没有使用时内部的call和request都不会创建,
// 为了避免重新测原有业务,不做修改了,但是如果要使用它的request和call信息时注意其时效性
}
附BaseData:
public class BaseData {
/**
* 因为配合RxJava使用,需要修改的底层过多,我们留一个引用来存储原来的Call
*/
@Nullable
public transient Call<?> callHolder;
@SerializedName("code")
public String code;
@SerializedName(value = "msg")
public String msg;
@Nullable
@SerializedName("ext")
public JsonElement ext;
}
没错,这只是一个取巧的改法,我们没有改变源,毕竟一个已经上线的项目,对于底层修改的兼容性要求很高,如果是刚开始搭建的项目,倒是可以考虑直接修改事件源,当然,这会让后续的编码变得很糟糕。
此时我们就可以在请求结果的流处理中获得原始的请求了。我们先忽略掉人机校验弹窗的事情,假设我们已经通过回调方式获得了人机校验的行为参数。
我们将:
- 从原始的call得到原始okhttp3的Request对象
- Request遵循Builder设计模式,我们可以很轻松的将Request对象转变为Builder并拷贝原始的对应属性,继而添加人机校验的参数。
- 利用Retrofit的CallFactory重新创建Call,其实这个工厂就是OkHttpClient,我们上面提到过,他实现了Okhttp中CallFactory
- 确保主线程中再次调用onStart,让UI层显得优雅一点,但是务必要注意原先的业务中onStart()是否有必须成对出现的逻辑
- 重新发送请求并使用包装类,复用当前的请求回调
参考下面的代码:
protected void onHumanCheck(@NonNull final retrofit2.Call oldCall, final int code, final Result<T> result) {
humanCheckRef = new WeakReference<Function1<HumanCheckParam, Unit>>(new Function1<HumanCheckParam, Unit>() {
@Override
public Unit invoke(HumanCheckParam humanCheckParam) {
if (isDisposed())
return null;
if (humanCheckParam == null) {
onFailureCode(code, result);
return null;
}
try {
Request request = oldCall.request();
Call call = RetrofitClient.getRetrofit().callFactory()
.newCall(request.newBuilder()
.url(request.url().newBuilder()
.setQueryParameter("scene", null2Empty(humanCheckParam.getScene()))
.setQueryParameter("sig", null2Empty(humanCheckParam.getSig()))
.setQueryParameter("nc_token", null2Empty(humanCheckParam.getNcToken()))
.setQueryParameter("csessionid", null2Empty(humanCheckParam.getSessionid()))
.build())
//header存在长度限制,还是使用queryString
// .addHeader("scene", null2Empty(humanCheckParam.getScene()))
// .addHeader("sig", null2Empty(humanCheckParam.getSig()))
// .addHeader("ncToken", null2Empty(humanCheckParam.getNcToken()))
// .addHeader("sessionId", null2Empty(hu
// manCheckParam.getSessionid()))
.build()
);
ReComposeOkHttpCallBack.Companion.getMainHandler().post(new Runnable() {
@Override
public void run() {
try {
onStart();
} catch (Exception ignore) {
}
}
});
if (ReComposeOkHttpCallBack.Companion.canHandle(oldCall)) {
call.enqueue(new ReComposeOkHttpCallBack<T>(oldCall, RetrofitSubscriber.this));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String null2Empty(@Nullable String nullable) {
if (nullable == null)
return "";
return nullable;
}
});
HumanChecker.INSTANCE.requestCheck(null, humanCheckRef);
}
///////
class ReComposeOkHttpCallBack<T>(private val originalCall: Call<Result<T>>, private val subscriber: RetrofitSubscriber<T>) : okhttp3.Callback {
companion object {
val mainHandler: Handler = Handler(Looper.getMainLooper())
fun canHandle(originalCall: Call<*>): Boolean {
return originalCall is OkHttpCall
}
}
override fun onFailure(call: okhttp3.Call, e: IOException) {
mainHandler.post {
subscriber.onFailure(RetrofitException(RetrofitException.NETWORK, RetrofitException.ERROR_NONE_NETWORK, e))
}
}
override fun onResponse(call: okhttp3.Call, rawResponse: okhttp3.Response) {
val response: Response<Result<T>>
try {
if (originalCall is OkHttpCall) {
response = originalCall.parseResponse(rawResponse)
mainHandler.post {
try {
subscriber.onNext(response.body())
} catch (e: Exception) {
subscriber.onFailure(RetrofitException(RetrofitException.UNKNOWN, "未知错误", e))
} finally {
try {
subscriber.onComplete()
} catch (e2: Exception) {
}
}
}
}
} catch (e: Throwable) {
mainHandler.post {
subscriber.onFailure(RetrofitException(RetrofitException.UNKNOWN, "内部错误", e))
}
}
}
}
总结
回归到导语立的FLAG,这一篇我们试图通过几个实战问题,和大家一同挖掘下Retrofit和OkHttp的设计意图,并记住一些细节部分。
那就先看看OkHttp吧,先刨去协议实现部分和OKio的部分,OkHttp使用了Request、Response和Call来描述请求中的主要对象和行为。
Call是对行为的抽象和封装,他的创建依赖于Request,通过执行可以得到Response,并且遵循工厂模式和原型模式设计。
Request是对请求的封装,包含请求动词、url、header、body以及与协议无关的tag,同样实现了工厂模式,Response内部和协议相关的内容比较多,这一次不展开。
Okhttp中的细节基本都是围绕着三者展开的:
- 封装了HttpUrl、Header等对象,一同参与请求的抽象表达
- 通过工厂利用Request生产Call,而请求的过程中,有很多细节,但是这些细节是有序的,于是采用AOP设计和责任链封装:
- 封装了Chain来描述整个处理过程链
- 封装了Interceptor来描述切面
- 切面是按照责任链排序的,先后顺序:
- interceptors += client.interceptors
- interceptors += RetryAndFollowUpInterceptor(client)
- interceptors += BridgeInterceptor(client.cookieJar)
- interceptors += CacheInterceptor(client.cache)
- interceptors += ConnectInterceptor
- if (!forWebSocket) { interceptors += client.networkInterceptors }
- interceptors += CallServerInterceptor(forWebSocket)
- 而内部细节过于复杂,为了减轻使用者的使用成本,OkHttpClient使用Facade模式封装,如果对对应的模块有订制需求,实现相应模块后并在创建OkHttpClient时使用
我们再看看Retrofit,Retrofit原意是翻新、改进的含义,在Square看来,单独一个OkHttp并没有“革命性”的变化,无论其底层采用了怎样的优化,对于使用者来说,他并没有比前辈级的库优秀在哪里,还是需要用业务代码处理Request的构建,于是乎Retrofit应运而生。
他利用注解的形式来约定请求的的header、url(包括path参数和queryString参数)、body(最常用的是表单),通过反射获知这些信息,使用动态代理的方式,从而在底层封装Request的构建。
这还没完,使用预定的Callback来处理结果回调的制约太大,于是提供了CallAdapter机制,让使用者可以自定义处理方式。
到此为止,我们已经通过3个案例,回顾了Retrofit和OkHttp的关键源码,并且给出了解决方案,相信大家对于这两个常用库的认知又深了一些。