OkHttp大流程分析

1,249 阅读12分钟

用过OkHttp的小伙伴们,都知道先new一个OkHttpClient的builder,紧接着添加InterceptorNetWorkInterceptor和连接超时等配置,然后通过build方法构建出OkHttpClient对象;

第二部分接着new了一个Request的builder对象,设置了url和header等相关参数,接着也是通过build方法生成了Request对象;

最后通过第一步OkHttpClient的newCall传入request,拿到了Call对象,接着通过Call的同步方法execute和异步方法enqueue拿到Response,这里文字计较多,我直接上一个大家熟悉的代码:

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.addInterceptor(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.d(TAG, "Interceptor url:" + chain.request().url().toString());
        return chain.proceed(chain.request());
    }
});
builder.addNetworkInterceptor(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.d(TAG, "NetworkInterceptor url:" + chain.request().url().toString());
        return chain.proceed(chain.request());
    }
});
OkHttpClient build = builder.build();
Request request = new Request.Builder()
        .url("https://www.baidu.com")
        .build();
Call call = build.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: " + e.getMessage());
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "response:" + response.body().string());
    }
});

其实分析源码我觉得突破点就是根据平时写的demo代码,然后一步步跟着进去看,虽然看的过程中很枯燥,但是当你坚持下来的时候,你就发现其实你也快能写出这样优秀的代码,然后反思,为什么会用这种写法,加自己的总结,相信你也会写出这么牛的代码。好了,下面开始源码分析部分。

源码分析

OkHttpClient.Builder

这块主要是通过builder模式配置Interceptor、NetWorkInterceptor、连接超时、读取超时等相关参数配置,这里列几个常见的属性:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {

  final Dispatcher dispatcher;//同步和异步处理类
  ......
  final List<Interceptor> interceptors;//普通的拦截器
  final List<Interceptor> networkInterceptors;//netWork拦截器
  ......
  final int connectTimeout;//连接超时
  final int readTimeout;/读取超时
  final int writeTimeout;//写入超时
  ......

}  

单看这个类是没什么好说的,主要有dispatcher对象,做同步和异步任务入口类,这个后面会介绍,第二个就是两种类型的拦截器,后面会分析这两种拦截器的区别,以及平时该怎么使用,第三个就是超时的参数配置。其他的属性在时去掉了,方便我们分析流程。

Request.Builder

这块主要是配置请求的url、请求方式(GET、PUT等配置)、header和body等相关配置。里面的配置比较少,直接看属性:

public final class Request {
  final HttpUrl url;//请求路径
  final String method;//请求方式
  final Headers headers;//请求头的key value配置
  final @Nullable RequestBody body;//请求体的key value配置
  final Map<Class<?>, Object> tags;//请求tag标识
}  

OkHttpClient.newCall

这块也很简单,通过传入进来的request对象,返回了Call对象:

Call是一个接口,实际返回的是RealCall对象,将OkHttpClient、request传给了RealCall,最后一个参数默认是false,表示是否是webSocket连接。第四步是调用了Callenqueue方法,直接看RealCallenqueue方法。

RealCall.enqueue

