前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想

彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想

作者头像
用户3045442
发布于 2018-09-11 08:21:51
发布于 2018-09-11 08:21:51
3.2K10
代码可运行
举报
文章被收录于专栏:Android研究院Android研究院
运行总次数:0
代码可运行

文章持续更新中…..

OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp。OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码和语言只是工具,重要的是思想。

在OKhttp 源码解析之前,我们必须先要了解http的相关基础知识,任何的网络请求都离不开http。

概述

okhttp的源码分析,网上有好多博客讲解,但讲解的都是一些源码可有可无的知识,并没有将okhttp的核心设计思想讲解到位,我们阅读一些框架的源码,学习的其实就是其设计思想,了解了整体的框架设计,在深入了解细节的实现会更加容易。

OkHttp 源码解析

1、OkHttp 的整体框架设计

建议将okhttp的源码下载下来,用AndroidStudio 打开,整篇文章是根据源码的分析来学习okhttp的设计技巧和思想,如果本篇文章有内容分析不到位的地方,欢迎大家和我一起讨论。

下图为okhttp请求网络的完整流程图(大致看一遍)

image.png

okhttp的使用方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OkHttpClient client = new OkHttpClient();

我们第一步先看一下okhttp的构造函数OkHttpClient()和一些配置相关,大致了解一下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public OkHttpClient() {
        this(new Builder());
    }

原来OkHttpClient 内部已经实现了OkHttpClient(Builder builder),如果我们不需要配置client,okhttp已将帮我们默认实现了配置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public Builder() {
            dispatcher = new Dispatcher();
            protocols = DEFAULT_PROTOCOLS;
            connectionSpecs = DEFAULT_CONNECTION_SPECS;
            eventListenerFactory = EventListener.factory(EventListener.NONE);
            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;
            pingInterval = 0;
        }

如果需要一些配置如添加拦截器等,则需要这样调用即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(loggingInterceptor)
                            .retryOnConnectionFailure(true)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();

我们看一下OkHttpClient 都有那些属性,稍微了解一下即可。后面在分析

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    final Dispatcher dispatcher;//调度器
    final @Nullable
    Proxy proxy;//代理
    final List<Protocol> protocols;//协议
    final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议
    final List<Interceptor> interceptors;//拦截器
    final List<Interceptor> networkInterceptors;//网络拦截器
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;//代理选择器
    final CookieJar cookieJar;//cookie
    final @Nullable
    Cache cache;//cache 缓存
    final @Nullable
    InternalCache internalCache;//内部缓存
    final SocketFactory socketFactory;//socket 工厂
    final @Nullable
    SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
    final @Nullable
    CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
    final HostnameVerifier hostnameVerifier;//主机名字确认
    final CertificatePinner certificatePinner;//证书链
    final Authenticator proxyAuthenticator;//代理身份验证
    final Authenticator authenticator;//本地省份验证
    final ConnectionPool connectionPool;//链接池 复用连接
    final Dns dns; //域名
    final boolean followSslRedirects;//安全套接层重定向
    final boolean followRedirects;//本地重定向
    final boolean retryOnConnectionFailure;//重试连接失败
    final int connectTimeout;//连接超时
    final int readTimeout;//读取超时
    final int writeTimeout;//写入超时
请求网络
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

从源码中可以看出 okhttp 实现了Call.Factory接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface Factory {
    Call newCall(Request request);
  }

我们看一下okhttpClient 如何实现的Call接口,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 @Override
    public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }

可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。

RealCall 主要方法:同步请求 :client.newCall(request).execute(); 和 异步请求: client.newCall(request).enqueue();

下面我们着重分析一下异步请求,因为在项目中所有的网络请求基本都是异步的,同步很少用到,最后我们在分析一下同步请求即可。

异步请求

