前言
1.okhttp现在使用越来越广泛,许多第三方的框架里面需要网络的都使用了okhttp,比如bmob等,由于个人项目原因最近一段时间用volley比较多,所以对okhttp有点生疏了,现在我们来重温习一遍okhttp。
2.个人认为volley适用于小型个人项目,如果是较大项目还是动用okhttp吧。
3.本篇文章是基于okhttp3.8.0版本源码来讲述。
4.导入声明:(
implementation ‘com.squareup.okhttp3:okhttp:3.8.0’
implementation ‘com.squareup.okio:okio:1.12.0’
)
这里同时需要导入一个okio的包,有不熟悉的朋友就会问了,我用的是okhttp3为什么还要导入一个okio的,其实这就是好像是okhttp3它里面用到这个包,我们需要一并导入给它使用。而okio是干嘛用的呢,这里我给出一些介绍。
okio基本特性
(1)紧凑的封装 是对Java IO/NIO 的一个非常优秀的封装,绝对的“短小精焊”,不仅支持文件读写,也支持Socket通信的读写。
(2) 使用简单 不用区分字符流或者字节流,也不用记住各种不同的输入/输出流,统统只有一个输入流Source和一个输出流Sink。
(3)API丰富 其封装了大量的API接口用于读/写字节或者一行文本
(4)读写速度快 这得益于其优秀的缓冲机制和处理内存的技巧,使I/O在缓冲区得到更高的复用处理,从而尽量减少I/O的实际发生。
>okio支撑机制 (1)超时机制 在读/写时增加了超时机制,且有同步与异步之分。 (2)缓冲机制 读/写都是基于缓冲来来实现,尽量减少实际的I/O。 (3)压缩机制 写数据时,会对缓冲的每个Segment进行压缩,避免空间的浪费。当然,这是其内部的优化技巧,提高内存利用率。 (4)共享机制 主要是针对 Segment 而言的,对于不同的 buffer 可以共享同一个 Segment。这也是其内部的优化技巧。
因为okhttp3里面也会用到读写,所以使用了这个库。看完上面关于okio的介绍你大概知道我们为什么需要同时导入okio的包吧。
正文
首先我们说一下GET,POST的同步异步请求
不管是GET同步异步或者是POST同步异步他们都需要创建一个OkHttpClient对象,这个对象很重要,我们要发送网络请求,它就必不可少。
我们构建了一个请求信息Request对象,而OkHttpClient对象就是承载着这个请求对象进行性网络请求操作。
(比如你还不明白的话,就这样打个比喻OkHttpClient对象是一个快递员,Request对象就是一个快递,没快递员你送个鬼快递)
我们发一个请求也只需要这俩个对象是不是很容易理解,一个包裹一个快递员就够了。
- ##调用代码
下面先看一下调用代码,我们再来说它们的源码的实现原理
这里如果进行同步请求,一定要自己开启一个子线程给它,不然会出错。为什么要开线程,我们都知道啊,网络是耗时操作,你不开个子线程把它放在主线程中跑那不是扯淡嘛。别开我们下面的异步线程我们没开子线程给它跑,你就以为它在主线程跑了,它其实还是弄了一个线程池。
- ###GET同步:
String url = "https://www.baidu.com";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.build();
Call call = client.newCall(request);
//使用
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
System.out.println("GET同步请求:"+response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
- ###GET异步:
这里异步的话我们就注册回调接收它返回的数据,它回调的方法也很简单,一个失败一个成功。写法与同步也是类似,处理接受数据的方式有点区别,但是底层实现差距是不大的。
String url = "https://www.baidu.com";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.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 {
String data = response.body().string();
System.out.println("GET异步请求:"+data);
}
});
POST和GET这里不同点就在于我们需要再构建一个RequestBody对象来承载我们的请求数据,接着上一个比喻,我们进一步比喻可以这样理解: OkHttpClient是快递员 Request是一个包裹 而 RequestBody就是我们包裹上的商家信息(比如说加急等信息)
- ###POST同步
OkHttpClient client = new OkHttpClient();
String url = "http://47.94.255.194:3222/get_all_article";
RequestBody formBody = new FormBody.Builder()
.add("blog_id", "7")
.add("page","0")
.add("number","10")
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
//使用
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
System.out.println("POST同步请求:"+response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
- ###POST异步
OkHttpClient client = new OkHttpClient();
String url = "http://47.94.255.194:3222/get_all_article";
RequestBody formBody = new FormBody.Builder()
.add("blog_id", "7")
.add("page","0")
.add("number","10")
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
//使用
call.enqueue(new Callback() {
//请求失败执行的方法
@Override
public void onFailure(Call call, IOException e) {
}
//请求成功执行的方法
@Override
public void onResponse(Call call, Response response) throws IOException {
String data = response.body().string();
System.out.println("POST异步请求:"+data);
}
});
上面我们就把常用的网络请求都概述完毕,这里我们也只讲关于get,post基本网络操作。断点下载这些我们下次有空再去唠叨。
上面我们提到同步和异步的使用区别,同步我们需要自己去开线程,异步则不需要,异步okhttp弄了一个线程池,最后依旧是调用了同步的方法,所以俩者是没有本质的区别的,这里我们来看一下源码:
这是我们异步调用的enqueue方法,这里新建了一个AsyncCall,并加入dispatcher的待执行队列。在dispatcher的线程池执行到AsyncCall.executeOn()
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
看一下AsyncCall的源码:
这里给出的不是完整的源码,省略号那里省去了一部分,不过我们要看的就是execute,看到没有异步里面出现了我们之前同步调用的方法。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
@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);
}
}
}
这里我们已说明了同步异步其实底层实现是一样的。
- ##okhttp拦截器特点
拦截器顾名思义就是拦截的作用,那么eokhttp为什么会使用拦截器这种方式,作用又是干嘛?
我们先来看一下okhttp源码中的拦截器使用代码:
// 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));、
这里源码中定义一个集合保存所有拦截器,总共七个拦截器,其中有个forWebSocket参数,我们默认传false,代码如下,所以这里会进去判断,使用七个拦截器。那么这七个拦截的有什么作用呢?
- interceptors这个是外部配置的拦截器,就是自定义自己的拦截器。
- retryAndFollowUpInterceptor 失败重试,重定向。
- BridgeInterceptor 转换用户请求,转换服务器的响应。
- CacheInterceptor 读取缓存,修改缓存。
- ConnectInterceptor 与服务器建立连接。
- networkInterceptors 外部配置的network拦截器。
- CallServerInterceptor 发起网络请求,接受数据。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
上面我们说几个拦截器,那么在okhttp中,它是怎么执行这些拦截器的呢?
在我们的okhttp中有一个Interceptor接口定义,其实RealInterceptorChain就实现了Interceptor接口。也就是在RealInterceptorChain中我们执行所有拦截器,下面给出源码,源码也很容易看懂,即使取出集合中配置的拦截器,然后执行,直到执行完毕。
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
下面给出拦截器的执行流程图。
那么,至此我们整个okhttp发送一个GET或者POST请求就完毕了,这里我们介绍了okhttp发送网络请求的一个流程。
如果你有时间我建议你再自己去看一下CacheInterceptor拦截器的源码以及CallServerInterceptor拦截器对缓存的处理
这里由于篇幅原因我简单提及一下,我们如果的第一次请求a网络资源,我们okhttp会发送氢气去请求这些资源,并且把资源保存起来,当我们第二次再去请求这个网站的时候,我们okhttp会先检查本地有没有这个网站的资源,如果没有则请求,如果有则检查缓存资源是否过期即使服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比,如果有有资源修改了则就返回最新的资源,状态码是200,如果没有进行修改则返回304。
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
一定要注意okhttp拦截器是一个u型,从1到7,返回也要从7到1。
如果上面http不是很了解,建议去脑补一些http方面的技术。
http参考文章