@Override public void enqueue(Callback responseCallback) {
  //同步锁将executed置为true
  synchronized (this) {
    //如果是executed了,则直接抛异常
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.callStart();
  //调用dispatcher的enqueue方法,并且将AsyncCall作为参数
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

上面代码很清晰,先判断是不是在executed过程,如果是executed则直接抛异常,否则走dispatcher的enqueue方法,并且传入了AsyncCall对象。

dispatcher.enqueue

void enqueue(AsyncCall call) {
  synchronized (this) {
    //首先添加到readyAsyncCalls集合里面
    readyAsyncCalls.add(call);
    //如果不是forWebSocket走这里,在构建RealCall的时候默认传入的false
    if (!call.get().forWebSocket) {
      //找是否有相同的host域名的请求call
      AsyncCall existingCall = findExistingCallWithHost(call.host());
      //如果有将存在的call对象的callsPerHost给当前的call对象
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
    }
  }
  promoteAndExecute();
}

首先将AsyncCall放到了readyAsyncCalls集合里面,接着判断如果不是webSocket请求,将已经存在的callsPerHost给了当前call的callsPerHost,最后调用了promoteAndExecute方法,findExistingCallWithHost很简单,就是通过running和ready集合里面比对host:

@Nullable private AsyncCall findExistingCallWithHost(String host) {
  for (AsyncCall existingCall : runningAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  for (AsyncCall existingCall : readyAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  return null;
}

如果找到了调用call的reuseCallsPerHostFrom方法:

void reuseCallsPerHostFrom(AsyncCall other) {
  this.callsPerHost = other.callsPerHost;
}

callsPerHost是一个int类型的原子类AtomicInteger,存储RealCall当前相同host的请求个数,后面会用到callsPerHost来限制详情host的请求数量。

dispatcher.promoteAndExecute

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));
  //用来存需要执行的call
  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();
      //如果正在执行的call超过了maxRequests直接跳过
      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      //相同host请求不能超过maxRequestsPerHost
      if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
      //从ready集合中移除该call
      i.remove();
      //对相同host原子对象+1
      asyncCall.callsPerHost().incrementAndGet();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }
  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    //真正执行call的地方
    asyncCall.executeOn(executorService());
  }
  return isRunning;
}
  • 首先判断最大请求个数是否超过了maxRequests=64个和相同host是否超过了maxRequestsPerHost=5个,这里面试中经常的一个考点。
  • 如果都没到最大限制,则把当前的AsyncCall从readyAsyncCalls集合中移除掉,同时对RealCall的host个数+1操作,同时加到runningAsyncCalls和executableCalls集合中。
  • 最后遍历executableCalls集合,执行AsyncCall的executeOn方法,这里要注意executorService方法是返回了一个线程池。所以最后执行任务的是在asyncCall的executeOn方法。

这里还有一个考点,在执行AsyncCall的时候是一个什么样的线程池:

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

知道线程池原理的小伙伴都知道,这里创建的线程都是非核心线程,并且最大线程池个数是无边界的,线程的等待时间是60秒。所以可以想象一下,OkHttp从刚开始有请求的时候,都是把任务先放到任务队列,然后开启对应个数的线程。如果在60秒内还有空闲的线程,则直接用空闲的线程处理任务,而不创建新的线程。

小节

到这里,我们先可以总结下:

  • RealCall负责包装OkHttpClient、Request后,在同步的时候是直接交给了dispatcher的execute方法处理,在异步的时候new了一个AsyncCall,交给了dispatcher的enqueue方法处理。
  • 在异步过程中先将AsyncCall添加到ready集合中,再判断非webSocket请求,然后将存在的AsyncCall存储的host数给当前的AsyncCall。
  • 接着判断最大请求和相同host的个数,符合要求才添加到running和executable集合中。
  • 最后遍历executable集合,通过new了一个线程池,开始调用AsyncCall的executeOn。

AsyncCall.executeOn

void executeOn(ExecutorService executorService) {
    //通过线程池的execute方法执行RealCall
    executorService.execute(this);
}

这里我把一些异常和finally中回调都给去掉了,留下了精简代码,直接通过线程池执行AsyncCall,而AsyncCall是继承自NamedRunnable抽象类,所以直接看它的run方法,run方法执行了execute方法,直接看AsyncCall的execute方法:

