今儿个咱们就来看看到底okhttp内部是如何实现的,这篇文章咱从okhttp整体框架方面出发,解析okhttp的源码。
okhttp框架源码地址: github.com/square/okht…
如何使用 okhttp
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
try {
//1.同步请求方式
Response response = call.execute();
//2.异步请求方式
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.info("cozing", "交易失败");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.info("cozing", "交易成功");
}
});
} catch (IOException e) {
e.printStackTrace();
}
这是使用okhttp发送一个简单通信流程,其中包括同步请求和异步请求:
- 同步请求调用的方法是
call.execute(),内部采用的是线程阻塞方式直接将结果返回到Response,后面咱们会详细讲解; - 异步请求调用的方法是
call.enqueue(Callback callback),该方法需要传入一个Callback等待结果回调的接口,交易结果在onFailure()(失败)和onResponse()(成功)中返回。
那么,接下来咱们来看看okhttp的源码的整体流程。
整体架构流程图
接下来咱们将根据这个整体架构图来来看看okhttp的内部实现。

流程走读
创建OkHttpClient
首先创建OkHttpClient对象,OkHttpClient是okhttp框架的客户端,用于发送http请求(Requests)和读取交易返回数据(Responses)。官方建议使用单例创建OkHttpClient,即一个进程中只创建一次即可,以后的每次交易都使用该实例发送交易。这是因为OkHttpClient拥有自己的连接池和线程池,这些连接池和线程池可以重复使用,这样做利于减少延迟和节省内存,如果咱们每次发交易都创建一个OkHttpClient的话,将会浪费很多内存资源。
创建方式:
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
还有一种方式创建OkHttpCleint对象的方式:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
这两种创建方式内部实现,第一种采用构建者模式创建OkHttpClient对象,这种方式可以自定义Builder内部的每一个参数属性,第二种采用普通的创建实例方式创建一个OkHttpClient对象,内部创建了一个默认的Builder,因此这种方式使用默认Builder的内部属性。
创建Call对象
一个Call对象表示一次请求,每一次交易请求都会生产一个新的Call,Call其实是一个接口对象,它的具体实现类是RealCall
Call的创建过程:
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
可以看到在创建Call对象时候传进去了一个Request对象,Request对象表示用户的交易请求参数,咱们看看它的内部实现:

- url:交易请求地址;
- method:请求方式,比如:get/post/http...等请求方式;
- headers:请求头;
- body:请求体;
咱们这时看看Call的创建方法client.newCall(request)的内部实现:

RealCall()

RealCall对象。
请求交易
okhttp中提供了两种请求方式:一种是同步请求,第二种是异步请求。同步请求调用call.execute()方法,异步请求调用call.enqueue(Callback callback)方法,
在看两个请求方式的实现之前,咱们先来看okhttp中一个重要成员Dispatcher(调度器):
Dispatcher(调度器)
Dispatcher是okhttp的任务调度核心类,负责管理同步和异步的请求,管理每一个请求任务的请求状态,并且其内部维护了一个线程池用于执行相应的请求,Dispatcher的实现框架图:


Dispatcher当成生产者,把线程池当成消费者,当生产者生产的线程大于消费者所能承受的最大范围,就把未能及时执行的任务保存在readyAsyncCalls队列中,当时机成熟,也就是线程池有空余线程可以执行时,会调用promoteCall()这个方法把等待队列中的任务取出放到线程池中执行,并且把这个任务转移到runningAsyncCalls队列中去。
接下来咱们分别看看同步请求和异步请求的实现过程,并详细说一下他们是如何实现的。
同步交易请求
call.execute()实现方式:


Dispatcher调度器的executed()方法,继续看Dispatcher的实现:

RealCall存进了Deque队列,Deque是一个双向队列接口,Deque接口具有丰富的抽象数据形式,它支持从队列两端点检索和插入元素,在此不对其做过多讲解。
接下来看的client.dispatcher().finished(this),不管结果请求结果如何,都会调用finally中的client.dispatcher().finished(this)将本次请求从队列中移除。
接下来调用到getResponseWithInterceptorChain()方法:

异步交易请求
call.enqueue(Callback callback)实现方式:

dispatcher的equeue(new AsyncCall(responseCallback))方法,该方法需要传入一个AsyncCall的对象。
接下来看dispatcher的equeue(new AsyncCall(responseCallback))方法的实现:

executorService()的execute(call)方法;两者中只要有一个条件不成立,就会调用redyAsncCalls.add(call)将表示此次请求的call对象存在readyAsyncCalls队列中,readyAsyncCalls表示已准备好并等待执行请求的队列,当有空闲网络请求线程时,会从该队列中取出并执行网络请求。
接下来看executorService().execute(call)

通过上图可以知道这个call的实现对象类型是AsyncCall,来看看内部实现:

AsyncCall是RealCall的一个内部类,继承自NamedRunnable,再看NamedRunnable

AsyncCall就是一个Runnable的实现,用来开启一个线程,当网络请求线程池执行该线程的run()方法时,会调用AsyncCall的execute()的方法,最后在execute()方法内部调用了和上面咱们分析的同步请求方法一样的getResponseWithInterceptorChain()。
getResponseWithInterceptorChain()/拦截器链
通过上面的分析咱们知道不管是同步请求还是异步请求,最后都会走getResponseWithInterceptorChain()方法,getResponseWithInterceptorChain()是okhttp中的精髓设计之一,那么现在咱们来看看这个方法的内部实现:

这些拦截器包括:
- 用户自定义的拦截器
- retryAndFollowUpInterceptor:重试和重定向拦截器,主要负责网络失败重连。
- BridgeInterceptor:主要负责添加交易请求头。
- CacheInterceptor:缓存拦截器,主要负责拦截缓存。
- ConnectInterceptor:网络连接拦截器,主要负责正式开启http请求。
- CallServerInterceptor:负责发送网络请求和读取网络响应。
有关每个拦截器的具体实现和内部流程,读者可自行阅读源码了解,这篇文章咱们主要还是分析okhttp的整体架构。
根据上面的源码可以看到,最后交给了RealInterceptorChain(拦截器链类)这个真正的处理类,并调用RealInterceptorChain``的proceed()`方法来实现,具体实现:


intercept(next)方法,只有当前拦截器的response返回有结果时,才会执行下一个拦截器,因此得出结论:下一个拦截器依赖于当前拦截器的返回,可以保证拦截器的依次执行。
在拦截器链中执行的结果,在同步请求中会直接在response返回,而异步请求:

Callback的onReponse回调给用户。
总结
至此,okhttp的整体架构分析完毕,建议可以跟着源码一步步去理解,去了解okhttp的设计思想,然后应用到项目开发中。当然,okhttp是一个很庞大的一个框架,这篇文章主要是从它的整体架构方面对其做了简单的分析,内部的实现逻辑和思想都很值得认真思考和细细品味。
点关注,不迷路
文章每周持续更新,可以微信搜索「 十分钟学编程 」第一时间阅读和催更,如果这个文章写得还不错,觉得有点东西的话 ~求点赞👍 求关注❤️ 求分享❤️
各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
