OkHttp是一个精巧的网络请求库,有如下特性:
1)支持http2,对一台机器的所有请求共享同一个socket
2)内置连接池,支持连接复用,减少延迟
3)支持透明的gzip压缩响应体
4)通过缓存避免重复的请求
5)请求失败时自动重试主机的其他ip,自动重定向
6)好用的API

其本身就是一个很强大的库,再加上Retrofit2、Picasso的这一套组合拳,使其愈发的受到开发者的关注。本篇博客,我将对Okhttp3进行分析(源码基于Okhttp3.4)。

如何引入Okhttp3?

配置Okhttp3非常简单,只需要在Android Studio 的gradle进行如下的配置:

 compile 'com.squareup.okhttp3:okhttp:3.4.1'

添加网络权限:

<uses-permission android:name="android.permission.INTERNET"/>

Okhttp3的基本使用

okHttp的get请求
okHttp的一般使用如下,okHttp默认使用的就是get请求

 String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";mHttpClient = new OkHttpClient();Request request = new Request.Builder().url(url).build();okhttp3.Response response = null;try {response = mHttpClient.newCall(request).execute();String json = response.body().string();Log.d("okHttp",json);} catch (IOException e) {e.printStackTrace();}}

我们试着将数据在logcat进行打印,发现会报错,原因就是不能在主线程中进行耗时的操作

说明mHttpClient.newCall(request).execute()是同步的,那有没有异步的方法呢,答案是肯定的,就是mHttpClient.newCall(request).enqueue()方法,里面需要new一个callback我们对代码进行修改,如下

public void requestBlog() {String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";mHttpClient = new OkHttpClient();Request request = new Request.Builder().url(url).build();
/* okhttp3.Response response = null;*//*response = mHttpClient.newCall(request).execute();*/mHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String json = response.body().string();Log.d("okHttp", json);}});}

Okhttp的POST请求

POST提交Json数据

private void postJson() throws IOException {String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";String json = "haha";OkHttpClient client = new OkHttpClient();RequestBody body = RequestBody.create(JSON, json);Request request = new Request.Builder().url(url).post(body).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.d(TAG, response.body().string());}});}

POST提交键值对
很多时候我们会需要通过POST方式把键值对数据传送到服务器。 OkHttp提供了很方便的方式来做这件事情。

private void post(String url, String json) throws IOException {OkHttpClient client = new OkHttpClient();RequestBody formBody = new FormBody.Builder().add("name", "liming").add("school", "beida").build();Request request = new Request.Builder().url(url).post(formBody).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string();Log.i(TAG, str);}});}

异步上传文件
上传文件本身也是一个POST请求
定义上传文件类型

public static final MediaType MEDIA_TYPE_MARKDOWN= MediaType.parse("text/x-markdown; charset=utf-8");

将文件上传到服务器上:

private void postFile() {OkHttpClient mOkHttpClient = new OkHttpClient();File file = new File("/sdcard/demo.txt");Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)).build();mOkHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.i(TAG, response.body().string());}});
}

添加如下权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

提取响应头
典型的HTTP头 像是一个 Map<String, String> :每个字段都有一个或没有值。但是一些头允许多个值,像Guava的Multimap。例如:HTTP响应里面提供的Vary响应头,就是多值的。OkHttp的api试图让这些情况都适用。
当写请求头的时候,使用header(name, value)可以设置唯一的name、value。如果已经有值,旧的将被移除,然后添加新的。使用addHeader(name, value)可以添加多值(添加,不移除已有的)。
当读取响应头时,使用header(name)返回最后出现的name、value。通常情况这也是唯一的name、value。如果没有值,那么header(name)将返回null。如果想读取字段对应的所有值,使用headers(name)会返回一个list。
为了获取所有的Header,Headers类支持按index访问。

private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("https://api.github.com/repos/square/okhttp/issues").header("User-Agent", "OkHttp Headers.java").addHeader("Accept", "application/json; q=0.5").addHeader("Accept", "application/vnd.github.v3+json").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println("Server: " + response.header("Server"));System.out.println("Date: " + response.header("Date"));System.out.println("Vary: " + response.headers("Vary"));
}

Post方式提交String
使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。

private void postString() throws IOException {OkHttpClient client = new OkHttpClient();String postBody = ""+ "Releases\n"+ "--------\n"+ "\n"+ " * zhangfei\n"+ " * guanyu\n"+ " * liubei\n";Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println(response.body().string());}});}

Post方式提交流

以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。