@Override protected void execute() {
  boolean signalledCallback = false;
  transmitter.timeoutEnter();
  try {
    //拿response的关键点
    Response response = getResponseWithInterceptorChain();
    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 {
    //执行完当前的AsyncCall处理
    client.dispatcher().finished(this);
  }
}

代码也都很简单,第一个是getResponseWithInterceptorChain方法拿response,第二个如果失败了回调给responseCallback,第三个是成功或失败完了后,调用dispatcher的finished,这里需要该方法:

dispatcher.finished

void finished(AsyncCall call) {
  call.callsPerHost().decrementAndGet();
  finished(runningAsyncCalls, call);
}

很简单,对当前AsyncCall存储的host数减一,接着调用了finished方法:

private <T> void finished(Deque<T> calls, T call) {
  Runnable idleCallback;
  synchronized (this) {
    //从running中移除掉当前call
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    idleCallback = this.idleCallback;
  }
  //继续执行ready集合中还剩下的AsyncCall
  boolean isRunning = promoteAndExecute();
  if (!isRunning && idleCallback != null) {
    idleCallback.run();
  }
}

上面finish方法首先将AsyncCall从runningAsyncCalls中移除掉,然后继续调用promoteAndExecute方法执行ready集合中没有完成的AsyncCall。说完了finish方法,我们回到上面的getResponseWithInterceptorChain调用:

getResponseWithInterceptorChain

从这个方法开始,我们进入到了拦截器部分,下面通过源码的方式介绍有哪几种拦截器:

Response getResponseWithInterceptorChain() throws IOException {
 // Build a full stack of interceptors.
 List<Interceptor> interceptors = new ArrayList<>();
 //添加最原始的拦截器
 interceptors.addAll(client.interceptors());
 interceptors.add(new RetryAndFollowUpInterceptor(client));
 interceptors.add(new BridgeInterceptor(client.cookieJar()));
 interceptors.add(new CacheInterceptor(client.internalCache()));
 interceptors.add(new ConnectInterceptor(client));
 if (!forWebSocket) {
  //添加netWork类型的拦截器
   interceptors.addAll(client.networkInterceptors());
 }
//添加真正联网请求的拦截器
 interceptors.add(new CallServerInterceptor(forWebSocket));
 //RealInterceptorChain真正处理拦截器连贯起来工作的类
 Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
     originalRequest, this, client.connectTimeoutMillis(),
     client.readTimeoutMillis(), client.writeTimeoutMillis());
 boolean calledNoMoreExchanges = false;
 try {
   //proceed是依次执行拦截器
   Response response = chain.proceed(originalRequest);
   if (transmitter.isCanceled()) {
     closeQuietly(response);
     throw new IOException("Canceled");
   }
   return response;
 } catch (IOException e) {
   calledNoMoreExchanges = true;
   throw transmitter.noMoreExchanges(e);
 } finally {
   if (!calledNoMoreExchanges) {
     transmitter.noMoreExchanges(null);
   }
 }
}
  • 首先添加client.interceptors()到interceptors集合中,紧接着添加RetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorclient.networkInterceptors(),其中client.interceptors()是通过OkHttpClient.Builder.addInterceptor方法添加进来的,而client.networkInterceptors()是通过OkHttpClient.Builder.addNetWorkInterceptor方法添加进来的,它两都是集合,说明这两种拦截器是可以添加多个。
  • 再来看下RealInterceptorChainproceed方法:
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
  //如果索引大于interceptors的size直接抛异常
  if (index >= interceptors.size()) throw new AssertionError();

  RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
      index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
  //获取到当前的拦截器
  Interceptor interceptor = interceptors.get(index);
  //调当前拦截器的intercept方法
  Response response = interceptor.intercept(next);

  return response;
}
  • 首先判断index是否大于interceptors的size,接着将next+1传给了new的RealInterceptorChain,然后通过index获取到当前的interceptor,最终调用了当前interceptor的intercept方法,返回response,流程其实挺简单的,这里画了一张图:

未命名文件 (1).png

