okhttp源码分析

如需转载请评论或简信,并注明出处,未经允许不得转载

目录

前言

okhttp是square开源的轻量级网络框架

  • 官网:https://square.github.io/okhttp/
  • github地址:https://github.com/square/okhttp

依赖方式

"com.squareup.okhttp3:okhttp:4.2.1"

使用步骤

先来看看okhttp的基本使用方法。本文主要介绍原理,更多的使用方式可参考okhttp官网

GET请求

//1.创建OKHttpClient对象
OkHttpClient client = new OkHttpClient();

String get(String url) throws IOException {
  //2.创建Request对象
  Request request = new Request.Builder()
      .url(url)
      .build();
    //3.创建NewCall对象
  //4.执行newCall.execute(),生成response对象
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

POST请求

//1.创建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2.设置mediaType
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

String post(String url, String json) throws IOException {
  //3.创建请求体
  RequestBody body = RequestBody.create(JSON, json);
  //4.创建Request对象
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  //5.创建NewCall对象
  //6.执行newCall.execute(),生成response对象
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

可以看出GET请求和POST请求的步骤其实都大同小异,主要分为如下几步

  1. 创建okHttpClient对象

OkHttpClient对象有两种创建方式,一种是直接new OKHttpClient(),还有一种可以通过建造者模式new OkHttpClient.Builder()添加其他属性。实际上okhttp在默认的构造方法中就已经赋值了很多属性的默认值

  1. 创建request对象

每一个http请求都包含请求url、请求方法(GET/POST)、请求头(Header),同时也可能包含请求体(Body

  1. 创建response对象

response就是对request请求的一个回复,通过执行newCall中的execute()生成

源码分析

okhttp支持同步请求和异步请求,execute()是同步请求,enqueue()是异步请求。下面我们通过源码来看一下response是如何创建的(本人当前使用的okhttp版本的源码是基于kotlin)

同步请求

RealCall.kt

  override fun execute(): Response {
    synchronized(this) {
      //1.先检查newcall是否被执行过,被执行过会抛出异常
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.timeoutEnter()
    transmitter.callStart()
    try {
      //2.通过dispatcher调度器执行
      client.dispatcher.executed(this)
      //3.通过执行一系列拦截器链,最终返回response
      //☆关于拦截器的内容下文会重点分析
      return getResponseWithInterceptorChain()
    } finally {
      //4.执行完请求后,将请求从dispatcher调度器移除
      client.dispatcher.finished(this)
    }
  }

Dispather.kt

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的同步请求队列
private val runningSyncCalls = ArrayDeque()
  
@Synchronized internal fun executed(call: RealCall) {
    //将call请求放到双向队列中
  runningSyncCalls.add(call)
}

异步请求

RealCall.kt

override fun enqueue(responseCallback: Callback) {
  synchronized(this) {
    //1.和同步请求一样,先检查newcall是否被执行过,被执行过会抛出异常
    check(!executed) { "Already Executed" }
    executed = true
  }
  transmitter.callStart()
  //2.执行dispatcher.enqueue()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

Dispather.kt

/** Ready async calls in the order they'll be run. */
//缓存等待的异步请求队列
private val readyAsyncCalls = ArrayDeque()

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在执行的异步请求队列
private val runningAsyncCalls = ArrayDeque()

@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
  get() {
    if (executorServiceOrNull == null) {
      //执行异步任务的线程池创建
      //SynchronousQueue是一个内部只能包含一个元素的队列。
      //插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。
      //同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
      //类似生产者和消费者模型
      executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
          SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
    }
    return executorServiceOrNull!!
  }

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    //先将请求添加到缓存等待队列
    readyAsyncCalls.add(call)
    if (!call.get().forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host())
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  //执行请求
  promoteAndExecute()
}

  private fun promoteAndExecute(): Boolean {
    assert(!Thread.holdsLock(this))

    val executableCalls = mutableListOf()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()
    //请求最大运行数不能超过64(maxRequests=64),最大主机连接数不能超过5(maxRequestsPerHost=5)
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
    //从缓存等待队列中移除
        i.remove()
        asyncCall.callsPerHost().incrementAndGet()
        executableCalls.add(asyncCall)
        //添加到正在执行的异步请求队列
        runningAsyncCalls.add(asyncCall)
      }
      //运行数>0说明正在运行
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //executorService是一个线程池ThreadPoolExecutor对象
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

Dispather功能

  1. 维护请求的状态

一个Call只能被执行一次,否则抛出异常

  1. 维护请求队列
  • runningSyncCalls:正在运行的同步请求队列
  • readyAsyncCalls:正在执行的异步请求队列,请求最大运行数不能超过64,最大主机连接数不能超过5
  • runningAsyncCalls:缓存等待的异步请求队列
  1. 维护线程池

创建了一个阀值是Integer.MAX_VALUE的线程池,它不保留任何最小线程,随时创建更多的线程数,而且如果线程空闲后,只能多活60秒。所以也就说如果收到20个并发请求,线程池会创建20个线程,当完成后的60秒后会自动关闭所有20个线程。他这样设计成不设上限的线程,以保证I/O任务中高阻塞低占用的过程,不会长时间卡在阻塞上

拦截器

通过response的创建过程的分析,我们发现okhttp在返回response的过程中,会经过一系列的拦截器

拦截器是okhttp提供的一种强大的机制,可以监视,重写和重试网络请求。下面是一个简单的拦截器,用于记录请求和相应的日志

public class LoggingInterceptor implements Interceptor {
    private static final String TAG = "LoggingInterceptor";

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
                
            //1.请求前--打印请求信息
        long t1 = System.nanoTime();
        Log.i(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));
                //网络请求,并继续执行拦截器链
        Response response = chain.proceed(request);

        //3.网络响应后--打印响应信息
        long t2 = System.nanoTime();
        Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

拦截器可以链接。假设同时具有压缩拦截器和校验拦截器,则确定是先压缩数据然后对校验和进行校验,还是先对数据进行校验和然后进行压缩。okhttp使用ArrayList来管理拦截器列表,并通过责任链模式按顺序执行拦截器。用户可传入的 interceptor 分为两类:Application IntercetorNetwork Interceptor

拦截器.png

Application interceptors 应用拦截器
okClient.addInterceptor(new LoggingInterceptor())
Application Interceptor 是第一个 Interceptor 因此它会被第一个执行,因此这里的 request 还是最原始的。而对于 response 而言呢,因为整个过程是递归的调用过程,因此它会在 CallServerInterceptor 执行完毕之后才会将 response 进行返回,因此在 Application Interceptor 这里得到的 response 就是最终的响应,虽然中间有重定向,但是这里只关心最终的 response

  1. 不需要去关心中发生的重定向和重试操作。因为它处于第一个拦截器,会获取到最终的响应
  2. 只会被调用一次,即使这个响应是从缓存中获取的
  3. 只关注最原始的请求,不去关系请求的资源是否发生了改变,我只关注最后的 response 结果而已
  4. 因为是第一个被执行的拦截器,因此它有权决定了是否要调用其他拦截,也就是 Chain.proceed() 方法是否要被执行
  5. 因为是第一个被执行的拦截器,因此它有可以多次调用 Chain.proceed() 方法,其实也就是相当与重新请求的作用了

Network Interceptors 网络拦截器
okClient.addNetworkInterceptor(new LoggingInterceptor())
NetwrokInterceptor 处于第 6 个拦截器中,它会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request 请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部是发生了一次重定向操作,所以NetworkInterceptor 可以比 Application Interceptor 得到更多的信息了

  1. 因为 NetworkInterceptor 是排在第 6 个拦截器中,因此可以操作经过 RetryAndFollowup 进行失败重试或者重定向之后得到的resposne
  2. 为响应直接从 CacheInterceptor 返回了
  3. 观察数据在网络中的传输
  4. 可以获得装载请求的连接。

注意事项

  1. 推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。
  2. response.body().string()只调用一次
  3. 每一个CallRealCall)只能执行一次,否则会报异常
  4. 子线程加载数据后,主线程刷新数据

总结

你可能感兴趣的:(okhttp源码分析)