public static final MediaType MEDIA_TYPE_MARKDOWN= MediaType.parse("text/x-markdown; charset=utf-8");private void postStream() throws IOException {RequestBody requestBody = new RequestBody() {@Overridepublic MediaType contentType() {return MEDIA_TYPE_MARKDOWN;}@Overridepublic void writeTo(BufferedSink sink) throws IOException {sink.writeUtf8("Numbers\n");sink.writeUtf8("-------\n");for (int i = 2; i <= 997; i++) {sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));}}private String factor(int n) {for (int i = 2; i < n; i++) {int x = n / i;if (x * i == n) return factor(x) + " × " + i;}return Integer.toString(n);}};Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(requestBody).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println(response.body().string());}});
}

Post方式提交表单

private void postForm() {OkHttpClient client = new OkHttpClient();RequestBody formBody = new FormBody.Builder().add("search", "Jurassic Park").build();Request request = new Request.Builder().url("https://en.wikipedia.org/w/index.php").post(formBody).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println(response.body().string());}});}

Post方式提交分块请求
MultipartBody 可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");private void postMultipartBody() {OkHttpClient client = new OkHttpClient();// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/imageMultipartBody body = new MultipartBody.Builder("AaB03x").setType(MultipartBody.FORM).addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),RequestBody.create(null, "Square Logo")).addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png"))).build();Request request = new Request.Builder().header("Authorization", "Client-ID " + IMGUR_CLIENT_ID).url("https://api.imgur.com/3/image").post(body).build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println(response.body().string());}});
}

响应缓存
为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。
一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttpClient(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。
响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.cache(cache);
OkHttpClient client = builder.build();Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();Call call = client.newCall(request);
call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String response1Body = response.body().string();System.out.println("Response 1 response:          " + response);System.out.println("Response 1 cache response:    " + response.cacheResponse());System.out.println("Response 1 network response:  " + response.networkResponse());}});

超时
没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

private void ConfigureTimeouts() {OkHttpClient.Builder builder = new OkHttpClient.Builder();OkHttpClient client = builder.build();client.newBuilder().connectTimeout(10, TimeUnit.SECONDS);client.newBuilder().readTimeout(10,TimeUnit.SECONDS);client.newBuilder().writeTimeout(10,TimeUnit.SECONDS);Request request = new Request.Builder().url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay..build();Call call = client.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println("Response completed: " + response);}});}

简单封装okHttp框架

新建一个工具类OkHttpUtils
OkHttpClient必须是单例的,所以这里我们需要使用到单例设计模式,私有化构造函数,提供一个方法给外界获取OkHttpUtils实例对象

public class OkHttpUtils {private  static  OkHttpUtils mInstance;private OkHttpClient mHttpClient;private OkHttpUtils() {};public static  OkHttpUtils getInstance(){return  mInstance;}}

一般网络请求分为get和post请求两种,但无论哪种请求都是需要用到request的,所以我们首先封装一个request,创建一个doRequest方法,在其内先编写mHttpClient.newCall(request).enqueue(new Callback())相关逻辑

public  void doRequest(final Request request){mHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {}});
}

我们需要自定义一个callback,BaseCallback,并将其传入request方法中

public class BaseCallback  {}

在OkHttpUtils中编写get和post方法

public void get(String url){}public void post(String url,Map<String,Object> param){}

post方法中构建request对象,这里我们需要创建一个buildRequest方法,用于生成request对象

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){return null;
}

这里需要定一个枚举对象HttpMethodType,用于区分是get还是post

enum  HttpMethodType{GET,POST,}

buildRequest方法根据HttpMethodType不同有相应的逻辑处理

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){Request.Builder builder = new Request.Builder().url(url);if (methodType == HttpMethodType.POST){builder.post(body);}else if(methodType == HttpMethodType.GET){builder.get();}return builder.build();}

builder.post()方法中需要一个body,所以我们需要创建一个方法builderFormData()方法用于返回RequestBody,这里内部逻辑后面再进行完善

private RequestBody builderFormData(Map<String,Object> params){return null;
}

于是buildRequest方法变成了这样

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){Request.Builder builder = new Request.Builder().url(url);if (methodType == HttpMethodType.POST){RequestBody body = builderFormData(params);builder.post(body);}else if(methodType == HttpMethodType.GET){builder.get();}return builder.build();}

get方法进行修改:

public void get(String url,BaseCallback callback){Request request = buildRequest(url,HttpMethodType.GET,null);doRequest(request,callback);}

post方法进行修改:

public void post(String url,Map<String,Object> params,BaseCallback callback){Request request = buildRequest(url,HttpMethodType.POST,params);doRequest(request,callback);
}

