Okhttp源码解析(一)

Okhttp(官网github)作为安卓主流的网络加载框架,其基本使用相信大家已经很熟悉,通过简单的依赖和设置参数即可完成网络的请求,且包含丰富的API方便调用,例如简单的图片加载实例。本文旨在学习其源码相关的知识,了解网络加载背后源码的执行流程,方便更好的使用该框架和解决问题。

1、网络加载的执行流程

在这里插入图片描述

 OkHttpClient client = new OkHttpClient
                .Builder()
                .build();

        Request request = new Request
                .Builder()
                .url("http://www.baidu.com")
                .build();

        Call call = client.newCall(request);

        //同步
        try {
            Response response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }


        //异步请求
        call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("连接失败");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //请求处理,输出结果
                System.out.println(response.body());
            }
        });

结合上图和实例代码,okhttp的使用主要包括四部分:

  • 创建OkHttpClient :设置一些基本的参数例如时间、缓存和拦截器等,同时初始化一些参数,例如Dispatcher、ConnectionPool和参数的默认设置等;
  • 创建Request :该部分主要是请求参数的设置,例如URL、method、body和headers等;
  • 获取新的Call :将创建的OkHttpClient 和Request 相结合构建一个请求对象;
  • 执行网络请求操作:通过execute()或enqueue()真正执行网络请求。

1.1 创建OkHttpClient

public Builder() {
      dispatcher = new Dispatcher();
     ...
      connectionPool = new ConnectionPool();
    ...
    }

在创建OkHttpClient 时,主要是通过build的形式构建其对象,相比使用时经常用到的API的设置(例如时间、缓存和拦截器等),我们这里关注的是两个参数:Dispatcher和ConnectionPool,其中Dispatcher是okhttp的构建核心,主导okhttp请求的主要流程,特别是异步请求时,管理其队列,下文会详细讲解。ConnectionPool是作为请求的连接池,用于请求的复用和策略相关的操作。

1.2 创建Request

与OkHttpClient 类似也是采用构建者的方式,Request 最主要的功能是设置URL、method和body,默认采用的是GET请求方式,可通过.post或.put方式切换至其他请求模式,其中body主要包括FormBody和MultipartBody。

1.3 获取新的Call

由于Call是一个接口,主要通过其实现类RealCall来完成OkHttpClient 和Request相结合,构建RealCall 。

//RealCall .java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

1.4 执行网络请求操作

完成以上三步的准备工作后,开始执行真正的网络请求操作,根据调用的方法(execute()或enqueue())来区分同步还是异步的请求操作,这里需要注意,同步和异步其中最大的区别为是否阻塞当前的工作线程,异步请求中会开启一个新的工作线程执行网络请求操作,通过callback的方式返回请求结果。

2、源码分析

在通过RealCall来完成OkHttpClient 和Request相结合后,开始执行网络请求的操作,根据execute()或enqueue()区分是同步请求或是异步,下面通过源码分析同步和异步请求的逻辑处理。

2.1 同步执行

在这里插入图片描述

//RealCall .java
@Override public Response execute() throws IOException {
   //1 
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //2
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
    //3 
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
    //4 
      client.dispatcher().finished(this);
    }
  }

结合UML和源码分析,其执行流程如下:

  1. 状态判断:通过executed 变量判断是否已经执行请求操作, 如已经执行则抛出异常;
  2. 状态捕获:捕获请求中一些堆栈信息便于流程分析;
  3. 请求操作:通过Dispatcher的executed()执行真正的网络请求操作,经过拦截器中一系列链式的请求操作后,将结果返回;
  4. 清理操作:请求完成后,通过Dispatcher的finshed()方法清理堆栈中的请求信息。

以上流程可以看出,请求的主要过程都是通过Dispatcher来完成,重点分析其调用的两个方法。
executed

//Dispatcher.java
 synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

在1.1中OkHttpClient的Builder的构建时,默认就创建好了Dispatcher对象,因此此时可以直接调用其方法,在executed中方法比较简单,直接将请求的RealCall 对象添加至同步正在执行的队列中。而后通过getResponseWithInterceptorChain()获取网络请求的Response;

finished
网络请求完成后,在最后的finally中会调用finished方法,其主要功能是清理堆栈中的对象。

//Dispatcher.java
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
...
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    ...
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

2.2 异步执行

在这里插入图片描述

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

异步请求流程与同步类似,但是在调用网络请求的方法为enqueue,并将responseCallback通过AsyncCall封装为参数,传递至Dispatcher的enqueue方法中。查看AsyncCall的源码可以发现,其继承自NamedRunnable,而NamedRunnable实现了Runnable方法,因此在执行异步操作时,会开启一个新的工作线程,避免线程阻塞,在AsyncCall中真正实现网络 的请求操作。

在执行Dispatcher的enqueue方法时,主要是将封装好的AsyncCall添加至队列中,在Dispatcher类针对异步定义了两个队列:readyAsyncCalls和runningAsyncCalls,用于存储等待线程和正在执行的线程。添加至那个队列通过如下的判断:

  • 添加至正在运行的异步队列:正在运行的异步队列<64&&当前网络请求<5
  • 添加至等待队列:当大于maxRequests 或maxRequestsPerHost添加至等待队列中
//RealCall .java
 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

当添加至正在执行的队列后,开始通过executorService线程池执行call线程,executorService的创建的参数比较有意思,如下所示,其核心的四个参数,corePoolSize=0,maximumPoolSize= Integer.MAX_VALUE,keepAliveTime =60,TimeUnit = TimeUnit.SECONDS。
其特点是没有核心线程,只有非核心线程,当异步请求加入线程池后,开始执行请求操作,并且在线程池完成请求操作后,其余未执行的线程会在60s后自动销毁。
这里需要注意,理论上可以添加Integer.MAX_VALUE,但是由于runningAsyncCalls的限制条件(maxRequests 和maxRequestsPerHost)所以不会无限添加。方法中添加synchronized 是保证executorService调用的单例性。

//RealCall .java
 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

添加至线程池后,开始开启新的线程执行网络请求操作,上文可知AsyncCall最终实现了Runnable方法,并在run()调用抽象方法execute(),下面查看一下AsyncCall重写execute()的执行流程。

//RealCall .java
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
...

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        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) {
  ...
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
       ...
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

执行流程包括:

  • 执行网络请求链:和同步类似,通过getResponseWithInterceptorChain()获取请求结果;
  • 是否调用取消操作:如调用取消回调至onFailure否则,将Response返回;
  • 是否包含异常:如包含则调用onFailure和eventListener进行回调和时间记录;
  • 清理操作:该步较为重要,用于清理和准备队列和执行队列中数据的添加。
//Dispatcher.java
void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

在Dispatcher中,最终调用的是finished的重载方法中,如下所示。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
   ...
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

该方法在同步时,也会调用,但是不同的是传入的promoteCalls为true,则会执行promoteCalls方法 ,如下所示查看该源码发现,其主要作用就是readyAsyncCalls中的队列传递至runningAsyncCalls中。其他执行的流程与同步类似。

//Dispatcher.java
private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

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

3、小结

本文主要通过OkHttpClient的简单的请求实例,来分析同步和异步请求的源码执行流程,其中有一个比较重要的是Dispatcher类,负责请求事件的分发和队列的维护。然而真正的数据请求是调用getResponseWithInterceptorChain(),通过拦截器链逐步的获取请求结果,下文会重点分析Interceptor相关的源码流程。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值