OKHttp 源码阅读(一) 之 主流程源码解读

子曰:温故而知新,可以为师矣。 《论语》-- 孔子


在讲源码之前,我们先来略提一下 OKHttp 的简单实用,本篇文章主要还是对于 OKHttp 主流程的源码进行梳理。


一、基本使用

1.1 添加 Gradle依赖

implementation("com.squareup.okhttp3:okhttp:3.10.1")

1.2 Get 请求

 // get 请求

    public void asyncGet(){
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

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

        final Call call = okHttpClient.newCall(request);

        // 同步
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    System.out.println(response.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // 异步
        
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d("TAG","onFailure:"+e.toString());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.d("TAG","onResponse"+response.toString());
            }
        });

    }

1.3 post 请求

// post 请求
    public void asyncPost(){
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

        RequestBody formBody = new FormBody.Builder()
                .add("ip","59.108.54.37")
                .build();

        Request request = new Request.Builder()
                .url("http://ip.taobao.com/service/getIpInfo.php")
                .post(formBody)
                .build();

        final Call call = okHttpClient.newCall(request);
        
        // 同步
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    System.out.println(response.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // 异步
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d("TAG","onFailure:"+e.toString());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.d("TAG","onResponse"+response.toString());
            }
        });
    }

好了,现在正式开始本篇的主要内容,各位食客慢慢享用。

二、主线流程源码解读

1. 创建 OkHttp 对象。

OkHttpClient okHttpClient = new kHttpClient.Builder().build();

首先创建一个 OkHttpClient 的对象,这个对象的创建采用了 构建者 模式。关于此模式,在后面的文章中会详细说到,这边先知道是通过这种模式创建的就行。

查看 OkHttpClient 这个类的源码,这个类里面有一个内部静态类 Builder 类。

 // OkHttpClient 类中的静态内部类 Builder 
 public static final class Builder {
      Dispatcher dispatcher;
      //...
     
      public Builder() {
      dispatcher = new Dispatcher();
        //...  
      }
 }

所以调用 OkHttpClient.Builder() 方法得到 Builder 对象。我们可以看到这个 Builder 类中有很多属性,例如上面的 dispatcher,我们可以这样设置:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .dispatcher(null) // 假设传null
                .build();

那么我们在创建静态内部类 Builder 类的时候,就给此类中的 dispatcher 这个属性赋值了,然后调用这个类的 build() 方法,将 Builder 中的 dispatcher 属性值赋值给 OkHttpClient 类中的 dispatcher,怎么传值的,我们来看一下 build() 方法:

 // OkHttpClient 类中的静态内部类 Builder 类中的 build() 方法
 public OkHttpClient build() {
      return new OkHttpClient(this);
    }

build() 方法返回一个 OkHttpClient 实例对象,它调用的是传入 Builder 类的带形参的构造方法:

// 含有 Builder 形参的 OkHttpClient 类的构造方法
OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    //....
    }

通过这个构造方法,就可以看出来将传入的Builder 中的 dispatcher 属性值赋值给 OkHttpClient 类中自身的 dispatcher属性。


2. 创建 Request 对象。

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

Request 对象的创建与 OkHttp 对象的创建过程类似,都是使用的构建者模式,同上,关于此模式,在后面的文章会讲解,这边先跳过。


3. 创建 Call 对象。

Call call = okHttpClient.newCall(request);

这边调用了 newCall 方法,我们点击此方法,看一下源码:

// OkHttpClient 类中的 newCall() 方法
@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

可以看到,此方法返回的是一个 Call 对象,也就是 Call 接口,源码中返回的是 RealCall,那么不用看也知道这个 RealCallCall 接口的实现类。


4. 异步调用。

call.enqueue(new Callback() {
            @Override
            public void onFailure( Call call,  IOException e) {
                Log.d("TAG","onFailure:"+e.toString());
            }

            @Override
            public void onResponse( Call call,  Response response) throws IOException {
                Log.d("TAG","onResponse"+response.toString());
            }
        });

用上面的分析,我们可以知道其实实际上是调用了源码中 RealCallenqueue() 方法,也就是调用 RealCall 类中重写了 Call 接口中的 enqueue() 方法。

// RealCall 类中的 enqueue 方法
@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));
  }

在这个方法中,有一个同步锁,executed 默认为 false,一旦请求大于 1 次,那么就会抛出异常。在这个方法中,还有这么一行代码需要关注:

client.dispatcher().enqueue(new AsyncCall(responseCallback));

这边的 client 就是在 OkHttpClient 类中 调用 newCall() 方法,也就是 RealCall.newRealCall(OkHttpClient client ,...) 传入的第一个参数,也就是先拿到 OkHttpClient 类中的 dispatcher(调度器),然后调用 Dispatcher 类中的 enqueue 方法,同时将传入的 callback 作为参数传入到了 AsyncCall 的构造器中作为形参,并且创建了 AsyncCall