跟着源码 走一遍 RealCall.enqueue() 的实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RealCall.java

 @Override public void enqueue(Callback responseCallback) {
    //TODO 不能重复执行
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //TODO 交给 dispatcher调度器 进行调度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到上述代码做了几件事:

  1. synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的call,可以调用如下方法:进行克隆
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
  @Override public RealCall clone() {
    return RealCall.newRealCall(client, originalRequest, forWebSocket);
  }
  1. 利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。

下面我们着重看一下调度器的实现。

Dispatcher 调度器

Dispatcher#enqueue 的方法实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 //TODO 执行异步请求
    synchronized void enqueue(AsyncCall call) {
        //TODO 同时请求不能超过并发数(64,可配置调度器调整)
        //TODO okhttp会使用共享主机即 地址相同的会共享socket
        //TODO 同一个host最多允许5条线程通知执行请求
        if (runningAsyncCalls.size() < maxRequests &&
                runningCallsForHost(call) < maxRequestsPerHost) {
            //TODO 加入运行队列 并交给线程池执行
            runningAsyncCalls.add(call);
            //TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现
            executorService().execute(call);
        } else {
            //TODO 加入等候队列
            readyAsyncCalls.add(call);
        }
    }

从上述代码可以看到Dispatcher将call 加入到队列中,然后通过线程池来执行call。

可能大家看了还是懵懵的,我们先了解一下Dispatcher几个属性和方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    //TODO 同时能进行的最大请求数
    private int maxRequests = 64;
    //TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    //TODO 如 https://restapi.amap.com  restapi.amap.com - host
    private int maxRequestsPerHost = 5;
    /**
     * Ready async calls in the order they'll be run.
     * TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除
     * 异步等待队列
     *
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    /**
     * Running asynchronous calls. Includes canceled calls that haven't finished yet.
     * TODO 正在进行的异步队列
     */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

很明显,okhttp 可以进行多个并发网络请求,并且可以设置最大的请求数

executorService() 这个方法很简单,只是创建了一个线程池

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 线程池的相关概念 需要理解
            //TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

既然我们将 call放到了线程池中那么它是如何执行的呢?注意这里的call是AsyncCall。

我们看一下AsyncCall的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final class AsyncCall extends NamedRunnable

NamedRunnable 是什么鬼?点进去看一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

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

  protected abstract void execute();
}

原来如此 AsyncCall其实就是一个 Runnable,线程池实际上就是执行了execute()。

我们在看一下AsyncCall的execute()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 final class AsyncCall extends NamedRunnable {
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //TODO 责任链模式
        //TODO 拦截器链  执行请求
        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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //TODO 移除队列
        client.dispatcher().finished(this);
      }
    }
  }

从上述代码可以看出真正执行请求的是getResponseWithInterceptorChain(); 然后通过回调将Response返回给用户。

值得注意的finally 执行了client.dispatcher().finished(this); 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //TODO calls 移除队列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            //TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列
            if (promoteCalls) promoteCalls();
            //TODO 运行队列的数量
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }
        //闲置调用
        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

    private void promoteCalls() {
        //TODO 检查 运行队列 与 等待队列
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

        //TODO 将等待队列加入到运行队列中
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            //TODO  相同host的请求没有达到最大,加入运行队列
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }

            if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
    }

真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain(),下面我们着重分析一下这个方法:

每段代码我都加上了注释。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//TODO 核心代码 开始真正的执行网络请求
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //TODO 责任链
    List<Interceptor> interceptors = new ArrayList<>();
    //TODO 在配置okhttpClient 时设置的intercept 由用户自己设置
    interceptors.addAll(client.interceptors());
    //TODO 负责处理失败后的重试与重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
    //TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
    //TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //TODO 可配置用户自己设置的缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //TODO 配置okhttpClient 时设置的networkInterceptors
      //TODO 返回观察单个网络请求和响应的不可变拦截器列表。
      interceptors.addAll(client.networkInterceptors());
    }
    //TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
    //TODO 进行http请求报文的封装与请求报文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 创建责任链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    //TODO 执行责任链
    return chain.proceed(originalRequest);
  }

从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。

