OkHttp
分发器
- 对于同步请求,分发器只记录请求,用于判断dleRunnable是否需要执行
- 对于异步请求,向分发器中提交请求
1、如何决定将请求放入ready还是running?
答:如果当前正在请求个数为64,则将请求放入ready等待执行;如果小于64,但是已经存在同一域名主机的请求5个,也会放入ready,否则放入running队列立即执行
class Dispatcher.java
synchronized void enqueue(AsyncCall call) {
// 先判断请求个数是否符合要求
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//符合加入执行队列
runningAsyncCalls.add(call);
//执行,线程
executorService().execute(call);
} else {
//不符合加入准备队列
readyAsyncCalls.add(call);
}
}
2、从ready移动running的条件是什么?
答:每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动!
client.dispatcher().finished(this);
3、分发器线程池的工作行为?
答:无等待,最大分发。
class Dispatcher.java
//线程池,最大大小
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
拦截器
这些拦截器会形成一条链,组织了请求接口的所有工作。
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<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)
val chain = RealInterceptorChain(
interceptors = interceptors
//...
)
val response = chain.proceed(originalRequest)
}
1、RetryAndFollowUpInterceptor
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
while (true) {
try {
try {
response = realChain.proceed(request)
} catch (e: RouteException) {
//路由错误
continue
} catch (e: IOException) {
// 请求错误
continue
}
//获取响应码判断是否需要重定向
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
//没有重定向
return response
}
//赋予重定向请求,再次进入下一次循环
request = followUp
}
}
}
}
- 当发生请求过程中错误的时候,就需要重试,也就是通过continue进入下一次循环,重新走到realChain.proceed方法进行网络请求。
- 当请求结果需要重定向的时候,就赋予新的请求,并进入下一次循环,重新请求网络。
- 当请求结果没有重定向,那么就直接返回response响应结果。
2、封装拦截器(BridgeInterceptor)
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//添加头部信息
requestBuilder.header("Content-Type", contentType.toString())
requestBuilder.header("Host", userRequest.url.toHostHeader())
requestBuilder.header("Connection", "Keep-Alive")
requestBuilder.header("Accept-Encoding", "gzip")
requestBuilder.header("Cookie", cookieHeader(cookies))
requestBuilder.header("User-Agent", userAgent)
val networkResponse = chain.proceed(requestBuilder.build())
//解压
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
请求前的代码很简单,就是添加了一些必要的头部信息,包括Content-Type、Host、Cookie等等,封装成一个完整的请求报文,然后交给下一个拦截器。
requestBuilder.header("Accept-Encoding", "gzip")
这一句其实就是在告诉服务器,客户端所能接受的文件的压缩格式,这里设置了gzip之后,服务器看到了就能把响应报文数据进行gzip压缩再传输,提高传输效率,节省流量。
所以请求之后的这段关于gzip的处理其实就是客户端对压缩数据进行解压缩,而GzipSource是okio库里面一个进行解压缩读取数据的类。
3、缓存拦截器(CacheInterceptor)
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//取缓存
val cacheCandidate = cache?.get(chain.request())
//缓存策略类
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
// 如果不允许使用网络,并且缓存数据为空
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)//504
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// 如果不允许使用网络,但是有缓存
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
networkResponse = chain.proceed(networkRequest)
// 如果缓存不为空
if (cacheResponse != null) {
//304,表示数据未修改
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
cache.update(cacheResponse, response)
return response
}
}
//如果开发者设置了缓存,则将响应数据缓存
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
//缓存header
val cacheRequest = cache.put(response)
//缓存body
return cacheWritingResponse(cacheRequest, response)
}
}
return response
}
}
-
请求之前,通过request获取了缓存,然后判断缓存为空,就直接返回code为504的结果。如果有缓存并且缓存可用,则直接返回缓存。
-
请求之后,如果返回304代表服务器数据没修改,则直接返回缓存。如果cache不为空,那么就把response缓存下来。
-
cache在实例化CacheInterceptor的时候传进去的,所以这个cache是需要我们创建OkHttpClient的时候设置的,比如这样:
val okHttpClient =
OkHttpClient().newBuilder()
.cache(Cache(cacheDir, 10 * 1024 * 1024))
.build()
4、连接拦截器(ConnectInterceptor)
private fun findConnection(): RealConnection {
// 1、复用当前连接
val callConnection = call.connection
if (callConnection != null) {
//检查这个连接是否可用和可复用
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
return callConnection
}
//2、从连接池中获取可用连接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
//3、从连接池中获取可用连接(通过一组路由routes)
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
return result
}
route = localRouteSelection.next()
// 4、创建新连接
val newConnection = RealConnection(connectionPool, route)
newConnection.connect
// 5、再获取一次连接,防止在新建连接过程中有其他竞争连接被创建了
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
return result
}
//6、还是要使用创建的新连接,放入连接池,并返回
connectionPool.put(newConnection)
return newConnection
}
获取连接的过程很复杂,为了方便看懂,我简化了代码,分成了6步。
1、检查当前连接是否可用。
怎么判断可用的?主要做了两个判断 1)判断是否不再接受新的连接 2)判断和当前请求有相同的主机名和端口号。
这倒是很好理解,要这个连接是连接的同一个地方才能复用是吧,同一个地方怎么判断?就是判断主机名和端口号。
还有个问题就是为什么有当前连接??明明还没开始连接也没有获取连接啊,怎么连接就被赋值了?
还记得重试和重定向拦截器吗?对了,就是当请求失败需要重试的时候或者重定向的时候,这时候连接还在呢,是可以直接进行复用的。
2和3、从连接池中获取可用连接。
第2步和第3步都是从连接池获取连接,有什么不一样吗?
connectionPool.callAcquirePooledConnection(address, call, null, false)
connectionPool.callAcquirePooledConnection(address, call, routes, false)
好像多了一个routes字段?
这里涉及到HTTP/2的一个技术,叫做 HTTP/2 CONNECTION COALESCING(连接合并),什么意思呢?
假设有两个域名,可以解析为相同的IP地址,并且是可以用相同的TLS证书(比如通配符证书),那么客户端可以重用相同的TCP连接从这两个域名中获取资源。
再看回我们的连接池,这个routes就是当前域名(主机名)可以被解析的ip地址集合,这两个方法的区别也就是一个传了路由地址,一个没有传。
继续看callAcquirePooledConnection代码:
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
if (address.url.host == this.route().address.url.host) {
return true
}
//HTTP/2 CONNECTION COALESCING
if (http2Connection == null) return false
if (routes == null || !routeMatchesAny(routes)) return false
if (address.hostnameVerifier !== OkHostnameVerifier) return false
return true
}
1)判断主机名、端口号等,如果请求完全相同就直接返回这个连接。
2)如果主机名不同,还可以判断是不是HTTP/2请求,如果是就继续判断路由地址,证书,如果都能匹配上,那么这个连接也是可用的。
4、创建新连接。
如果没有从连接池中获取到新连接,那么就创建一个新连接,这里就不多说了,其实就是调用到socket.connect进行TCP连接。
5、再从连接池获取一次连接,防止在新建连接过程中有其他竞争连接被创建了。
创建了新连接,为什么还要去连接池获取一次连接呢?因为在这个过程中,有可能有其他的请求和你一起创建了新连接,所以我们需要再去取一次连接,如果有可以用的,就直接用它,防止资源浪费。
其实这里又涉及到HTTP2的一个知识点:多路复用。
简单的说,就是不需要当前连接的上一个请求结束之后再去进行下一次请求,只要有连接就可以直接用。
HTTP/2引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样在收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。同样是因为有了序列,服务器就可以并行的传输数据,这就是流所做的事情。
所以在HTTP/2中可以保证在同一个域名只建立一路连接,并且可以并发进行请求。
6、新连接放入连接池,并返回。
最后一步好理解吧,走到这里说明就要用这个新连接了,那么就把它存到连接池,返回这个连接。
IO拦截器(CallServerInterceptor)
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//写header数据
exchange.writeRequestHeaders(request)
//写body数据
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
exchange.noRequestBody()
}
//结束请求
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
//获取响应数据
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.build()
var code = response.code
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
return response
}
}
这个拦截器 倒是没干什么活,之前的拦截器兄弟们都把准备工作干完了,它就调用下exchange类的各种方法,写入header,body,拿到code,response。
被遗漏的自定义拦截器(networkInterceptors)
好了,最后补上这个拦截器networkInterceptors,它也是一个自定义拦截器,位于CallServerInterceptor之前,属于倒数第二个拦截器。
那为什么OkHttp在有了一个自定义拦截器的前提下又提供了一个拦截器呢?
可以发现,这个拦截器的位置是比较深的位置,处在发送数据的前一刻,以及收到数据的第一刻。这么敏感的位置,决定了通过这个拦截器可以看到更多的信息,比如:
请求之前,OkHttp处理之后的请求报文数据,比如增加了各种header之后的数据。
请求之后,OkHttp处理之前的响应报文数据,比如解压缩之前的数据。
所以,这个拦截器就是用来网络调试的,调试比较底层、更全面的数据。
总结
- addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
- RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
- BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
- CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
- ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec
- networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
- CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。
retrofit
okHttp问题:
- a、用户网络请求的接口配置繁琐,尤其需要配置复杂请求body,请求头,参数的时候
- b、数据解析过程需要用户手动拿到Responsebody进行解析,不能复用
- c、无法适配自动进行线程切换
- d、万一我们的存在嵌套网络请求就会嵌入“回调陷阱”
retrofit创建流程
函数解析
1、 retrofit.create(service)
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//使用了动态代理
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public 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);
}
//利用反射得到ServiceMethod
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
2、 ServiceMethod
ServiceMethod<?, ?> loadServiceMethod(Method method) {
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
//如果本地缓存有这个方法,则直接返回
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
- serviceMethod 通过retofit创建,用于执行,每个接口都会有一个独立的serviceMethod,去查找具体需要用到的方法,一一对应、
- Retrofit.Builder()是在全局上,引入用到类,但在每个接口具体实现,还是通过serviceMethod
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null");
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
//使用了抽象工厂模式
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
·····
通过注解解析到okhttpcall上,发送给okhttp2请求
数据适配
在初始化retofit的时候,会传入
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
//数据解析
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
通过parseResponse(rawResponse)方法里的
T body = serviceMethod.toResponse(catchingBody);
通过responseConverter.convert(body),获取到,最初拿到的数据解析类型,进行解析返回
回调嵌套处理
通过Rxjava处理 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
serviceMethod.adapt(okHttpCall);
--->
线程切换
static class Android extends Platform {
@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
handler.post(r);
}
}
}
1、用户网络
retrofit 大局上获取可能用到的方法
构建设计模式:bind >5个参数
serviceMethod 通过retofit创建,用于执行,每个接口都会有一个独立的,去查找需要用到的类
rxjava:嵌套 链式调用
okhttp :call -> java对象
线程切换:platform 线程切换 ExecuorCallbackCall
RxJavaCallAdapterFactory
抽象工厂类型 根据不同的三种类型adapter