完善builderFormData()方法

private RequestBody builderFormData(Map<String,String> params){FormBody.Builder builder =  new FormBody.Builder();if(params!=null){for(Map.Entry<String,String> entry:params.entrySet()){builder.add(entry.getKey(),entry.getValue());}}return builder.build();
}

BaseCallback中定义一个抽象方法onBeforeRequest,这样做的理由是我们在加载网络数据成功前,一般都有进度条等显示,这个方法就是用来做这些处理的

public abstract class BaseCallback  {public  abstract void onBeforeRequest(Request request);}

OkHttpUtils的doRequest方法增加如下语句:

baseCallback.onBeforeRequest(request);

BaseCallback中多定义2个抽象方法

public abstract  void onFailure(Request request, Exception e) ;/***请求成功时调用此方法* @param response*/
public abstract  void onResponse(Response response);

由于Response的状态有多种,比如成功和失败,所以需要onResponse分解为3个抽象方法

/**** 状态码大于200,小于300 时调用此方法* @param response* @param t* @throws*/
public abstract void onSuccess(Response response,T t) ;/*** 状态码400,404,403,500等时调用此方法* @param response* @param code* @param e*/
public abstract void onError(Response response, int code,Exception e) ;/*** Token 验证失败。状态码401,402,403 等时调用此方法* @param response* @param code*/
public abstract void onTokenError(Response response, int code);

response.body.string()方法返回的都是String类型,而我们需要显示的数据其实是对象,所以我们就想抽取出方法,直接返回对象,由于我们不知道对象的类型是什么,所以我们在BaseCallback中使用范型

public abstract class BaseCallback<T>

BaseCallback中需要将泛型转换为Type,所以要声明Type类型

public   Type mType;

BaseCallback中需要如下一段代码,将泛型T转换为Type类型

static Type getSuperclassTypeParameter(Class<?> subclass)
{Type superclass = subclass.getGenericSuperclass();if (superclass instanceof Class){throw new RuntimeException("Missing type parameter.");}ParameterizedType parameterized = (ParameterizedType) superclass;return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}

在BaseCallback的构造函数中进行mType进行赋值

public BaseCallback()
{mType = getSuperclassTypeParameter(getClass());
}

OkHttpUtils中doRequest方法的onFailure与onResponse方法会相应的去调用baseCallback的方法

mHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {baseCallback.onFailure(request,e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()) {baseCallback.onSuccess(response,null);}else {baseCallback.onError(response,response.code(),null);}/*mGson.fromJson(response.body().string(),baseCallback.mType);*/}});

onResponse方法中成功的情况又有区分,根据mType的类型不同有相应的处理逻辑,同时还要考虑Gson解析错误的情况