责任链模式是设计模式中的一种也相当简单参考链接,这里不在复述。

我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。

上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。

其实核心代码就是chain.proceed() 通过该方法进行责任链的执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    //TODO 创建新的拦截链,链中的拦截器集合index+1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器
    Interceptor interceptor = interceptors.get(index);
    //TODO 执行拦截器
    Response response = interceptor.intercept(next);
    return response;
  }

从上述代码,我们可以知道,新建了一个RealInterceptorChain 责任链 并且 index+1,然后 执行interceptors.get(index); 返回Response。

其实就是按顺序执行了拦截器,这里我画了一个简图:

image.png

拦截器的执行顺序便是如上图这样执行的。

这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。

CacheInterceptor 缓存拦截器就是很好的证明,我们来通过CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。

CacheInterceptor 的实现如下:

代码比较长,我们一步一步的来进行分析。

首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @Override public Response intercept(Chain chain) throws IOException
  {
//TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    //TODO 执行响应缓存策略
    long now = System.currentTimeMillis();
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //TODO 如果networkRequest == null 则说明不使用网络请求
    Request networkRequest = strategy.networkRequest;
    //TODO 获取缓存中(CacheStrategy)的Response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
    //TODO 缓存无效 关闭资源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    //TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null  返回失败
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    //TODO 不使用网络请求 且存在缓存 直接返回响应
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    }

上述的代码,主要做了几件事:

  1. 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest
  2. 如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清楚cacheCandidate缓存。
  3. 如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
  4. 如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。

上部分代码,其实就是没有网络的时候的处理。

那么下部分代码肯定是,有网络的时候处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    //TODO 执行下一个拦截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //TODO 网络请求 回来 更新缓存
    // If we have a cache response too, then we're doing a conditional get.
    //TODO 如果存在缓存 更新
    if (cacheResponse != null) {
      //TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //TODO 缓存Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

下部分代码主要做了这几件事:

  1. 执行下一个拦截器,也就是请求网络
  2. 责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。

这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。

这也是okhttp设计的最优雅最核心的功能。

当然我们可以通过一个小例子来进行验证,实践才最重要。

首先我们模拟一个 拦截器的接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author prim
 * @version 1.0.0
 * @desc 模拟okhttp拦截器
 * @time 2018/8/3 - 下午4:29
 */
public interface Interceptor {
    String interceptor(Chain chain);

    interface Chain {
        String request();

        String proceed(String request);
    }
}

然后在实现几个拦截器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BridgeInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("执行 BridgeInterceptor 拦截器之前代码");
        String proceed = chain.proceed(chain.request());
        System.out.println("执行 BridgeInterceptor 拦截器之后代码 得到最终数据:"+proceed);
        return proceed;
    }
}

public class RetryAndFollowInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("执行 RetryAndFollowInterceptor 拦截器之前代码");
        String proceed = chain.proceed(chain.request());
        System.out.println("执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:" + proceed);
        return proceed;
    }
}

public class CacheInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("执行 CacheInterceptor 最后一个拦截器 返回最终数据");
        return "success";
    }
}

然后实现Chain 接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class RealInterceptorChain implements Interceptor.Chain {

    private List<Interceptor> interceptors;

    private int index;

    private String request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public String request() {
        return request;
    }

    @Override
    public String proceed(String request) {
        if (index >= interceptors.size()) return null;

        //获取下一个责任链
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request);
        // 执行当前的拦截器
        Interceptor interceptor = interceptors.get(index);

        return interceptor.interceptor(next);
    }
}

然后进行测试,看看我们是否分析的正确

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(new BridgeInterceptor());
        interceptors.add(new RetryAndFollowInterceptor());
        interceptors.add(new CacheInterceptor());

        RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");

        request.proceed("request");

打印的log日志如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
执行 BridgeInterceptor 拦截器之前代码
执行 RetryAndFollowInterceptor 拦截器之前代码
执行 CacheInterceptor 最后一个拦截器 返回最终数据
执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:success
执行 BridgeInterceptor 拦截器之后代码 得到最终数据:success