关于类似的图网上也有,这个是根据自己的理解画上整个拦截器工作图,在每一个拦截器里面通过上一个拦截器传入的RealInterceptorChain,在interceptor方法里面最终返回了RealInterceptorChain.proceed,也就是上一个拦截器的response来自下一个烂机器的response。所以从这个拦截器的时序图来看,传入的是RealInterceptorChain,传出的是response这里要注意下,面试中会问到有哪些拦截器,有开发者自己添加的最原始的拦截器(OkHttpClient.interceptors)、重定向拦截器(RetryAndFollowUpInterceptor)、桥接拦截器(BridgeInterceptor)、缓存拦截器(CacheInterceptor)、连接拦截器(ConnectInterceptor)、读写拦截器(CallServerInterceptor)、netWorkInterceptor拦截器,其中最原始的那个拦截器和netWorkInterceptor是okhttp暴露给开发者用的拦截器,其余的都是内置的拦截器。由于篇幅的原因,在这里主要看下CallServerInterceptor内部interceptor方法:

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Exchange exchange = realChain.exchange();
  Request request = realChain.request();

  long sentRequestMillis = System.currentTimeMillis();

  exchange.writeRequestHeaders(request);

  boolean responseHeadersStarted = false;
  Response.Builder responseBuilder = null;
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      exchange.flushRequest();
      responseHeadersStarted = true;
      exchange.responseHeadersStart();
      responseBuilder = exchange.readResponseHeaders(true);
    }
  }
  //真正网络请求的地方
  Response response = responseBuilder
      .request(request)
      .handshake(exchange.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
  return response;
}

其他地方的代码都给去掉了,主要列出了请求的关键点,顺着exchange.connection()可以找到最后okHttp是通过sockect请求的,有兴趣的朋友可以顺着这行代码看看,在这里就不带大家看了。关于其他内置的几个拦截器在这里就不讲了,后面专门拿一篇出来说这几个拦截器。

到这里拦截器的过程就讲得差不多了,我们回到上面okhttp给我们开发者自己实现的拦截器有addInterceptor方式添加和addNetworkInterceptor方式添加,下面我们着重说下这两个拦截器的区别:

addInterceptor(应用拦截器)和addNetworkInterceptor(网络拦截器)方式添加的拦截器区别

从上面拦截器时序图可以看出来,首先添加到interceptors集合的是通过OkhttpClient.Builder的addInterceptor方法添加进去的,而addNetworkInterceptor方式添加的拦截器是在ConnectInterceptor拦截器之后之后添加的。可以看出来addInterceptor方式的拦截器是最原始的,而网络拦截器是经过了重定向拦截器,缓存拦截器。

  • 应用拦截器

    • 不需要关心像重定向和重试这样的中间响应。
    • 总是调用一次,即使HTTP响应从缓存中获取服务。
    • 监视应用原始意图。不关心OkHttp注入的像If-None-Match头。
    • 允许短路并不调用Chain.proceed()。
    • 允许重试并执行多个Chain.proceed()调用。
  • 网络拦截器

    • 可以操作像重定向和重试这样的中间响应。
    • 对于短路网络的缓存响应不会调用。
    • 监视即将要通过网络传输的数据。
    • 访问运输请求的Connection。
  • 应用场景

    • 应用拦截器:如果对网络请求统一的加密或者添加统一的请求头或请求体,应用拦截器是不错的选择。
    • 网络拦截器:如果需要监测请求的重定向、Connection、Accept-Encoding等详细信息就需要添加该监听器,比如facebook的Stetho框架就是通过添加StethoInterceptor拦截器实现网络监听。

总结

  • OkHttpClient通过builder构建,可以添加应用拦截器、网络拦截器,设置读数据、连接的超时时间。
  • Request通过builder构建,配置url、header、body等参数。
  • 通过OkHttpClient的newCall方法并传入上面的Request对象,交给RealCall处理。
  • 在RealCall的同步方法中直接添加到runningCalls集合中,接着就是调用了getResponseWithInterceptorChain获取response。
  • 在异步方法中,构建了一个AsyncCall,并把它添加到ReadyCalls集合中,接着拿到相同host的Call把host数给当前的Call。
  • 在开始执行AsyncCall前,先判断总请求数是否超过了64个和相同host请求数是否超过5个。如果都没超过,通过new的线程池来执行AsyncCall。
  • 在执行AsyncCall的过程中,是通过getResponseWithInterceptorChain将所有的拦截器放到集合中,通过RealInterceptorChain来连接每一个拦截器,最后通过上一个拦截器返回的response依次返回。

写到这okhttp流程算是分析完了,由于上家公司裁员,本人不幸是其中一个,因此如果有同行有android岗位可以推荐我,本人联系方式:a1002326270@163.com