OkHttp同步请求步骤:

  1. 创建OkHttpClient,客户对象
  2. 创建Request,请求主体,在请求主体设置请求的url,超时时间等
  3. 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的execute()发起同步请求。
  4. execute()返回的是Response对象。可以用execute().body().toString()得到请求所返回的主体内容。
val client = OkHttpClient()
val request = Request.Builder().url("https://www.baidu.com").build()
val response = client.newCall(request).execute().body().toString()

注意:发送请求后,就会进入阻塞状态,直到收到响应。

OkHttp异步请求步骤:

  1. 创建OkHttpClient,客户对象
  2. 创建Request,请求主体,在请求主体设置请求的url,超时时间等
  3. 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的enqueue()发起异步请求。
  4. enqueue(object: Callback{重写onFailure、onResponse方法}) 在onResponse方法中获取申请数据内容。
val client = OkHttpClient()
val request = Request.Builder().url("https://www.baidu.com").build()
val response = client.newCall(request).enqueue(object: Callback {override fun onFailure(call: Call, e: IOException) {TODO("Not yet implemented")}override fun onResponse(call: Call, response: Response) {response.body().toString()}
})

源码分析:

注意:下面的源代码段可能来自不同一个类文件,只是将他们放一起,容易观察,主要放一些关键代码,其他会有...代替。

1.关于创建OkHttpClient对象,下面源码:

public OkHttpClient() {this(new Builder());}public Builder() {dispatcher = new Dispatcher();   protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;   ......connectionPool = new ConnectionPool();.....}

可以看到OkHttp采用了建造者模式,在Builder()里面封装各种需要的属性,关键的主要有dispatcher分发器,connectionSpecs决定是异步还是同步,connectionPool 连接池。连接池具体到连接拦截器才会使用到,每个连接都会放入连接池中,由它进行管理。

总结新建Client对象时,新建了一个分发器和一个连接池,还有一些属性的初始化。

2.创建Request对象时,源码:

public Builder() {this.method = "GET";this.headers = new Headers.Builder();}Request(Builder builder) {this.url = builder.url;this.method = builder.method;this.headers = builder.headers.build();this.body = builder.body;this.tags = Util.immutableMap(builder.tags);}

可以看到Request里面也有一个Builder类,Builder构造函数默认请求方式为Get,还有对请求头部的封装。

总结:新建一个Request对象里面主要封装了请求路径,头部信息等。

3.用newCall(request)将Reuqest对象封装成Call对象时,源码:

  @Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}//可以看到newCall方法里面是调用了RealCall类的newRealCall方法,下面到RealCall类里看看。static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// 调用RealCall的构造函数RealCall call = new RealCall(client, originalRequest, forWebSocket);call.eventListener = client.eventListenerFactory().create(call);return call;}//下面是RealCall类构造函数private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {this.client = client;this.originalRequest = originalRequest;this.forWebSocket = forWebSocket;this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);......}

总结:newCall方法实际生成RealCall对象,对象里面包含了Client客户对象和Request的请求对象,还新建了一个RetryAndFollowUpInterceptor 重定向拦截器。

4.Call对象调用的execute()同步请求方法,源码:

@Override public Response execute() throws IOException {.....
//开启事件监听eventListener.callStart(this);   try {
//分发器用executed方法将Call对象添加进同步运行队列client.dispatcher().executed(this);
//结果是从拦截器链方法中获取的Response result = getResponseWithInterceptorChain();  ......} finally {
//finish方法里将Call对象从Calls队列中移出client.dispatcher().finished(this);  }
}//下面进到client.dispatcher().executed(this)的excuted方法里面synchronized void executed(RealCall call) {
//runningSyncCalls是正在运行的同步队列runningSyncCalls.add(call);  }

总结:excute()同步申请方法,分发器将Call对象添加到同步运行队列。请求数据从Response result = getResponseWithInterceptorChain();  中获取。

5.enqueue异步请求方法,源码:

@Override public void enqueue(Callback responseCallback) {
//判断是否请求过这个Call对象synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}
//异常检测captureCallStackTrace();
//事件监听eventListener.callStart(this);
//调用分发器的enqueue方法,分发器在client创建时新建的。client.dispatcher().enqueue(new AsyncCall(responseCallback));}

可以看到enqueue方法里面又调用了分发器的enqueue方法,在enqueue方法里新建了一个AsyncCall对象,

AsyncCall对象传入我们上一层传入enqueue方法的CallBack对象。

接下来看看上面的AsyncCall类是什么东西。

final class AsyncCall extends NamedRunnable {private final Callback responseCallback;AsyncCall(Callback responseCallback) {super("OkHttp %s", redactedUrl());this.responseCallback = responseCallback;}...........
}

看到AsyncCall继承自NamedRunnable,再来看看NamedRunnable是什么东西

public abstract class NamedRunnable implements Runnable {.....@Override public final void run() {String oldName = Thread.currentThread().getName();Thread.currentThread().setName(name);try {execute();} finally {Thread.currentThread().setName(oldName);}}.....
}

可以看到NamedRunnable实现了Runnable接口,里面最核心的就是在run方法里面运行了execute()方法,这个方法的具体实现其实跟同步请求execute方法一样,在AsyncCall类里,和同步请求最后的execute()是同一个方法。

@Override protected void execute() {.......Response response = getResponseWithInterceptorChain(); .....}

我把大部分代码都省了,最重要的就上面那句,跟同步请求一样,最后结果也是经过一系列拦截器的方法后的数据。

那么同步跟异步有什么区别呢?

异步传入enqueue方法的CallBack的对象实现了Runnable接口,让它在子线程中运行。

还有,接下来回到开头看看client.dispatcher().enqueue(new AsyncCall(responseCallback));这句,分发器类里的变量和它的enqueue方法(刚刚看的是AsyncCall类)。

public final class Dispatcher {//默认的最大并发请求量 private int maxRequests = 64;//单个host支持的最大并发量private int maxRequestsPerHost = 5;  .........//异步等待队列private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//异步运行队列private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//同步运行队列private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();...........//计算队列内请求数量的方法,如果异步请求满足不超过64,5的条件则进行请求操作。//有的版本OkHttp是通过promoteAndExecute()进行条件判断,原理差不多synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {//把Call对象添加进runningAsyncCalls异步进行队列runningAsyncCalls.add(call);//创建线程池并执行Call请求executorService().execute(call);} else {readyAsyncCalls.add(call);}}......
}

分发器对Request类型进行判断,把Call对象添加进readyAsyncCalls异步等待队列或runningAsyncCalls,而在同步请求时分发器是把Call对象直接添加到runningSyncCalls同步运行队列。异步请求最后开启线程池获取数据。

总结:enqueue方法传入CallBack对象,CallBack对象被封装为AsyncCall,AsyncCall内部实现了Runnable接口,分发器进行判断,如果符合条件就把AsyncCall传入了异步进行对列,开启线程池在子线程获取数据。否则添加进异步等待队列。

readyAsyncCalls异步等待队列的请求什么时候能运行呢?

我们已经知道异步跟同步请求通过分发器分发队列,但是最后都要经过AsyncCall类的execute()方法来获取数据,execute()方法最后finally里面运行client.dispatcher().finished(this);方法,我们进去finished方法看看。

//异步请求的finished方法
void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);}
//同步请求的finished方法void finished(RealCall call) {finished(runningSyncCalls, call, false);}
//具体的finished方法private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {
//将实现的Call对象从队列中移出if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//注意promoteCalls是第三个参数,既如果是异步请求才会运行该方法,重新整理队列。if (promoteCalls) promoteCalls();runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();}}

进入promoteCalls()方法

  private void promoteCalls() {if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//循环到队列最后一个元素,call为最后一个请求for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();
//如果符合条件就把call从等待队列移除加入运行队列。if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}

总结:在readyAsyncCalls队列中的请求会在异步请求的finished方法里进行判断,如果符合条件则进入runningAsyncCalls。

Dispatcher分发器:

从上面的Dispatcher类可以看出分发器有3个队列,异步有两个队列是运用了消费者模式,类中还有excuted,enqueue,finished方法,Dispatcher主要作用就是根据Request类型将Call对象调入不同队列,最后用finished将完成的请求移除队列并把等待的请求调进运行队列。

ExecutorService线程池:

下面进到executorService().execute(call)看看,

  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;}

第一个参数代表核心线程数量,为0就代表线程空闲之后不会被保留,会被销毁;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。

第二个参数是int整数的最大值,他表示的是线程池中可以容纳的最大线程数量。但是确实它得满足64跟5的条件。

