Okhttp源码解析(一)

Okhttp(官网、github)作为安卓主流的网络加载框架,其基本使用相信大家已经很熟悉,通过简单的依赖和设置参数即可完成网络的请求,且包含丰富的API方便调用,例如简单的图片加载实例。本文旨在学习其源码相关的知识,了解网络加载背后源码的执行流程,方便更好的使用该框架和解决问题。
1、网络加载的执行流程 Okhttp源码解析(一)
文章图片

OkHttpClient client = new OkHttpClient .Builder() .build(); Request request = new Request .Builder() .url("http://www.baidu.com") .build(); Call call = client.newCall(request); //同步 try { Response response = call.execute(); } catch (IOException e) { e.printStackTrace(); }//异步请求 call.enqueue(new Callback() {@Override public void onFailure(Call call, IOException e) { System.out.println("连接失败"); }@Override public void onResponse(Call call, Response response) throws IOException { //请求处理,输出结果 System.out.println(response.body()); } });

结合上图和实例代码,okhttp的使用主要包括四部分:
  • 创建OkHttpClient :设置一些基本的参数例如时间、缓存和拦截器等,同时初始化一些参数,例如Dispatcher、ConnectionPool和参数的默认设置等;
  • 创建Request :该部分主要是请求参数的设置,例如URL、method、body和headers等;
  • 获取新的Call :将创建的OkHttpClient 和Request 相结合构建一个请求对象;
  • 执行网络请求操作:通过execute()或enqueue()真正执行网络请求。
1.1 创建OkHttpClient
public Builder() { dispatcher = new Dispatcher(); ... connectionPool = new ConnectionPool(); ... }

在创建OkHttpClient 时,主要是通过build的形式构建其对象,相比使用时经常用到的API的设置(例如时间、缓存和拦截器等),我们这里关注的是两个参数:Dispatcher和ConnectionPool,其中Dispatcher是okhttp的构建核心,主导okhttp请求的主要流程,特别是异步请求时,管理其队列,下文会详细讲解。ConnectionPool是作为请求的连接池,用于请求的复用和策略相关的操作。
1.2 创建Request
与OkHttpClient 类似也是采用构建者的方式,Request 最主要的功能是设置URL、method和body,默认采用的是GET请求方式,可通过.post或.put方式切换至其他请求模式,其中body主要包括FormBody和MultipartBody。
1.3 获取新的Call
由于Call是一个接口,主要通过其实现类RealCall来完成OkHttpClient 和Request相结合,构建RealCall 。
//RealCall .java static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }

1.4 执行网络请求操作
完成以上三步的准备工作后,开始执行真正的网络请求操作,根据调用的方法(execute()或enqueue())来区分同步还是异步的请求操作,这里需要注意,同步和异步其中最大的区别为是否阻塞当前的工作线程,异步请求中会开启一个新的工作线程执行网络请求操作,通过callback的方式返回请求结果。
2、源码分析 在通过RealCall来完成OkHttpClient 和Request相结合后,开始执行网络请求的操作,根据execute()或enqueue()区分是同步请求或是异步,下面通过源码分析同步和异步请求的逻辑处理。
2.1 同步执行
Okhttp源码解析(一)
文章图片

//RealCall .java @Override public Response execute() throws IOException { //1 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } //2 captureCallStackTrace(); eventListener.callStart(this); try { //3 client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { //4 client.dispatcher().finished(this); } }

结合UML和源码分析,其执行流程如下:
  1. 状态判断:通过executed 变量判断是否已经执行请求操作, 如已经执行则抛出异常;
  2. 状态捕获:捕获请求中一些堆栈信息便于流程分析;
  3. 请求操作:通过Dispatcher的executed()执行真正的网络请求操作,经过拦截器中一系列链式的请求操作后,将结果返回;
  4. 清理操作:请求完成后,通过Dispatcher的finshed()方法清理堆栈中的请求信息。
以上流程可以看出,请求的主要过程都是通过Dispatcher来完成,重点分析其调用的两个方法。
executed
//Dispatcher.java synchronized void executed(RealCall call) { runningSyncCalls.add(call); }

