okhttp的火热程度,不用多说,已经被谷歌爸爸加入到Android源码中,也是面试高频的问题之一,如果只是满足于API工程师,那么面试还是有一点难度的。
1、HTTP报文结构
- 请求报文
- 响应报文
2、HTTP发展历史
-
HTTP/0.9
只有一个命令GET
没有HEADER等描述数据的信息
服务器发送完毕,就关闭TCP连接 -
HTTP/1.0
增加了很多命令
增加status code和header
多字符集支持、多部分发送、权限、缓存等 -
HTTP/1.1
持久连接
pipeline
增加host和其他一些命令 -
HTTP2
所有数据以二进制传输
同一个连接里面发送多个请求不再需要按照顺序来
头信息压缩以及推送等提高效率的功能
注意:目前使用最多的仍然是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
具体细节,请阅读源码,这里不再进行细节描述。
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运用的设计模式
-
构造者模式(OkhttpClient,Request等各种对象的创建)
-
工厂模式(在Call接口中,有一个内部工厂Factory接口。)
-
单例模式(Platform类,已经使用Okhttp时使用单例)
-
策略模式(在CacheInterceptor中,在响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。)
-
责任链模式(拦截器的链式调用)
-
享元模式(Dispatcher的线程池中,不限量的线程池实现了对象复用)
在这我也分享一份自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点
总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
image.png扫码领取资源.png关注微信公众号“Android扫地僧”(微信->添加朋友->公众号->输入“Android扫地僧”)
自动回复,即可获取下载地址
网友评论