前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )

【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )

作者头像
韩曙亮
发布2023-03-29 14:22:46
1.6K0
发布2023-03-29 14:22:46
举报
文章被收录于专栏:韩曙亮的移动开发专栏

OkHttp 系列文章目录

【OkHttp】OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 )

【OkHttp】Android 项目导入 OkHttp ( 配置依赖 | 配置 networkSecurityConfig | 配置 ViewBinding | 代码示例 )

【OkHttp】OkHttp Get 和 Post 请求 ( 同步 Get 请求 | 异步 Get 请求 | 同步 Post 请求 | 异步 Post 请求 )

【OkHttp】OkHttp 上传图片 ( 获取 SD 卡动态权限 | 跳转到相册界面选择图片 | 使用 OkHttp 上传图片文件 )

【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )

【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 )

【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )


文章目录

一、分析 OkHttp 执行原理


以 OkHttp 同步 / 异步 Get 请求为例 , 分析底层的运行细节 ;

代码语言:javascript
复制
    /**
     * OkHttp 异步 Get 请求
     */
    private void httpAsynchronousGet() {
    	// 初始化 OkHttp 
		OkHttpClient mOkHttpClient = new OkHttpClient();

        // Request 中封装了请求相关信息
        Request request = new Request.Builder()
                .url("https://www.baidu.com")   // 设置请求地址
                .get()                          // 使用 Get 方法
                .build();

        // 异步 Get 请求
        mOkHttpClient.newCall(request).enqueue(new Callback(){

            @Override
            public void onFailure(Call call, IOException e) {
                // 请求失败的情况
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                // 请求成功 , 获取
                String result = response.body().string();
                Log.i(TAG, "result : " + result);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // 主线程中执行相关代码
                    }
                });
            }
        });
    }

1、创建 OkHttpClient

创建 OkHttpClient : 调用者调用 OkHttpClient 构造函数 , 创建 OkHttpClient , 然后返回给调用者 ;

代码语言:javascript
复制
OkHttpClient mOkHttpClient = new OkHttpClient();

OkHttpClient 构造函数中 , 实际上创建了自身的创建者 ;

代码语言:javascript
复制
  public OkHttpClient() {
    this(new Builder());
  }

上述创建者构造函数调用的是无参构造函数 , 也就是默认设置了一系列参数 , 如下 :

代码语言:javascript
复制
  public static final class Builder {
    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      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;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
  }

2、创建 Request

创建 Request 时 , 使用 Request 的创建者 Request.Builder 创建该 Request 对象 ;

先调用 Request.Builder 的构造函数 , 创建 Request.Builder 对象 , 然后调用 Request.Builder 的 build 方法 , 创建 Request 对象 ;

代码语言:javascript
复制
// Request 中封装了请求相关信息
Request request = new Request.Builder()
        .url("https://www.baidu.com")   // 设置请求地址
        .get()                          // 使用 Get 方法
        .build();

3、获取 RealCall

调用 OkHttpClient 对象的 newCall 方法 , 发起新的请求调用 , 返回

1

个 RealCall 类型对象 ;

代码语言:javascript
复制
mOkHttpClient.newCall(request)

在 OkHttpClient 的 newCall 方法中 , 创建了 RealCall , 并返回给了调用者 ;

代码语言:javascript
复制
  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

RealCall 实现了 Call 接口 ;

代码语言:javascript
复制
final class RealCall implements Call {
  final OkHttpClient client;
}

Call 接口提供的功能 : execute 是同步请求 , enqueue 是异步请求 ;

代码语言:javascript
复制
public interface Call extends Cloneable {
  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Timeout timeout();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

4、通过 RealCall 发送 同步 / 异步 Request 请求

RealCall 实现了上述 Call 接口的各项功能 , 主要关注其实现 Call 接口的 execute 同步请求方法 , enqueue 异步请求方法 ;

代码语言:javascript
复制
final class RealCall implements Call {
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);
      // 返回一个责任链 
      return getResponseWithInterceptorChain();
    } finally {
      client.dispatcher().finished(this);
    }
  }

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

