前言
OkHttp 就不做多余的介绍了,可说说是目前使用最广泛的网络请求框架,在很多的其他第三方框架中也是用了改网络请求框架例如Retrofit,这里主要想说一些问什么使用okhttp。
一、为什么使用版OKHttp
1.1 Socket
如果我们需要从服务器获取一个资源,那么就一定需要访问网络,访问网络就一定需要使用到Socket,我想大家在学习Java的网络编程的时候也就是Socket 编程的时候一定写过客户端与服务端,客户端法一个消息然后服务端返回一个消息,就是一个极为简单的聊天软件,理论上我们可以通过Socket 访问网络 获取任何我们需要的资源,Sokcet 给用户提供了网络访问的通道,但是没有提供通信的协议。 目前我们最常用的通信协议是Http。
1.2 Http
关于Http 也不想多费口舌,Http的协议内容是固定的,但是Http协议在不同语言或者是不同框架上实现的方式与支持力度是不一样的,这就延伸出了很多的问题。
我记得最开始学习Http的时候,我们还曾经自己通过Sokcet 实现最简单的Http协议
private static void doGet(String host, int port, String uri) {
Socket socket=null;
try {
socket=new Socket(host,port);
} catch (Exception e) {
e.printStackTrace();
}
try {
StringBuffer sb=new StringBuffer("GET "+uri+" HTTP/1.1\r\n");
sb.append("Accept: */*\r\n");
sb.append("Accept-Language: zh-cn\r\n");
sb.append("User-Agent: HTTPClient\r\n");
sb.append("Host: localhost:8080\r\n");
sb.append("Connection: Keep-Alive\r\n");
//send http request
OutputStream socketOut=socket.getOutputStream();
socketOut.write(sb.toString().getBytes());
Thread.sleep(2000);//sleep 2s ,wait response
//recieve
InputStream socketIn=socket.getInputStream();
int size=socketIn.available();
byte[] buffer=new byte[size];
socketIn.read(buffer);
System.out.println(new String(buffer));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
上面就是最简单的Http 的get 请求,当然也实现了对应的Post的请求这里就不在贴出来了,我们一度很困惑,既然自己实现了Http协议,为什么还要使用别的框架,自己实现的难道不是更简单节约吗,说实在的我真的困惑的很长一段时间,现在看来这真的是一个很愚蠢的问题。
我产生这个困惑并且迟迟没有没能像明白的原因是因为在大多数的开发里面,我们访问网络仅仅是我发出一个请求,服务器将数据返回来就可以了,中间没有根本没有重定向,数据缓存,cookie ,数据压缩,以及其他Http的错误码的处理。实际上直到现在对于大部分app而言获取数据也是这样,如果使用了第三方的api另当别论。
后来随着开发时间长了才渐渐明白这些东西。如果仅仅考虑自己的一个App也许可以使用上那个最简单的实现方法,但是考虑到日后的维护与扩展,这样一个简单的实现明显是不行的。
一个框架除了考虑对整个Http协议支持的完整度,(如是否支持HTTPS,HTTP2,是否支持代理,是否支持Cookie)还要考虑实现的方式与实现的效率。(例如Sokcet的链接复用以便提高链接速度,API的友好性等等。)
最终在一系列竞争中OkHttp 获得了胜利。 关于OkHttp 与其他的网络框架例如Volley 的对比大家可以自行搜索一下。
二、OKHttp 请求过程
void test() throws IOException {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)//连接失败后是否重新连接
.connectTimeout(15, TimeUnit.SECONDS)//超时时间15S
.build();
String url = "http://124.205.184.180:8080/up/Upload?flg=0";
Request request = new Request.Builder()
.url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
如上,这里对于OkHttpClient 的创建过程不做介绍,无非是配置各种参数。主要从创建请求开始
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
newCall 创建一个请求,返回的是Call的实现类RealCall,下面进入RealCall的enqueue 方法
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
dispatch 返回一个分发器Dispatcher,Dispatcher 可以看成是对线程池的封装,可以控制同时并发请求的数量等,
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
如果当前正在执行的请求数量小于配置的最大请求,同时当前Host的请求数量小于配置的数量,此时就会将请求抛到线程池等待执行,
AsyncCall 是对RealCall的装饰,同时AsyncCall 实现了Runnable 接口,所以抛到线程池之后最终会调用到AsyncCall 的run 方法
run 方法最终调用的是execute 方法
@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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
核心内容是在getResponseWithInterceptorChain
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
getResponseWithInterceptorChain 里面可以看到大名鼎鼎的责任链。
这里拦截器分为两部分
retryAndFollowUpInterceptor 之前的称之为应用拦截器,下面的这一部分是网络拦截器
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
应用拦截器与网络拦截器的区别:
之前在网上也查询过一些解释,大都是你抄我,我吵他,最终也都没有给出一个很清晰的解释,实际一般情形下二者没啥区别都会执行一次,但是如果涉及到重定向,或者说url对应多个ip地址,当第一个ip地址不可用,此时okhttp也会尝试链接第二个ip, 而这一部分功能都包含在RetryAndFollowUpInterceptor 里面,RetryAndFollowUpInterceptor 可能会调用多次proceed 方法,而没调用一次proceed 方法,在RetryAndFollowUpInterceptor 后面的拦截器都会执行一次,因此此时网络拦截器会多次执行。
关于拦截器的内容就先介绍这么多,具体的下一章介绍。