第三个keepAliveTime,当我们的线程池中线程数量大于核心线程数量时,空闲线程需要等待60秒的时间才会被终止。

OkHttp拦截器

我们已经知道了请求最后都是从Response result = getResponseWithInterceptorChain()这句中获取的数据。

  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 (!forWebSocket) {interceptors.addAll(client.networkInterceptors());}interceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);}

总归创建了6个拦截器

全部拦截器的基本流程:

  1. 在发起请求前对request进行处理。
  2. 调用chain.proceed()方法,获取下一个拦截器的response。
  3. 对reponse进行处理,返回给上一个拦截器。

RetryAndFollowUpInterceptor 重定向拦截器

负责失败重连的拦截器。

  1. 创建StreamAllocation对象,但是没有使用,它的使用是在ConnectInterceptor。它负责为一次“请求”寻找“连接”并建立“流”。Connection是建立在Socket之上的物理通信信道,而Stream则是代表逻辑的流,如果有多个stream(即多个 Request) 都是连接在一个 host 和 port上,那么它们就可以共同使用同一个 socket ,这样做的好处就是可以减少TCP的一个三次握手的时间。
  2. 调用RealInterceptorChain.proceed(...)进行网络请求
  3. 根据异常结果或响应结果判断是否进行重新请求(20次)
  4. 调用下一个拦截器,对response进行处理,返回上一个拦截器

BridgeInterceptor  桥拦截器

该拦截器是链接客户端代码和网络代码的桥梁,它首先将客户端构建的Request对象信息构建成真正的网络请求;然后发起网络请求,最后就是讲服务器返回的消息封装成一个Response对象。

  1. 将用户构建的Request请求转化为能够进行网络访问的请求
  2. 执行符合条件的请求
  3. 将Response转化为用户可用的Response,OkHttp支持Gzip压缩,GzipSource类对数据进行解压。

CacheInterceptor 缓存拦截器

该拦截器用于处理缓存的功能,主要取得缓存 response 返回并刷新缓存。

  1. 底层使用的是 DiskLruCache 缓存机制。
  2. CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  3. get() 方法获取一个 CacheStrategy 对象。CacheStrategy,它是一个策略器,负责判断是使用缓存还是请求网络获取新的数据。
  4. responseCache.put(userResponse);
  5. put(userResponse)方法将 userResponse 缓存到本地。

为什么需要缓存 Response?

  • 客户端缓存就是为了下次请求时节省请求时间,可以更快的展示数据。
  • OKHTTP 支持缓存的功能

ConnectInterceptor  连接拦截器

该拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpCodec它包括通向服务器的输入流和输出流。

  1. 获取到第一个拦截器生成的StreamAllocation对象,
  2. 通过StreamAllocation对象,streamAllocation.newStream()创建HttpCodec对象,
  3. streamAllocation.connection()获取一个RealConnection对象
  4. 将HttpCodec、RealConnection对象传递给拦截器

NetworkInterceptors 网络拦截器

  • 允许像重定向和重试一样操作中间响应。
  • 网络发生短路时不调用缓存响应。
  • 在数据被传递到网络时观察数据。
  • 有权获得装载请求的连接。

CallServerInterceptor 调用服务拦截器

该拦截器的功能使用 HttpCodec与服务器进行数据的读写操作的。

  1. 首先是获取了httpCodec对象,该对象的主要功能就是对不同http协议(http1.1和http/2)的请求和响应做处理,该对象的初始化是在ConnectIntercepor的intercept里面
  2. OkHttp通过OKIO的Sink对象(该对象可以看做Socket的OutputStream对象)的writeRequest来向服务器发送请求的。
  3. 将OKIO的Source对象作为输入流InputStream对象读取数据封装为Response对象。
  4. 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。

OKHTTP的责任链模式优点:

  • 可以降低逻辑的耦合,相互独立的逻辑写到自己的拦截器中,也无需关注其它拦截器所做的事情。
  • 扩展性强,可以添加新的拦截器。

当然它也有缺点:

  • 因为调用链路长,而且存在嵌套,遇到问题排查其它比较麻烦。

ConnectionPool

OkHttp中所有的连接(RealConnection)都是通过ConnectionPool来管理。

  1. StreamAllocation里面包含了RealConnection对象,该对象归根是由ConnectionPool的get() 方法遍历 connections 中的所有 RealConnection 寻找同时满足条件的RealConnection,重复利用RealConnection。
  2. ConnectionPool类里put方法,采用GC回收算法,异步触发清理任务,然后将健康的connection添加到connections队列中。调用cleanup方法执行清理,并等待一段时间,持续清理,其中cleanup方法返回的值来来决定而等待的时间长度。

