okhttp的火热程度,不用多说,已经被谷歌爸爸加入到Android源码中,也是面试高频的问题之一,如果只是满足于API工程师,那么面试还是有一点难度的。
1、HTTP报文结构
2、HTTP发展历史
注意:目前使用最多的仍然是HTTP1.1,可以抓包看。
3、okhttp有哪些优势
1)支持http2,对一台机器的所有请求共享同一个socket
2)内置连接池,支持连接复用,减少延迟
3)支持透明的gzip压缩响应体
4)通过缓存避免重复的请求
5)请求失败时自动重试主机的其他ip,自动重定向
6)丰富的API,可扩展性好
4、okhttp使用
//1.创建OkHttpClient
OkHttpClient client = new OkHttpClient();
//2.创建Request,并填入url信息
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
//3.同步请求
Response response = client.newCall(request).execute();
//4.异步请求
client.newCall(request).enqueue(responseCallback);
5、看过okhttp源码吗?简单介绍一下
根据以上使用代码,不管同步还是异步请求,都是通过client.newCall(request)来进行执行,这个newCall其实是创建了一个RealCall对象,所有的请求处理,都是由RealCall来完成,RealCall在进行请求前,会检查是否已经执行过,如果已执行会抛出异常,也就是说,一个Call对象只能处理一次请求。真正进行网络请求的是getResponseWithInterceptorChain()方法,该方法内部将一系列的拦截器构成拦截链,然后链式执行proceed()方法完成网络请求。
6、同步请求详细源码解读
@Override
public Response execute() throws IOException {
synchronized (this) {
//1.如果该请求已经运行抛出异常,否则将运行标志位置为true,防止重复请求
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//2.捕获调用堆栈的跟踪
captureCallStackTrace();
//告知eventlisten请求开始
eventListener.callStart(this);
try {
//3.通过dispatcher的executed来实际执行
client.dispatcher().executed(this);
//4.经过一系列"拦截"操作后获取结果
Response result = getResponseWithInterceptorChain();
//如果result为空抛出异常
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
//告知eventlisten请求失败
eventListener.callFailed(this, e);
throw e;
} finally {
//通知dispatcher执行完毕
client.dispatcher().finished(this);
}
}
③中client.dispatcher().executed(this) 仅仅是将call加入同步请求队列,并没有真正开始进行网络请求,Dispatcher 内部维护着三个队列:同步请求队列 runningSyncCalls、异步请求队列 runningAsyncCalls、异步缓存队列 readyAsyncCalls,和一个线程池 executorService,来维护、管理、执行OKHttp的请求。④中getResponseWithInterceptorChain()才开始进行网络请求。
Response getResponseWithInterceptorChain() throws IOException {
//创建一个拦截器链表用于存放各种拦截器
List<Interceptor> interceptors = new ArrayList<>();
//向链表中添加用户自定义的拦截器
interceptors.addAll(client.interceptors());
//1.向链表中添加retryAndFollowUpInterceptor用于失败重试和重定向
interceptors.add(retryAndFollowUpInterceptor);
//2.向链表中添加BridgeInterceptor用于把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//3.向链表中添加CacheInterceptor用于读取缓存以及更新缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
//4.向链表中添加ConnectInterceptor用于与服务器建立连接
interceptors.add(new ConnectInterceptor(client));
//如果不是webSocket添加networkInterceptors
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//5.向链表中添加CallServerInterceptor用于从服务器读取响应的数据
interceptors.add(new CallServerInterceptor(forWebSocket));
//根据上述的拦截器链表生成一个拦截链
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//通过拦截链的proceed方法开始整个拦截链事件的传递
return chain.proceed(originalRequest);
}
主要列举一下默认已经实现的几个拦截器的作用:
RetryAndFollowUpInterceptor:重试和失败重定向拦截器
BridgeInterceptor:桥接拦截器,处理一些必须的请求头信息的拦截器
CacheInterceptor:缓存拦截器,用于处理缓存
ConnectInterceptor:连接拦截器,建立可用的连接,是CallServerInterceptor的基本
CallServerInterceptor:请求服务器拦截器将 http 请求写进 IO 流当中,并且从 IO 流中读取响应 Response
具体细节,请阅读源码,这里不再进行细节描述。
https://yq.aliyun.com/articles/78105
7、异步请求详细源码解读
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
//1.如果该请求正在运行抛出异常,否则将运行标志位置为true,防止重复请求
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//2.捕获调用堆栈的跟踪
captureCallStackTrace();
eventListener.callStart(this);
//3.通过dispatcher的enqueue将此次请求添加到异步队列
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
enqueue实际上是new了一个RealCall的内部类AsyncCall扔进了dispatcher中,如果当前正在运行的异步请求数小于阈值maxRequests (默认Dispatcher中为64)并且同host下运行的请求小于阈值maxRequestsPerHost(默认Dispatcher中为5),就将AsyncCall添加到正在运行的异步队里,并通过线程池异步执行,否则就将其丢到等待队列排队。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
AsyncCall实际上是一个Runnable,我们看一下进入线程池后真正执行的代码:
@Override
protected void execute() {
boolean signalledCallback = false;
try {
//还是通过链式调用实现
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
可以看到,内部还是跟同步请求一样,通过getResponseWithInterceptorChain()完成请求,然后通过传入的callBack回调请求结果,最后在finally中通知Dispatcher此次请求已完成,Dispatcher会在finish中检查当前请求数是否已低于阈值,若低于就去readyAsyncCalls等待队列中取出下一个请求。
8、okhttp实现网络请求的方法
OkHttp3的最底层是Socket,而不是URLConnection,它通过Platform的Class.forName()反射获得当前Runtime使用的socket库
socket发起网络请求的流程一般是:
(1). 创建socket对象;
(2). 连接到目标网络;
(3). 进行输入输出流操作。
(1)(2)的实现,封装在connection接口中,具体的实现类是RealConnection。(3)是通过stream接口来实现,根据不同的网络协议,有Http1xStream和Http2xStream两个实现类,由于创建网络连接的时间较久(如果是HTTP的话,需要进行三次握手),而请求经常是频繁的碎片化的,所以为了提高网络连接的效率,OKHttp3实现了网络连接复用。
9、okhttp实现带进度上传下载
OkHttp把请求和响应分别封装成了RequestBody和ResponseBody,下载进度的实现可以自定义ResponseBody,重写source()方法,上传进度自定义RequestBody,重写writeTo()方法。
下载 https://www.jianshu.com/p/df7d4945f007 上传 https://blog.csdn.net/u011247387/article/details/83027254
10、为什么response.body().string() 只能调用一次
我们可能习惯在获取到Response对象后,先response.body().string()打印一遍log,再进行数据解析,却发现第二次直接抛异常,其实直接跟源码进去看就发现,通过source拿到字节流以后,直接给closeQuietly悄悄关闭了,这样第二次再去通过source读取就直接流已关闭的异常了。
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
//这里讲resource给悄悄close了
Util.closeQuietly(source);
}
}
解决方案:1.内存缓存一份response.body().string();2.自定义拦截器处理log。
11、okhttp运用的设计模式