OK 完美,验证没有问题,我想至此大家都应该懂了 okhttp的核心设计思想了。

okhttp的其他拦截器的具体实现大家可以自己研究一下即可,okhttp的这种设计思想我们完全可以应用到项目中去,解决一些问题。

同步请求

这里在稍微讲一下,okhttp的同步请求,代码很简单 同样是在RealCall 类中实现的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//TODO 同步执行请求 直接返回一个请求的结果
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //TODO 调用监听的开始方法
    eventListener.callStart(this);
    try {
      //TODO 交给调度器去执行
      client.dispatcher().executed(this);
      //TODO 获取请求的返回数据
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //TODO 执行调度器的完成方法 移除队列
      client.dispatcher().finished(this);
    }
  }

主要做了几件事:

  1. synchronized (this) 避免重复执行,上面的文章部分有讲。
  2. client.dispatcher().executed(this); 实际上调度器只是将call 加入到了同步执行队列中。代码如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//TODO 调度器执行同步请求
    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }
  1. getResponseWithInterceptorChain()最核心的代码,上面已经分析过了,请求网络得到响应数据,返回给用户。
  2. client.dispatcher().finished(this); 执行调度器的完成方法 移除队列

可以看出,在同步请求的方法中,涉及到dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。

总结

okhttp还有很多细节在本文中并没有涉及到,例如:okhttp是如何利用DiskLruCache实现缓存的、HTTP2/HTTPS 的支持等,本文主要讲解okhttp的核心设计思想,对整体有了清晰的认识之后,在深入细节,更容易理解。

简述okhttp的执行流程:

  1. OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
  2. RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
  3. getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android研究院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
