Android-详解网络请求框架-OkHttp(源码)

前言

1.OkHttp的优点

网络方面
(1)它支持http2.0,在网络请求响应方面使用了多路复用;
(2)内置连接池,减少连接开销,复用连接;
(3)内有Response缓存,缓存响应,避免重复的请求;
(4)对响应体支持gzip压缩,使得传输数据更为轻量;
(5)支持SPDY,允许连接同一主机的所有请求分享一个socket;
拓展方面
(1)支持重定向,重试请求,重写编码拦截器;
(2)支持头部信息拦截,自带CookieJar方法;
(3)支持网络数据传输时的监听;
(4)通过需求能自定义拦截器进行处理;

2.OkHttp工作流程

(1)通过创建OkhttpClientBuidler,实例化OkHttpClient;
(2)当我们发起一个请求时,OKhttpClient内部实现了call.Factory,负责根据请求创建新的Call,接着执行newCall,这里用工厂模式,返回调用了realCall,用dispatcher管理分发了同步异步请求;
(3)execute()以及enqueue()这两个方法最终都用调用realCall中的getResponseWithIntercepterChain()方法,配置着各个拦截器,使用责任链模式获取返回结果;
(4)getResponseWithIntercepterChain方法的拦截器中,依次通过retryAndFollowUpInterceptor->BridgeInterceptor->CacheInterceptor->ConnectInterceptor->CallServerInterceptor五个拦截器对请求依次处理,通过CallServerInterceptor拦截器发送request请求体/头,获得response请求体/头;
(5)最终调用RealInterceptorChain.proceed()启动链式调用;

3.OkHttp流程图

OKhttp流程图

进入正题

1.创建OkHttp实例对象

OkHttpClient client = new OkHttpClient();

查看内部实现的Builder方法

public Builder() {
    
    
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
}

看到该方法里创建了Dispatcher()方法,正如前言的工作流程所说,管理着请求的同步或者异步

2.发起网络请求过程分析

OkHttpClient client = new OkHttpClient();
//请求体
RequestBody body = new FormBody.Builder().add("key","value").build()
Request req = new Request.Builder().url("请求地址").post(body).build();
Response response = client.newCall(req).execute();

client.newCall发起了同步请求,得到的响应体response,来看看newCall方法里具体的实现吧

@Override 
public Call newCall(Request request) {
    
    
  return new RealCall(this, request);
}

可以看到这里没有多余步骤,只用了工厂模式,主要是由RealCall来管理实现,进入RealCall的世界,来看看这大世界的精彩。

2.1发起同步请求

2.1.1接着上面讲,RealCall#execute

@Override
public Response execute() throws IOException {
    
    
  synchronized (this) {
    
    
    ...
  }
  try {
    
    
    client.dispatcher().executed(this);                                 
    Response result = getResponseWithInterceptorChain();                
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    
    
    client.dispatcher().finished(this);                                 
  }
}

(1)这里第一步调用了dispatcher().executed(this)方法,执行同步请求;
(2)第二步可以看到之前的流程图,执行完dispatcher方法会执行getResponseWithInterceptorChain方法,这方法很关键,在其中调用了责任链模式的拦截器,返回请求结果;
(3)执行完毕后通知finished方法,我完事了;

dispatcher()涉及的内容不过是选择执行分支,同步/异步。

2.1.2 执行execute#getResponseWithInterceptorChain

这里我们选择性看关键代码

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
    
    
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

拦截器这一块是OKhttp的核心之一,它不单单只是拦截作用,实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,再调用RealInterceptorChain.proceed()方法进行处理,完成一次请求。
这里使用ArrayList保存着各个拦截器;

  • retryAndFollowUpInterceptor
    重试拦截器。处理重试请求的一个拦截器,可以去处理一些异常,只要不是致命的异常就会重新发起一次请求;然后会去处理一些重定向,从头部的Location中获取一个新的url地址,生成一个新的请求,重新发起请求;
  • BridgeInterceptor
    基础拦截器。做一些简单的处理,设置一些通用的请求头,例如Cookie、Content-Type…;对返回的response进行处理,如果数据头被压缩了,则用ZipSource保存Cookie.
  • CacheInterceptor
    缓存拦截器。在缓存里有之前返回的数据集时(相同数据),则读取本地缓存数据,不进行网络请求 读取数据。这个缓存有过期时间,会判断是否过期,如果没有过期才能读取数据,过期则发起请求。每次读取到新的响应后做一次缓存,这样的话,可以保证下一次从本地缓存中可以拿到;
  • ConnectInterceptor
    连接拦截器。主要作用是找到一个连接,首先判断是否有健康合适的连接,没有就创建连接,通过握手建立连接,OKhttp底层使用的是:socket+okio,okio(输入输出流),这样建立好连接就可以对服务器进行请求返回了;
  • CallServerInterceptor
    负责向服务器发送请求数据、从服务器读取响应数据,写头部信息,写body表单信息等等。

以上就是各个拦截器的作用,责任链模式还是很精妙。妙极了!(手动滑稽)

2.1.2.1建立连接getResponseWithInterceptorChain#ConnectInterceptor

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

  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();

  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

这里主要就是创建了一个HTTP2.0的HttpCodec对象,它用okio对socket的读写操作进行封装,更高效的IO操作,期间找到一个可用的,健康的,适合的RealConnection对象,通过它的IO操作创建HttpCoder对象,供后续操作。

2.1.2.2getResponseWithInterceptorChain#CallServerInterceptor

@Override 
public Response intercept(Chain chain) throws IOException {
    
    
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    
    
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    
    
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    
    
    streamAllocation.noNewStreams();
  }

  // ...

  return response;
}

这里就是拿到httpCodec对象,通过该对象请求/返回数据;

  1. 向服务器发起header请求
  2. 向服务器发起request body
  3. 读取头信息(response对象)
  4. 读取返回的数据体(response对象)

到这同步请求的一整个流程就完毕了。

2.2发起异步请求

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

// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
    
    
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    
    
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    
    
    readyAsyncCalls.add(call);
  }
}

一开始调用了RealCall的dispatcher.enqueue方法,接着看dispatcher中的enqueue方法;
当dispatcher在异步执行时,如果当前还能执行一个并发请求,那就立即执行,否则加入 readyAsyncCalls 队列。

new AsyncCall(responseCallback)

它是RealCall 的一个内部类,它实现了 Runnable,所以可以被提交到 ExecutorService 上执行,而它在执行时会调用 getResponseWithInterceptorChain() 函数,并把结果通过 responseCallback 传递给上层使用者,也就是说另开启了线程去执行。

总结

同步请求和异步请求的原理是一样的,都是在 getResponseWithInterceptorChain() 函数中通过 Interceptor 链条来实现的网络请求逻辑,而异步则是把请求加入队列,开启异步线程,通过 ExecutorService 实现。

拓展点

OKhttp在处理瀑布流照片时,想象一下,如果你有一个瀑布流,然后瀑布流里全部显示的都是图片。现在用户要不断地往下翻看瀑布流的图片。如果这些图片都用同步请求的话,可以想象效率多地下,所以有必要时可以看条件使用异步请求,一个是onFailure,一个是onResponse。这两个方法分别在请求失败和成功的时候调用。注意:这两个方法不是在主线程中。

最后鸣谢CSDN各个文章提供参考

猜你喜欢

转载自blog.csdn.net/q1210249579/article/details/110771175