对上面的分析汇总一下:

  • OKHttpClient.newCall() 创建的是 RealCall 对象,call.enqueue() 实际调用RealCall.enquue() ,此方法中有个 client.dispatcher().enqueue() 方法,实际调用传入的 OKHttpClient 类中 dispatcher 属性所在类的 enqueue() 方法。

好的,我们接着来看 Dispatcher 类中的 enqueue 方法:

// Dispatcher 类中的 enqueue 方法
synchronized void enqueue(AsyncCall call) {
    // 同时运行的异步任务小于 64 && 同时访问(同一个)服务器小于 5 个 
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      // 把运行任务加入到运行队列中
      runningAsyncCalls.add(call);
      // 执行异步任务
      executorService().execute(call);
    } else {
      // 加入到等待队列中
      readyAsyncCalls.add(call);
    }
  }

runningAsyncCallsreadyAsyncCalls 分别是 运行队列等待队列。考虑到性能,使用的是 双端队列。那么现在我们就看看 executorService().execute(call) 这一行代码,这行代码的意思是使用线程池执行 call 任务,那么线程池是如何工作的呢?我们可以看到是使用了 ThreadPoolExecutor 类来创建的线程池,对于此方法中的参数如下解释:

int corePoolSize:核心线程数。

int maximumPoolSize:线程池非核心线程池数,线程池规定大小。

long keepAliveTime: 时间数值

TimeUnit unit:时间单位

BlockingQueue<Runnable> workQueue:超出的任务会添加到队列中,缓存起来。


参数一 和 参数二 的设置的值体现了缓存的思想,参数三 和参数四 是在 正在执行的任务数 大于 核心线程数的时候才有作用,打个比方,如果核心线程数是3,我们要执行的任务是 20 个,参数三设置为 60 ,参数四设置为 秒,那么我们开启线程后,线程池中一开始会有 4 个线程任务,这四个线程任务执行后,参数三发挥作用,闲置 60s,过了闲置 60s 后,会回收掉任务,那么在 60s 内如果还有任务要执行,那么就会复用之前已开启的线程。我们举个栗子:


public class MyThreadPool {

    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());

        for (int i = 0;i<20;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("当前线程,执行耗时任务,线程是"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}



// 运行结果
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-3
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-2
当前线程,执行耗时任务,线程是pool-1-thread-4
当前线程,执行耗时任务,线程是pool-1-thread-5
当前线程,执行耗时任务,线程是pool-1-thread-1
当前线程,执行耗时任务,线程是pool-1-thread-3

在创建线程池的时候,Util.threadFactory("OkHttp Dispatcher", false) 这个参数通过 Util帮助类,使用线程工厂给线程设置名字,同时设置不使用守护线程,也就是这个 false 参数。我们可以看一下 threadFactory() 这个方法源码。

public static ThreadFactory threadFactory(final String name, final boolean daemon) {
    return new ThreadFactory() {
      @Override public Thread newThread(Runnable runnable) {
        Thread result = new Thread(runnable, name);
        result.setDaemon(daemon);
        return result;
      }
    };
  }

setDaemon(false) 就是设置不使用守护线程,守护线程的意思是 比如说我们在 main() 方法中写了开了一个线程,线程里写了一个死循环,如果线程 setDaemon(true),那么 执行一个时间片段后,JVM 虚拟机会认为当前进程需要结束,不会一直死循环下去,如果设置为 false,那么会一直死循环下去。


线程池说完了,那么知道真正执行耗时操作的就是这个 AsyncCall,那么这个 AsyncCall 是什么?

// RealCall 中的 内部类
final class AsyncCall extends NamedRunnable {
    //...
}

可以看到这个类继承 NamedRunnable 类:

// NamedRunnable类
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();
}

我们可以看到 NamedRunnable 实现了 Runnalbe 接口 ,到时候执行耗时操作,那么就会走 run() 方法,而 run() 方法中的 execute() 是一个抽象方法,那么实际是走了继承 NamedRunnable 这个抽象类,并且重写了 execute() 方法的 AsyncCall 中的 execute() 方法,真真正正的执行耗时操作就是 AsyncCall 中的 execute() 方法。 也就是说我们需要看 AsyncCall 中的 execute() 方法。

//AsyncCall 类中的 execute() 方法
@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        // 通过责任链模式获取返回值 response
        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 {
        client.dispatcher().finished(this);
      }
    }

上面这一串代码还是稍微好理解的,可能有一点困惑的就是这边作者做的异常处理,通过 signalledCallback 这个标识位,一旦请求成功,源码中给出了回调,我们自己在回调方法里面写错了,那么 signalledCallback 标识位就会为 true,同时进入 catch 代码块,并且打印 Log 日志,说明此异常不是由 OKHttp 引起的,这里就是一个责任分明的思想体现。



写在文末

纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游

好了,关于 OKHttp 主流程的源码的阅读就说到这,各位看官食用愉快。我们下一篇文章见。


码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。

发布了7 篇原创文章 · 获赞 7 · 访问量 3143

猜你喜欢

转载自blog.csdn.net/wild_onlyking/article/details/104316910