Android 腾讯面试Glide源码解析 3分钟手写最牛框架 看完成牛逼哄哄
https://blog.youkuaiyun.com/WHB20081815/article/details/88619371
Android 百度面试Evenbus3.0源码解析 3分钟手写最牛框架 看完成大神
https://blog.youkuaiyun.com/WHB20081815/article/details/88618798
Android 阿里面试OkHttp源码解析 最牛框架 看完成大神
https://blog.youkuaiyun.com/WHB20081815/article/details/88618460
1.okhttp3.0新增的功能和区别
2.okhttp优点是什么,支持的协议
3.用法解析 同步和异步 怎么实现的
4.封装okhttputil
5.拦截器的责任链模式理解
6.拦截器 socker连接池复用机制详解
7.拦截器 缓存机制 O缓存基于DiskLruCache
OkHttp是一个非常优秀的网络请求框架,已被谷歌加入到Android的源码中。目前比较流行的Retrofit也是默认使用OkHttp的
OkHttp由于基于Http协议,所以http协议都支持
在早期的版本中,OkHttp支持Http1.0,1.1,SPDY协议,但是Http2协议的问世,导致OkHttp也做出了改变,OkHttp鼓励开发者使用HTTP2,不再对SPDY协议给予支持。另外,新版本的OkHttp还有一个新的亮点就是支持WebScoket,这样我们就可以非常方便的建立长连接了。关于Http各个版本的异同,可以查看这篇博客:
/**
* 同步的Post请求
*
* @param url
* @param params post的参数
* @return
*/
private Response _post(String url, Param... params) throws IOException
{
Request request = buildPostRequest(url, params);
Response response = mOkHttpClient.newCall(request).execute();
return response;
}
异步
Request request = buildMultipartFormRequest(
url, new File[]{file}, new String[]{fileKey}, null);
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.add("username","张鸿洋");
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
mOkHttpClient.newCall(request).enqueue(new Callback(){});
---------------------
通过上面代码可以看出异步请求的基本流程:
1.创建OkHttpClient和Request对象
2.将Request封装成Call对象
3.调用Call的enqueue发起异步请求
*特别注意*:
onFailure和onResponse都是执行在子线程
中
对于同步和异步请求,唯一的区别就是异步请求会放在线程池(ThreadPoolExecutor)中去执行,而同步请求则会在当前线程中执行,注意:同步请求会阻塞当前线程。
Request、Response、Call 基本概念
公开给我们开发的是;OkHttpClient ,RequestBody ,Call
核心重点类Dispatcher线程池介绍
在Okhttp中,构建了一个核心为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做"OkHttp Dispatcher"的线程工厂。
不错的地址:
https://www.jianshu.com/p/ca8a982a116b/
首先,我们生成了一个OKHttpClient对象,注意OKHttpClient对象的生成有两种方式:一种是我们使用的方式,另一种是使用建造者(Builder)模式 -- new OkHttpClient.Builder()....Build()。那么这两种方式有什么区别呢?
第一种:
RealCall(生成一个Call)
在我们定义了请求对象request之后,我们需要生成一个Call对象,该对象代表了一个准备被执行的请求。Call是可以被取消的。Call对象代表了一个request/response 对(Stream).还有就是一个Call只能被执行一次。执行同步请求,代码如下(RealCall的execute方法):
---------------------
3.3、Dispatcher(调度器)
Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
如上图,针对同步请求,Dispatcher使用了一个Deque保存了同步任务;针对异步请求,Dispatcher使用了两个Deque,一个保存准备执行的请求,一个保存正在执行的请求,为什么要用两个呢?因为Dispatcher默认支持最大的并发请求是64个,单个Host最多执行5个并发请求,如果超过,则Call会先被放入到readyAsyncCall中,当出现空闲的线程时,再将readyAsyncCall中的线程移入到runningAsynCalls中,执行请求。先看Dispatcher的流程,跟着流程读源码
---------------------
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");//将请求移除集合
if (promoteCalls) promoteCalls();
...
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
-3.4、拦截器链
在依次介绍各个拦截器之前,先介绍一个比较重要的类:RealInterceptorChain,直译就是拦截器链类;这个类在什么地方会用到呢?还是3.2节,RealCall的execute方法有这么一段代码:
Response result = getResponseWithInterceptorChain();
没错,在getResponseWithInterceptorChain();方法中我们就用到了这个RealInterceptorChain类。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP,此外OkHttp还处理了代理服务器问题和SSL握手失败问题。
链路的概念
Gzip压缩
使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.
重试及重定向就不再说了,都知道什么意思,左上角给出了各浏览器或Http版本支持的重试或重定向次数。
OKHttp优点
-
支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。)
-
socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数。
-
基于Headers的缓存策略减少重复的网络请求。
-
拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)。
TCP三次握手
通常我们进行HTTP连接网络的时候我们会进行TCP的三次握手,然后传输数据,然后再释放连接。
TCP三次握手的过程为:
-
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
-
第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
-
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
TCP四次分手
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,断开连接就需要进行TCP四次分手:
-
第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment
Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了; -
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence
-
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
-
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
keepalive connections
当然大量的连接每次连接关闭都要三次握手四次分手的很显然会造成性能低下,因此http有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而不需要再次握手。
Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间)。
缓存策略
1、如果网络不可用并且无可用的有效缓存,则返回504错误;
2、继续,如果不需要网络请求,则直接使用缓存;
3、继续,如果需要网络可用,则进行网络请求;
4、继续,如果有缓存,并且网络请求返回HTTP_NOT_MODIFIED,说明缓存还是有效的,则合并网络响应和缓存结果。同时更新缓存;
5、继续,如果没有缓存,则写入新的缓存;
五种拦截器
看到这里,不禁会问,上面看到的那么多种拦截器到底分别是用来干啥的呢,在这里来总结一下(来自网络,只是做个归纳):
RetryAndFollowUpInterceptor
用来实现连接失败的重试和重定向
BridgeInterceptor
用来修改请求和响应的 header 信息
CacheInterceptor
用来实现响应缓存。比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的
ConnectInterceptor
用来打开到服务端的连接。其实是调用了 StreamAllocation 的newStream 方法来打开连接的。建联的 TCP 握手,TLS 握手都发生该阶段。过了这个阶段,和服务端的 socket 连接打通
CallServerInterceptor
用来发起请求并且得到响应。上一个阶段已经握手成功,HttpStream 流已经打开,所以这个阶段把 Request 的请求信息传入流中,并且从流中读取数据封装成 Response 返回
1.责任链模式
2.超时时间修改
3.哪个请求的
4.线程池的阻塞队列
5.缓存时间修改
总结:
OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。
看懂了拦截器其实也就是看懂了OkHttp,它的整个工作原理其实就是以拦截器责任链模式为核心,这种模式之下,我们可以很方便的来定制我们自己拦截器,比如可以改变请求头,处理缓存等等等等
参考博客:
https://www.jianshu.com/p/196f7b2a703c