在1.1中OkHttpClient的Builder的构建时,默认就创建好了Dispatcher对象,因此此时可以直接调用其方法,在executed中方法比较简单,直接将请求的RealCall 对象添加至同步正在执行的队列中。而后通过getResponseWithInterceptorChain()获取网络请求的Response;
【Okhttp源码解析(一)】finished
网络请求完成后,在最后的finally中会调用finished方法,其主要功能是清理堆栈中的对象。
//Dispatcher.java private void finished(Deque calls, T call, boolean promoteCalls) { ... synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); ... runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; }if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } }

2.2 异步执行
Okhttp源码解析(一)
文章图片

//RealCall .java @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }

异步请求流程与同步类似,但是在调用网络请求的方法为enqueue,并将responseCallback通过AsyncCall封装为参数,传递至Dispatcher的enqueue方法中。查看AsyncCall的源码可以发现,其继承自NamedRunnable,而NamedRunnable实现了Runnable方法,因此在执行异步操作时,会开启一个新的工作线程,避免线程阻塞,在AsyncCall中真正实现网络 的请求操作。
在执行Dispatcher的enqueue方法时,主要是将封装好的AsyncCall添加至队列中,在Dispatcher类针对异步定义了两个队列:readyAsyncCalls和runningAsyncCalls,用于存储等待线程和正在执行的线程。添加至那个队列通过如下的判断:
  • 添加至正在运行的异步队列:正在运行的异步队列<64&&当前网络请求<5
  • 添加至等待队列:当大于maxRequests 或maxRequestsPerHost添加至等待队列中
//RealCall .java synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }

当添加至正在执行的队列后,开始通过executorService线程池执行call线程,executorService的创建的参数比较有意思,如下所示,其核心的四个参数,corePoolSize=0,maximumPoolSize= Integer.MAX_VALUE,keepAliveTime =60,TimeUnit = TimeUnit.SECONDS。
其特点是没有核心线程,只有非核心线程,当异步请求加入线程池后,开始执行请求操作,并且在线程池完成请求操作后,其余未执行的线程会在60s后自动销毁。
这里需要注意,理论上可以添加Integer.MAX_VALUE,但是由于runningAsyncCalls的限制条件(maxRequests 和maxRequestsPerHost)所以不会无限添加。方法中添加synchronized 是保证executorService调用的单例性。
//RealCall .java public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }

添加至线程池后,开始开启新的线程执行网络请求操作,上文可知AsyncCall最终实现了Runnable方法,并在run()调用抽象方法execute(),下面查看一下AsyncCall重写execute()的执行流程。
//RealCall .java final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback 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) { ... eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); ... } finally { client.dispatcher().finished(this); } } }

执行流程包括:
  • 执行网络请求链:和同步类似,通过getResponseWithInterceptorChain()获取请求结果;
  • 是否调用取消操作:如调用取消回调至onFailure否则,将Response返回;
  • 是否包含异常:如包含则调用onFailure和eventListener进行回调和时间记录;
  • 清理操作:该步较为重要,用于清理和准备队列和执行队列中数据的添加。
//Dispatcher.java void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); }

在Dispatcher中,最终调用的是finished的重载方法中,如下所示。
private void finished(Deque calls, T call, boolean promoteCalls) { ... 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(); } }

该方法在同步时,也会调用,但是不同的是传入的promoteCalls为true,则会执行promoteCalls方法 ,如下所示查看该源码发现,其主要作用就是readyAsyncCalls中的队列传递至runningAsyncCalls中。其他执行的流程与同步类似。
//Dispatcher.java private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.for (Iterator 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. } }

3、小结 本文主要通过OkHttpClient的简单的请求实例,来分析同步和异步请求的源码执行流程,其中有一个比较重要的是Dispatcher类,负责请求事件的分发和队列的维护。然而真正的数据请求是调用getResponseWithInterceptorChain(),通过拦截器链逐步的获取请求结果,下文会重点分析Interceptor相关的源码流程。

    推荐阅读