( 1 ) 、同步 Request 请求

同步请求方法 , 返回一个责任链 , 在该方法中可以清楚的看到 OkHttp 的 Get 请求具体做了哪些步骤 ;

在该方法中通过添加不同功能的拦截器 , 实现相关业务路基 ;

代码语言:javascript
复制
  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) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      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);
      }
    }
  }

( 2 ) 、异步 Request 请求

在 RealCall 的 enqueue 异步请求方法中 , 最终调用的还是 OkHttpClient 的 dispatcher 进行调度 ;

在上一篇博客 【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 ) 已经提到过 OkHttpClient 的 Dispatcher dispatcher 成员 , 是 Get / Post 方法的请求线程调度器 ;

代码语言:javascript
复制
final class RealCall implements Call {
  final OkHttpClient client;
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
}

二、OkHttp 异步 Request 请求源码分析


异步 Request 请求涉及到线程调度 , 比较复杂 ;

OKHttpClient 调用 newCall 获取 RealCall , 然后调用 RealCall 的 enqueue 方法进行异步 Get/Post 请求 , 在该方法中最终调用 OKHttpClient 对象中的 Dispatcher dispatcher 线程调度器 的 enqueue 方法 , 进行异步请求 ;

1、Dispatcher 调度器 enqueue 方法分析

在 Dispatcher 的 enqueue 方法中 , 调用了 findExistingCallWithHost 方法获取 AsyncCall , 然后在方法最后调用了 promoteAndExecute 进行后续执行异步任务操作 ;

代码语言:javascript
复制
/**
 * Policy on when async requests are executed.
 *
 * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
 * own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
 * of calls concurrently.
 */
public final class Dispatcher {
  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }
  
  @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;
  }
}

AsyncCall 继承了 NamedRunnable , NamedRunnable 实现了 Runnable 接口 , AsyncCall 本质是 Runnable ;

代码语言:javascript
复制
final class AsyncCall extends NamedRunnable
public abstract class NamedRunnable implements Runnable

2、Dispatcher 调度器 promoteAndExecute 方法分析

分析 promoteAndExecute 方法 : 将符合条件的调用从 readyAsyncCalls 提升为 runningAsyncCalls , 并且在线程池中调用它们 ; 这些操作必须同步调用 , 因为执行这些调用需要调用用户代码 ;

最终的异步请求执行调用的是 AsyncCall 的 executeOn 方法 ;

代码语言:javascript
复制
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());

Dispatcher | promoteAndExecute 方法源码 :

代码语言:javascript
复制
  /**
   * Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
   * them on the executor service. Must not be called with synchronization because executing calls
   * can call into user code.
   *
   * @return true if the dispatcher is currently running calls.
   */
  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        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);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

3、AsyncCall 的 executeOn 方法分析

AsyncCall 的 executeOn 方法中 , 主要使用了 传入的 ExecutorService executorService 线程池 , 执行异步请求任务 ;

RealCall $ AsyncCall | executeOn 方法代码 :

代码语言:javascript
复制
final class RealCall implements Call {
  final class AsyncCall extends NamedRunnable {
    /**
     * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }
  }
}

三、OkHttp 请求时序图参考


四、博客资源

GitHub : https://github.com/han1202012/OkHttp

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OkHttp 系列文章目录
    • 文章目录
    • 一、分析 OkHttp 执行原理
      • 1、创建 OkHttpClient
        • 2、创建 Request
          • 3、获取 RealCall
            • 4、通过 RealCall 发送 同步 / 异步 Request 请求
              • ( 1 ) 、同步 Request 请求
              • ( 2 ) 、异步 Request 请求
          • 二、OkHttp 异步 Request 请求源码分析
            • 1、Dispatcher 调度器 enqueue 方法分析
              • 2、Dispatcher 调度器 promoteAndExecute 方法分析
                • 3、AsyncCall 的 executeOn 方法分析
                • 三、OkHttp 请求时序图参考
                • 四、博客资源
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档