Android—OkHttp同步异步请求过程源码分析与拦截器相关推荐

  1. Android应用程序启动Binder线程源码分析

    Android的应用程序包括Java应用及本地应用,Java应用运行在davik虚拟机中,由zygote进程来创建启动,而本地服务应用在Android系统启动时,通过配置init.rc文件来由Init ...

  2. Android App签名(证书)校验过程源码分析

      Android App安装是需要证书支持的,我们在Eclipse或者Android Studio中开发App时,并没有注意关于证书的事,也能正确安装App.这是因为使用了默认的debug证书.在A ...

  3. OkHttp3源码分析二 拦截器 上

    在上一篇中,我们大概知道了OkHttp进行网络请求的大概的流程: 按着流程,一个一个的分析. RetryAndFollowUpInterceptor 构造方法: public RetryAndFoll ...

  4. OkHttp原理流程源码分析

    OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上.迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的.但 ...

  5. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  6. Android服务查询完整过程源码分析

    Android服务注册完整过程源码分析中从上到下详细分析了Android系统的服务注册过程,本文同样针对AudioService服务来介绍Android服务的查询过程. 客户端进程数据发送过程 pri ...

  7. Android服务注册完整过程源码分析

    前面从不同片段分析了Android的Binder通信机制,本文结合前面介绍的内容,对整个Android的Binder通信过程进行一次完整的分析.分析以AudioService服务的注册过程为例. 由于 ...

  8. Android 数据Parcel序列化过程源码分析

    在Android系统中,所有的服务都必须注册到ServiceManger中,当客户进程需要请求某一服务时,首先从服务管家ServiceManger中查找出该服务,然后通过RPC远程调用的方式使用该服务 ...

  9. Android系统默认Home应用程序(Launcher)的启动过程源码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

最新文章

  1. 2018-3-28 基本粒子群优化算法
  2. 技术天地 | CSS-in-JS:一个充满争议的技术方案
  3. jquery 读秒,倒数计时方案
  4. 南昌理工学院的计算机科学与技术专业怎么样,南昌理工学院有哪些专业及什么专业好...
  5. Web Deploy发布网站及常见问题解决方法(图文)
  6. 使用Python requests post上传图片及示例demo
  7. 你赞同企业年薪百万的高管对员工说别羡慕赚的多,人家加班和付出的时候你在玩的说法吗?
  8. 执行效率太低又怎样? Python 照样火过 Java、C/ C++
  9. java中结构体入参_JNA中自定义结构体如何传参?
  10. 工作流引擎厂商有哪些_国内主要工作流厂商分析
  11. 编译原理-陈火旺-第三版-课后习题第八章123题
  12. java gbk编码_java 中文转GBK码
  13. max的标准库头文件 c语言,float.h - C语言标准库
  14. vuecli项目打包
  15. 【LaTex-错误和异常】\verb ended by end of line.原因是因为闭合边界符没有在\verb命令所属行中出现;\verb命令的正确和错误用法、verbatim环境的用法
  16. LeetCode. 拿硬币
  17. 【TeXstudio】【3】较为完整的论文排版模板与bib文件引用方法
  18. RLC振荡原理与RC Snubber吸收电路
  19. android 远距离识别,远距离 人脸识别!
  20. JVM类加载过程,JDK和JER区别

热门文章

  1. 打了断点为直接运行完_黑社会行为?男子驾校身亡,家属看现场被保安围殴,手臂被打断...
  2. TIMING_01 时序约束与时序分析引导篇
  3. 数据库开发技术java方向_Java开发工程师(Web方向) - 03.数据库开发 - 第5章.MyBatis...
  4. 蜗牛星际网卡驱动_矿渣“蜗牛星际“折腾NAS黑群晖全过程
  5. 数据结构算法 二进制转十进制_数据结构 - 栈
  6. 华为 鸿蒙 操作系统关键特性解读
  7. 关于CPU的12个硬核干货!
  8. Sobel边缘检测算法verilog实现及仿真
  9. 字典与集合_月隐学python第8课
  10. anglar ajax执行2次的原因,angular2 router’解决问题被执行两次