图片点开好糊
图片点开好糊
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
OkHttp源码解析
OkHttp in GitHub:https://github.com/square/okhttp
俞其荣
2022/07/28
7650
OkHttp源码解析
开源框架源码解析系列(1)——进入OkHttp的世界
以前曾经写过一篇关于Okhttp的使用的文章深入解析OkHttp3,通过这篇文章可以了解OkHttp的各种基本用法,光会使用并不算好汉,我们还要深入理解源码,学习优秀的设计思想,本篇我就带大家一起分析源码,基于Okhttp 3.10.0版本。
老马的编程之旅
2022/06/22
5920
开源框架源码解析系列(1)——进入OkHttp的世界
OKHttp原理解析
Okhttp 应该是Android目前非常流行的第三方网络库,尝试讲解他的使用以及原理分析,分成几个部分:
猿芯
2021/05/27
8250
OKHttp原理解析
锦囊篇|一文摸懂OkHttp
最近都在学校上课,三天满课,剩下还要课程复习维持绩点,基本上维持周更也已经比较吃力了,不过还是会继续坚持,之后的推文基本上会在周天推,嘻嘻。
ClericYi
2020/06/23
4220
OkHttp源码走心解析(很细 很长)
本文是对OkHttp开源库的一个详细解析,如果你觉得自己不够了解OkHttp,想进一步学习一下,相信本文对你会有所帮助。
没关系再继续努力
2021/12/16
1.1K0
Android开发神器:OkHttp框架源码解析
HTTP是我们交换数据和媒体流的现代应用网络,有效利用HTTP可以使我们节省带宽和更快地加载数据,Square公司开源的OkHttp网络请求是有效率的HTTP客户端。之前的知识面仅限于框架API的调用,接触到实际的工作之后深知自己知识的不足,故而深挖框架源码尽力吸取前辈的设计经验。关于此框架的源码解析网上的教程多不胜数,此文名为源码解析,实则是炒冷饭之作,如有错误和不足之处还望各位看官指出。
程序员小何SS
2021/12/04
6370
Android |《看完不忘系列》之okhttp
嗨,我是哈利迪~《看完不忘系列》将以从树干到细枝的思路分析一些技术框架,本文将对开源项目okhttp网络库进行介绍。
Holiday
2020/08/10
8640
Android |《看完不忘系列》之okhttp
刨解OkHttp框架
继AsyncTask,又把手术刀指向OkHttp,有时候解析源码会上瘾。因为源码里包含的东西仿佛就是组成计算机世界的砖头,水分,只要有这些东西,就可以保罗万物,无招胜有招。又说多了,开始吧
HelloJack
2018/08/28
7160
刨解OkHttp框架
深入理解OkHttp源码(二)——获取响应
在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChain()方法,本篇主要从这儿继续往下讲解。
用户1108631
2019/08/14
6580
OkHttp源码流程分析
前面1,2,3步很多文章已经分析过很多遍了 也比较简单 同学们可以自己看一下 我们就不再赘述 我们直接看第四步进入今天的主要流程
花落花相惜
2021/11/24
4680
OKHttp源码解析--初阶
这段时间老李的新公司要更换网络层,知道现在主流网络层的模式是RxJava+Retrofit+OKHttp,所以老李开始研究这三个项目的源代码,在更换网络层后,开始分享这个三个项目源码的分析。* 本篇文章 主要讲解OKHttp源码解析(3.7.0) OKHttp官网:http://square.github.io/okhttp/
用户2802329
2018/08/07
9070
OKHttp源码解析--初阶
超详细Okhttp 源码分析
RetryAndFollowUpInterceptor的作用就是处理了一些连接异常以及重定向。
对话、
2022/02/22
1.3K0
OkHttp源码分析【同步、异步请求流程】
之前我们写过volley源码分析 Volley源码解读 ,volley相比OkHttp而言简单些,这次我们来看下OkHttp源码吧。
笔头
2022/03/23
8800
OkHttp科普篇
1.实例化下一个拦截器对应的RealIterceptorChain对象,这个对象会在传递给当前的拦截器
路遥TM
2021/09/22
9260
Android 网络请求OkHttp3流程分析
首先从使用出发,其次再结合源码来分析OkHttp3的内部实现的,建议大家下载 OkHttp 源码跟着本文,过一遍源码。首先来看一下OkHttp3的请求代码。
xiangzhihong
2022/11/30
1.2K0
源码分析OKHttp的执行过程
OKHttp 是目前 Android 平台主流的网络请求的基础框架。因此我们有必要对其源码进行阅读学习,了解其内部的原理、项目结构、以及请求的执行过程。
阳仔
2019/07/30
5470
一步步带你读懂 Okhttp 源码
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
程序员徐公
2019/10/25
8110
OkHttp3.0 拦截器、调度器源码解析
聊一个框架的源码,我们先从它的用法说起,通过OkHttp来请求网络一般不外乎通过以下两种方式 : 1、异步回调网络请求
萬物並作吾以觀復
2019/07/17
8920
网络请求框架OkHttp3全解系列 - (二)OkHttp的工作流程分析
在本系列的上一篇文章中,我们学习了OkHttp的基本用法,体验了这个网络加载框架的强大功能,以及它非常简便的API。还没有看过上一篇文章的朋友,建议先去阅读 网络请求框架OkHttp3全解系列 - (一)OkHttp的基本使用 。
胡飞洋
2020/07/23
3.1K0
网络请求框架OkHttp3全解系列 - (二)OkHttp的工作流程分析
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )
【OkHttp】OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) 【OkHttp】Android 项目导入 OkHttp ( 配置依赖 | 配置 networkSecurityConfig | 配置 ViewBinding | 代码示例 ) 【OkHttp】OkHttp Get 和 Post 请求 ( 同步 Get 请求 | 异步 Get 请求 | 同步 Post 请求 | 异步 Post 请求 ) 【OkHttp】OkHttp 上传图片 ( 获取 SD 卡动态权限 | 跳转到相册界面选择图片 | 使用 OkHttp 上传图片文件 )
韩曙亮
2023/03/29
1.7K0
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )
相关推荐
OkHttp源码解析
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文