@Override
public void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()) {String resultStr = response.body().string();if (baseCallback.mType == String.class){baseCallback.onSuccess(response,resultStr);}else {try {Object obj = mGson.fromJson(resultStr, baseCallback.mType);baseCallback.onSuccess(response,obj);}catch (com.google.gson.JsonParseException e){ // Json解析的错误baseCallback.onError(response,response.code(),e);}}}else {baseCallback.onError(response,response.code(),null);}}

构造函数中进行一些全局变量的初始化的操作,还有一些超时的设计

private OkHttpUtils() {mHttpClient = new OkHttpClient();OkHttpClient.Builder builder = mHttpClient.newBuilder();builder.connectTimeout(10, TimeUnit.SECONDS);builder.readTimeout(10,TimeUnit.SECONDS);builder.writeTimeout(30,TimeUnit.SECONDS);mGson = new Gson();};

静态代码块初始化OkHttpUtils对象

static {mInstance = new OkHttpUtils();
}

在okHttpUtils内,需要创建handler进行UI界面的更新操作,创建callbackSuccess方法

private void callbackSuccess(final  BaseCallback callback , final Response response, final Object obj ){mHandler.post(new Runnable() {@Overridepublic void run() {callback.onSuccess(response, obj);}});
}

doRequest方法的onResponse方法也进行相应的改写

if (baseCallback.mType == String.class){/*baseCallback.onSuccess(response,resultStr);*/callbackSuccess(baseCallback,response,resultStr);
}

创建callbackError方法

private void callbackError(final BaseCallback callback, final Response response, final Exception e) {mHandler.post(new Runnable() {@Overridepublic void run() {callback.onError(response, response.code(), e);}});
}

将doRequest方法的onResponse方法中的baseCallback.onError(response,response.code(),e);替换为callbackError(baseCallback,response,e);方法

@Override
public void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()) {String resultStr = response.body().string();if (baseCallback.mType == String.class){/*baseCallback.onSuccess(response,resultStr);*/callbackSuccess(baseCallback,response,resultStr);}else {try {Object obj = mGson.fromJson(resultStr, baseCallback.mType);/*baseCallback.onSuccess(response,obj);*/callbackSuccess(baseCallback,response,obj);}catch (com.google.gson.JsonParseException e){ // Json解析的错误/*baseCallback.onError(response,response.code(),e);*/callbackError(baseCallback,response,e);}}}else {callbackError(baseCallback,response,null);/*baseCallback.onError(response,response.code(),null);*/}}

至此,我们的封装基本完成。

OkHttp3源码分析

请求处理分析
当我们要请求网络的时候我们需要用OkHttpClient.newCall(request)进行execute或者enqueue操作,当我们调用newCall时:

/*** Prepares the {@code request} to be executed at some point in the future.*/
@Override public Call newCall(Request request) {return new RealCall(this, request);
}

实际返回的是一个RealCall类,我们调用enqueue异步请求网络实际上是调用了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));
}

最终的请求是dispatcher来完成的。

Dispatcher任务调度

Dispatcher的本质是异步请求的管理器,控制最大请求并发数和单个主机的最大并发数,并持有一个线程池负责执行异步请求。对同步的请求只是用作统计。他是如何做到控制并发呢,其实原理就在上面的2个execute代码里面,真正网络请求执行前后会调用executed和finished方法,而对于AsyncCall的finished方法后,会根据当前并发数目选择是否执行队列中等待的AsyncCall。并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也会触发这个过程。
Dispatcher主要用于控制并发的请求,它主要维护了以下变量:

/** 最大并发请求数*/
private int maxRequests = 64;
/** 每个主机最大请求数*/
private int maxRequestsPerHost = 5;
/** 消费者线程池 */
private ExecutorService executorService;
/** 将要运行的异步请求队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在运行的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在运行的同步请求队列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

构造函数

public Dispatcher(ExecutorService executorService) {this.executorService = executorService;
}public Dispatcher() {
}public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;
}

Dispatcher有两个构造函数,可以使用自己设定线程池,如果没有设定线程池则会在请求网络前自己创建线程池,这个线程池类似于CachedThreadPool比较适合执行大量的耗时比较少的任务。

异步请求

synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}
}

当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时则把请求加载到runningAsyncCalls中并在线程池中执行,否则就再入到readyAsyncCalls中进行缓存等待。

AsyncCall
线程池中传进来的参数就是AsyncCall它是RealCall的内部类,内部也实现了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);}}
}

首先我们来看看最后一行, 无论这个请求的结果如何都会执行client.dispatcher().finished(this);

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);
}/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {finished(runningSyncCalls, call, false);
}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();runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();}
}
finished方法将此次请求从runningAsyncCalls移除后还执行了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.}
}

可以看到最关键的点就是会从readyAsyncCalls取出下一个请求,并加入runningAsyncCalls中并交由线程池处理。好了让我们再回到上面的AsyncCall的execute方法,我们会发getResponseWithInterceptorChain方法返回了Response,很明显这是在请求网络。

Interceptor拦截器
在回到RealCall中,我们看到无论是execute还是enqueue,真正的Response是通过这个函数getResponseWithInterceptorChain获取的,其他的代码都是用作控制与回调。而这里就是真正请求的入口,也是到了OkHttp的一个很精彩的设计:Interceptor与Chain
看一下RealCall中的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);
}

这也是与旧版本不一致的地方,在3.4.x以前,没有这些内部的这些拦截器,只有用户的拦截器与网络拦截器。而Request和Response是通过HttpEngine来完成的。在RealCall实现了用户拦截器与RetryAndFollowUp的过程,而在HttpEngine内部处理了请求转换、Cookie、Cache、网络拦截器、连接网络的过程。值得一提的是,在旧版是获取到Response后调用网络拦截器的拦截。
而在这里,RealInterceptorChain会递归的创建并以此调用拦截器,去掉诸多异常,简化版代码如下:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,Connection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();calls++;// If we already have a stream, confirm that the incoming request will use it.if (this.httpStream != null && !sameConnection(request.url())) {throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)+ " must retain the same host and port");}// If we already have a stream, confirm that this is the only call to chain.proceed().if (this.httpStream != null && calls > 1) {throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)+ " must call proceed() exactly once");}// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpStream, connection, index + 1, request);Interceptor interceptor = interceptors.get(index);Response response = interceptor.intercept(next);// Confirm that the next interceptor made its required call to chain.proceed().if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {throw new IllegalStateException("network interceptor " + interceptor+ " must call proceed() exactly once");}// Confirm that the intercepted response isn't null.if (response == null) {throw new NullPointerException("interceptor " + interceptor + " returned null");}return response;
}

Chain与Interceptor会互相递归调用,直到链的尽头。
我们看到,通过职责链模式,清楚地切开了不同的逻辑,每个拦截器完成自己的职责,从而完成用户的网络请求。
大概流程是:
1)先经过用户拦截器
2)RetryAndFollowUpInterceptor负责自动重试和进行必要的重定向
3)BridgeIntercetor负责将用户Request转换成一个实际的网络请求的Request,再调用下层的拦截器获取Response,最后再将网络Response转换成用户的Reponse
4)CacheInterceptor负责控制缓存
5)ConnectInterceptor负责进行连接主机
6)网络拦截器进行拦截
7)CallServerInterceptor是真正和服务器通信,完成http请求

连接与通信
在RetryAndFollowUpInterceptor中,会创建StreamAllocation,然后交给下游的ConnectInterceptor

@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;Request request = realChain.request();StreamAllocation streamAllocation = realChain.streamAllocation();// We need the network to satisfy this request. Possibly for validating a conditional GET.boolean doExtensiveHealthChecks = !request.method().equals("GET");HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);RealConnection connection = streamAllocation.connection();return realChain.proceed(request, streamAllocation, httpStream, connection);
}

这里会创建一个HttpStream,并且取到一个RealConnection,继续交给下游的CallServerInterceptor。
我们跟踪进去看看,StreamAllocation里面做了什么

public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {int connectTimeout = client.connectTimeoutMillis();int readTimeout = client.readTimeoutMillis();int writeTimeout = client.writeTimeoutMillis();boolean connectionRetryEnabled = client.retryOnConnectionFailure();try {RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);HttpStream resultStream;if (resultConnection.framedConnection != null) {resultStream = new Http2xStream(client, this, resultConnection.framedConnection);} else {resultConnection.socket().setSoTimeout(readTimeout);resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);resultStream = new Http1xStream(client, this, resultConnection.source, resultConnection.sink);}synchronized (connectionPool) {stream = resultStream;return resultStream;}} catch (IOException e) {throw new RouteException(e);}
}

这里的代码逻辑是这样的,找一个健康的连接,设置超时时间,然后根据协议创建一个HttpStream并返回。
继续跟进去看findHealthyConnection:

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)throws IOException {while (true) {RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);// If this is a brand new connection, we can skip the extensive health checks.synchronized (connectionPool) {if (candidate.successCount == 0) {return candidate;}}// Do a (potentially slow) check to confirm that the pooled connection is still good. If it// isn't, take it out of the pool and start again.if (!candidate.isHealthy(doExtensiveHealthChecks)) {noNewStreams();continue;}return candidate;}
}

上面的逻辑也很简单,在findConnection中找一个连接,然后做健康检查,如果不健康就回收,并再次循环,那么真正寻找连接的代码就在findConnection里面了:

/*** Returns a connection to host a new stream. This prefers the existing connection if it exists,* then the pool, finally building a new connection.*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {Route selectedRoute;synchronized (connectionPool) {if (released) throw new IllegalStateException("released");if (stream != null) throw new IllegalStateException("stream != null");if (canceled) throw new IOException("Canceled");RealConnection allocatedConnection = this.connection;if (allocatedConnection != null && !allocatedConnection.noNewStreams) {return allocatedConnection;}// Attempt to get a connection from the pool.RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);if (pooledConnection != null) {this.connection = pooledConnection;return pooledConnection;}selectedRoute = route;}if (selectedRoute == null) {selectedRoute = routeSelector.next();synchronized (connectionPool) {route = selectedRoute;refusedStreamCount = 0;}}RealConnection newConnection = new RealConnection(selectedRoute);acquire(newConnection);synchronized (connectionPool) {Internal.instance.put(connectionPool, newConnection);this.connection = newConnection;if (canceled) throw new IOException("Canceled");}newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),connectionRetryEnabled);routeDatabase().connected(newConnection.route());return newConnection;
}

这里大概分成分成3大步:
1)如果当前有连接并且符合要求的话,就直接返回
2)如果线程池能取到一个符合要求的连接的话,就直接返回
3)如果Route为空,从RouteSelector取一个Route,然后新建一个RealConnection,并放入ConnectionPool,随后调用connect,再返回

也就是说不管当前走的是步骤1还是2,一开始一定是从3开始的,也就是在RealConnection的connect中真正完成了socket连接。
connect里面代码比较长,真正要做的就是一件事,如果是https请求并且是http代理,则建立隧道连接,隧道连接请参考RFC2817,否则建立普通连接。
这两者都调用了2个函数:connectSocket(connectTimeout, readTimeout); establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
但是隧道连接则多了一个代理认证的过程,可能会反复的connectSocket和构造请求。
看一下connectSocket:

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {Proxy proxy = route.proxy();Address address = route.address();rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP? address.socketFactory().createSocket(): new Socket(proxy);rawSocket.setSoTimeout(readTimeout);try {Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);} catch (ConnectException e) {throw new ConnectException("Failed to connect to " + route.socketAddress());}source = Okio.buffer(Okio.source(rawSocket));sink = Okio.buffer(Okio.sink(rawSocket));
}

就是根据Route来创建socket,在connect,随后将rawSocket的InputStream与OutputStream包装成Source与Sink。这里提一下,OkHttp是依赖Okio的,Okio封装了Java的IO API,如这里的Source与Sink,非常简洁实用。

而establishProtocol里,如果是https则走TLS协议,生成一个SSLSocket,并进行握手和验证,同时如果是HTTP2或者SPDY3的话,则生成一个FrameConnection。这里不再多提,HTTP2和HTTP1.X大相径庭,我们这里主要是分析HTTP1.X的连接,后面有机会我们会单独开篇讲HTTP2。同时TLS相关的话题这里也一并略过,想了解的朋友可以看一看相应的Java API和HTTPS连接的资料。

再回到StreamAllcation.newStream的代码resultStream = new Http1xStream( client, this, resultConnection.source, resultConnection.sink);实质上HttpStream其实就是Request和Response读写Socket的抽象,我们看到Http1xStream取到了Socket输入输出流,随后在CallServerInterceptor可以拿来做读写。

我们看CallServerInterceptor做了什么:

@Override public Response intercept(Chain chain) throws IOException {HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();Request request = chain.request();long sentRequestMillis = System.currentTimeMillis();httpStream.writeRequestHeaders(request);if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);request.body().writeTo(bufferedRequestBody);bufferedRequestBody.close();}httpStream.finishRequest();Response response = httpStream.readResponseHeaders().request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();if (!forWebSocket || response.code() != 101) {response = response.newBuilder().body(httpStream.openResponseBody(response)).build();}if ("close".equalsIgnoreCase(response.request().header("Connection"))|| "close".equalsIgnoreCase(response.header("Connection"))) {streamAllocation.noNewStreams();}int code = response.code();if ((code == 204 || code == 205) && response.body().contentLength() > 0) {throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());}return response;
}

CallServerInterceptor顾名思义,就是真正和Server进行通信的地方。这里也是按照HTTP协议,依次写入请求头,还有根据情况决定是否写入请求体。随后读响应头闭构造一个Response。
里面具体是如何实现呢,我们看Http1xStream:
首先是写头:

@Override public void writeRequestHeaders(Request request) throws IOException {String requestLine = RequestLine.get(request, streamAllocation.connection().route().proxy().type());writeRequest(request.headers(), requestLine);
}

构造好请求行,进入writeRequest:

/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);sink.writeUtf8(requestLine).writeUtf8("\r\n");for (int i = 0, size = headers.size(); i < size; i++) {sink.writeUtf8(headers.name(i)).writeUtf8(": ").writeUtf8(headers.value(i)).writeUtf8("\r\n");}sink.writeUtf8("\r\n");state = STATE_OPEN_REQUEST_BODY;
}

这里就一目了然了,就是一行行的写请求行和请求头到sink中
再看readResponse:

/** Parses bytes of a response header from an HTTP transport. */
public Response.Builder readResponse() throws IOException {if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {throw new IllegalStateException("state: " + state);}try {while (true) {StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());Response.Builder responseBuilder = new Response.Builder().protocol(statusLine.protocol).code(statusLine.code).message(statusLine.message).headers(readHeaders());if (statusLine.code != HTTP_CONTINUE) {state = STATE_OPEN_RESPONSE_BODY;return responseBuilder;}}} catch (EOFException e) {// Provide more context if the server ends the stream before sending a response.IOException exception = new IOException("unexpected end of stream on " + streamAllocation);exception.initCause(e);throw exception;}
}

也是一样的,从source中读请求行和请求头
最后看openResponseBody:

@Override public ResponseBody openResponseBody(Response response) throws IOException {Source source = getTransferStream(response);return new RealResponseBody(response.headers(), Okio.buffer(source));
}

这里说一下就是根据请求的响应把包裹InputStream的source再次封装,里面做一些控制逻辑,然后再封装成ResponseBody。
例如FiexdLengthSource,就是期望获取到byte的长度是固定的值:

/** An HTTP body with a fixed length specified in advance. */
private class FixedLengthSource extends AbstractSource {private long bytesRemaining;public FixedLengthSource(long length) throws IOException {bytesRemaining = length;if (bytesRemaining == 0) {endOfInput(true);}}@Override public long read(Buffer sink, long byteCount) throws IOException {if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);if (closed) throw new IllegalStateException("closed");if (bytesRemaining == 0) return -1;long read = source.read(sink, Math.min(bytesRemaining, byteCount));if (read == -1) {endOfInput(false); // The server didn't supply the promised content length.throw new ProtocolException("unexpected end of stream");}bytesRemaining -= read;if (bytesRemaining == 0) {endOfInput(true);}return read;}@Override public void close() throws IOException {if (closed) return;if (bytesRemaining != 0 && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {endOfInput(false);}closed = true;}
}

当读完期望的长度时就把这个RealConnection回收,如果少于期望的长度则抛异常。

ConnectionPool
到了OkHttp3时代,ConnectionPool就是每个Client独享的了,我们刚才提到了ConnectionPool,那么他到底是如何运作呢。
ConnectionPool持有一个静态的线程池。
StreamAllocation不管通过什么方式,在获取到RealConnection后,RealConnection会添加一个对StreamAllocation的引用。
在每个RealConnection加入ConnectionPool后,如果当前没有在清理,就会把cleanUpRunnable加入线程池。
cleanUpRunnable里面是一个while(true),一个循环包括:
调用一次cleanUp方法进行清理并返回一个long, 如果是-1则退出,否则调用wait方法等待这个long值的时间
cleanUp代码如下:

ong cleanup(long now) {int inUseConnectionCount = 0;int idleConnectionCount = 0;RealConnection longestIdleConnection = null;long longestIdleDurationNs = Long.MIN_VALUE;// Find either a connection to evict, or the time that the next eviction is due.synchronized (this) {for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {RealConnection connection = i.next();// If the connection is in use, keep searching.if (pruneAndGetAllocationCount(connection, now) > 0) {inUseConnectionCount++;continue;}idleConnectionCount++;// If the connection is ready to be evicted, we're done.long idleDurationNs = now - connection.idleAtNanos;if (idleDurationNs > longestIdleDurationNs) {longestIdleDurationNs = idleDurationNs;longestIdleConnection = connection;}}if (longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections) {// We've found a connection to evict. Remove it from the list, then close it below (outside// of the synchronized block).connections.remove(longestIdleConnection);} else if (idleConnectionCount > 0) {// A connection will be ready to evict soon.return keepAliveDurationNs - longestIdleDurationNs;} else if (inUseConnectionCount > 0) {// All connections are in use. It'll be at least the keep alive duration 'til we run again.return keepAliveDurationNs;} else {// No connections, idle or in use.cleanupRunning = false;return -1;}}closeQuietly(longestIdleConnection.socket());// Cleanup again immediately.return 0;
}

遍历每一个RealConnection,通过引用数目确定哪些是空闲的,哪些是在使用中,同时找到空闲时间最长的RealConnection。
如果空闲数目超过最大空闲数或者空闲时间超过最大空闲时间,则清理掉这个RealConnection,并返回0,表示需要立刻再次清理
否则如果空闲的数目大于0个,则等待最大空闲时间-已有的最长空闲时间
否则如果使用中的数目大于0,则等待最大空闲时间
否则 返回 -1,并标识退出清除状态
同时如果某个RealConnection空闲后,会进入ConnectionPool.connectionBecameIdle方法,如果不可被复用,则被移除,否则立刻唤醒上面cleanUp的wait,再次清理,因为可能超过了最大空闲数目
这样通过一个静态的线程池,ConnectionPool做到了每个实例定期清理,保证不会超过最大空闲时间和最大空闲数目的策略。

OkHttp3分析就到此结束了。

最后,推送一下自己的微信公众号,喜欢的同学可以关注。

深入解析OkHttp3相关推荐

  1. OKHttp3的使用和详解

    一.概述 OKHttp是处理网络请求的开源框架,Andorid当前最火热的网络框架,Retrofit的底层也是OKHttp,用于替换HttpUrlConnection和Apache HttpClien ...

  2. OkHttp3源码详解

    前言:为什么有些人宁愿吃生活的苦也不愿吃学习的苦,大概是因为懒惰吧,学习的苦是需要自己主动去吃的,而生活的苦,你躺着不动它就会来找你了. 一.概述 OKHttp是一个非常优秀的网络请求框架,已经被谷歌 ...

  3. 都2023年了,Android凉了没?

    作为Android开发人员,经常会看到类似的问题 Android凉了没? Android开发前景如何? 其实并不是Android凉了,而是技术不过硬的Android凉了 Android不仅现在没凉,在 ...

  4. 适合Android进阶人群的全套系列学习资料,限时免费开源

    如今Android领域技术分工越来越细,大部分企业需要更加专精的人才,加之行业不景气,初级开发找到好工作的难度无疑大大增加了. 为此,也有很多伙伴留言私信,希望我能出一期适用于Android进阶的学习 ...

  5. OkHttp3源码解析(三)——连接池复用

    OKHttp3源码解析系列 OkHttp3源码解析(一)之请求流程 OkHttp3源码解析(二)--拦截器链和缓存策略 本文基于OkHttp3的3.11.0版本 implementation 'com ...

  6. DASH流媒体MPD文件解析

    MPD文件本质是XML文件,其说明了DASH服务端流媒体视频切片的相关信息,包含分辨率,大小,帧率等等,因此在DASH客户端实现的第一步便是解析服务端的MPD文件,其常见结构如图所示: MPD文件来源 ...

  7. Java爬取解析去哪儿景点信息

    前言:这两周在做 Web 课的大作业,顺便琢磨了一下如何使用 Java 从网上获取一些数据,现在写这篇博客记录一下. PS:这里仅限交流学习用,如利用代码进行恶意攻击他网站,和作者无关!!! Java ...

  8. android端使用http2.0,android Retrofit2+okHttp3使用总结

    使用前准备 Build.gradle文件配置 dependencies配置 compile 'com.squareup.retrofit2:retrofit:2.0.0' compile 'com.s ...

  9. Android进阶:七、Retrofit2.0原理解析之最简流程【上】

    retrofit 已经流行很久了,它是Square开源的一款优秀的网络框架,这个框架对okhttp进行了封装,让我们使用okhttp做网路请求更加简单.但是光学会使用只是让我们多了一个技能,学习其源码 ...

最新文章

  1. 自学python系列10:python的函数和函数式编程
  2. Android之jni编译出现no matching function for call to ‘_JNIEnv::GetJava(JNIEnv* , Java VM**)‘解决办法)‘
  3. SpringMVC 中整合JSON、XML视图二
  4. 1074: [SCOI2007]折纸origami - BZOJ
  5. 自然数从1到n之间,有多少个数字含有1
  6. [LeetCode-JAVA] Remove Duplicates from Sorted Array II
  7. Google Cloud Fundamentals简介
  8. mysql必知必会第4版pdf百度网盘_SQL必知必会 第4版pdf
  9. Java的二级动态菜单实现
  10. access mysql连接字符串_access 数据库连接字符串
  11. 抖音去水印,快手去水印,皮皮虾去水印操作方法(2019.6.12有效)
  12. aecmap快捷键_ARCMAP快捷键总结
  13. lombok导入报错,版本1.18.12已在maven本地仓库中
  14. 【毕业设计】大数据大众点评评论文本分析 - python 数据挖掘
  15. 用arduino mega2560通过isp给 arduino uno烧录程序
  16. cad怎么画坐标系箭头_怎样用CAD画一个路标箭头?,来学习吧
  17. 云linux界面设计,开源企业级的UI设计语言Ant Design 3.10.2发布下载
  18. 「Jenkins Pipeline」- 在 Jenkinsfile 中使用共享库 @20210306
  19. 易语言单窗口单ip软件源码_诺亚传说挂机多开小号搬砖防IP限制检测封号技巧...
  20. 电感,电容的单位换算

热门文章

  1. Android-自定义圆形ProgressBar加载
  2. Weak Cryptography (crypto) 弱密码
  3. java开发webservice简单实例_jsp实现的webservice的简单实例
  4. Linux 硬件响应性能检测工具 硬盘IO测试工具 iozone ioping fio dd
  5. Python基础知识(八):模块化、标准库、第三方库
  6. 平台化TestStand常遇到的一些问题解决方案
  7. HTML5 - 各大浏览器对html5的支持情况
  8. 自动将BAT文件转换为EXE
  9. 江苏机器人竞赛南航_关于举办南航金城学院首届大学生机器人比赛暨江苏省大学生机器人大赛院内选拔赛的通知...
  10. GAMLSS拟合连续分布练习1-代码及注释