当前位置:   article > 正文

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

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

OkHttp同步请求步骤:

  1. 创建OkHttpClient,客户对象
  2. 创建Request,请求主体,在请求主体设置请求的url,超时时间等
  3. 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的execute()发起同步请求。
  4. execute()返回的是Response对象。可以用execute().body().toString()得到请求所返回的主体内容。
  1. val client = OkHttpClient()
  2. val request = Request.Builder()
  3. .url("https://www.baidu.com")
  4. .build()
  5. 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方法中获取申请数据内容。
  1. val client = OkHttpClient()
  2. val request = Request.Builder()
  3. .url("https://www.baidu.com")
  4. .build()
  5. val response = client.newCall(request).enqueue(object: Callback {
  6. override fun onFailure(call: Call, e: IOException) {
  7. TODO("Not yet implemented")
  8. }
  9. override fun onResponse(call: Call, response: Response) {
  10. response.body().toString()
  11. }
  12. })

源码分析:

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

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

  1. public OkHttpClient() {
  2. this(new Builder());
  3. }
  4. public Builder() {
  5. dispatcher = new Dispatcher();
  6. protocols = DEFAULT_PROTOCOLS;
  7. connectionSpecs = DEFAULT_CONNECTION_SPECS;
  8. ......
  9. connectionPool = new ConnectionPool();
  10. .....
  11. }

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

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

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

  1. public Builder() {
  2. this.method = "GET";
  3. this.headers = new Headers.Builder();
  4. }
  5. Request(Builder builder) {
  6. this.url = builder.url;
  7. this.method = builder.method;
  8. this.headers = builder.headers.build();
  9. this.body = builder.body;
  10. this.tags = Util.immutableMap(builder.tags);
  11. }

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

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

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

  1. @Override public Call newCall(Request request) {
  2. return RealCall.newRealCall(this, request, false /* for web socket */);
  3. }
  4. //可以看到newCall方法里面是调用了RealCall类的newRealCall方法,下面到RealCall类里看看。
  5. static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  6. // 调用RealCall的构造函数
  7. RealCall call = new RealCall(client, originalRequest, forWebSocket);
  8. call.eventListener = client.eventListenerFactory().create(call);
  9. return call;
  10. }
  11. //下面是RealCall类构造函数
  12. private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  13. this.client = client;
  14. this.originalRequest = originalRequest;
  15. this.forWebSocket = forWebSocket;
  16. this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  17. ......
  18. }

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

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

  1. @Override public Response execute() throws IOException {
  2. .....
  3. //开启事件监听
  4. eventListener.callStart(this);
  5. try {
  6. //分发器用executed方法将Call对象添加进同步运行队列
  7. client.dispatcher().executed(this);
  8. //结果是从拦截器链方法中获取的
  9. Response result = getResponseWithInterceptorChain();
  10. ......
  11. } finally {
  12. //finish方法里将Call对象从Calls队列中移出
  13. client.dispatcher().finished(this);
  14. }
  15. }
  16. //下面进到client.dispatcher().executed(this)的excuted方法里面
  17. synchronized void executed(RealCall call) {
  18. //runningSyncCalls是正在运行的同步队列
  19. runningSyncCalls.add(call);
  20. }

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

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

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

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

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

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

  1. final class AsyncCall extends NamedRunnable {
  2. private final Callback responseCallback;
  3. AsyncCall(Callback responseCallback) {
  4. super("OkHttp %s", redactedUrl());
  5. this.responseCallback = responseCallback;
  6. }
  7. ...........
  8. }

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

  1. public abstract class NamedRunnable implements Runnable {
  2. .....
  3. @Override public final void run() {
  4. String oldName = Thread.currentThread().getName();
  5. Thread.currentThread().setName(name);
  6. try {
  7. execute();
  8. } finally {
  9. Thread.currentThread().setName(oldName);
  10. }
  11. }
  12. .....
  13. }

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

  1. @Override protected void execute() {
  2. .......
  3. Response response = getResponseWithInterceptorChain();
  4. .....
  5. }

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

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

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

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

  1. public final class Dispatcher {
  2. //默认的最大并发请求量
  3. private int maxRequests = 64;
  4. //单个host支持的最大并发量
  5. private int maxRequestsPerHost = 5;
  6. .........
  7. //异步等待队列
  8. private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  9. //异步运行队列
  10. private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  11. //同步运行队列
  12. private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  13. ...........
  14. //计算队列内请求数量的方法,如果异步请求满足不超过645的条件则进行请求操作。
  15. //有的版本OkHttp是通过promoteAndExecute()进行条件判断,原理差不多
  16. synchronized void enqueue(AsyncCall call) {
  17. if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  18. //Call对象添加进runningAsyncCalls异步进行队列
  19. runningAsyncCalls.add(call);
  20. //创建线程池并执行Call请求
  21. executorService().execute(call);
  22. } else {
  23. readyAsyncCalls.add(call);
  24. }
  25. }
  26. ......
  27. }

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

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

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

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

  1. //异步请求的finished方法
  2. void finished(AsyncCall call) {
  3. finished(runningAsyncCalls, call, true);
  4. }
  5. //同步请求的finished方法
  6. void finished(RealCall call) {
  7. finished(runningSyncCalls, call, false);
  8. }
  9. //具体的finished方法
  10. private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
  11. int runningCallsCount;
  12. Runnable idleCallback;
  13. synchronized (this) {
  14. //将实现的Call对象从队列中移出
  15. if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  16. //注意promoteCalls是第三个参数,既如果是异步请求才会运行该方法,重新整理队列。
  17. if (promoteCalls) promoteCalls();
  18. runningCallsCount = runningCallsCount();
  19. idleCallback = this.idleCallback;
  20. }
  21. if (runningCallsCount == 0 && idleCallback != null) {
  22. idleCallback.run();
  23. }
  24. }

进入promoteCalls()方法

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

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

Dispatcher分发器:

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

ExecutorService线程池: 

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

  1. public synchronized ExecutorService executorService() {
  2. if (executorService == null) {
  3. //创建线程池
  4. executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
  5. new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  6. }
  7. return executorService;
  8. }

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

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

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

OkHttp拦截器

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

  1. Response getResponseWithInterceptorChain() throws IOException {
  2. // Build a full stack of interceptors.
  3. List<Interceptor> interceptors = new ArrayList<>();
  4. interceptors.addAll(client.interceptors());
  5. interceptors.add(retryAndFollowUpInterceptor);
  6. interceptors.add(new BridgeInterceptor(client.cookieJar()));
  7. interceptors.add(new CacheInterceptor(client.internalCache()));
  8. interceptors.add(new ConnectInterceptor(client));
  9. if (!forWebSocket) {
  10. interceptors.addAll(client.networkInterceptors());
  11. }
  12. interceptors.add(new CallServerInterceptor(forWebSocket));
  13. Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
  14. originalRequest, this, eventListener, client.connectTimeoutMillis(),
  15. client.readTimeoutMillis(), client.writeTimeoutMillis());
  16. return chain.proceed(originalRequest);
  17. }

总归创建了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方法返回的值来来决定而等待的时间长度。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/1021603
推荐阅读
相关标签
  

闽ICP备14008679号