美文网首页
Okhttp主流程源码浅析(1)

Okhttp主流程源码浅析(1)

作者: wenou | 来源:发表于2018-06-19 11:10 被阅读29次
HttpClient关系图:
okhttp关系图

okhttp的基本使用:

 //1.TODO: 创建HttpClient
 HttpClient client = new HttpClient.Builder().build();
        //2.TODO: 创建Request
        Request request = new Request.Builder()
                .url("http://www.baidu.com/")
                .build();
        //3.TODO: 执行请求
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) {
                
            }
        });

基本使用很简单,分三步:
1.通过HttpClient.Builder().build()创建一个HttpClient
2.通过Request.Builder()创建一个请求Request
3.通过httpClient.newCall创建一个Call,并且把请求Request传入到call里面,然后去执行,并且拿到响应回调

源码分析:

一般的看源码都是从主流程看起,这里就从第一步看起

1.HttpClient.Builder().build()
从第一张关系图可以看出,HttpClient是控制整个流程的控制器,所以创建HttpClient是通过构建者模式builder来创建,可以配置一些信息

例如:设置超时时间,添加自己的拦截器,设置失败重连...等等

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                            .connectTimeout(10, TimeUnit.SECONDS)
                            .readTimeout(15, TimeUnit.SECONDS)
                            .writeTimeout(15, TimeUnit.SECONDS)
                            .addInterceptor(interceptor)
                            .retryOnConnectionFailure(true);

2.创建请求,Request.Builder()

 Builder(Request request) {
      this.url = request.url;  
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

Request的Builder可以配置请求路径url ,请求体body,请求头...

3.执行请求

Call call = client.newCall(request);
call.enqueue(new Callback() {...}

这里先是RealCall call = new RealCall(client, originalRequest, forWebSocket); 创建一个RealCall并且把OkHttpClient和Request保存到RealCall里面,然后是调用RealCall的enqueue去执行请求

@Override public void enqueue(Callback responseCallback) {
    //TODO: 不能重复执行
    synchronized (this) {
     ...
    eventListener.callStart(this);
    //TODO: 交给 dispatcher调度器,进行调度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

这里会new AsyncCall(responseCallback)Callback保存到一个AsyncCall对象里面

AsyncCall继承NamedRunnable,实现Runnable,所以AsyncCall虽然是叫call,但是和Callback没有关系,是Runnable的子类

然后会把AsyncCall交给dispatcher调度器去调度执行

这里到了本章重点,调度相关

先来看下调度器的源码

public final class Dispatcher {
  private int maxRequests = 64;//TODO: 最大同时请求数
  private int maxRequestsPerHost = 5; //TODO: 同时最大的相同Host的请求数
  private @Nullable Runnable idleCallback;
  //TODO: 线程池,维护执行请求和等待请求  
  private @Nullable ExecutorService executorService;
  //TODO: 异步的等待队列,双端队列,支持首尾两端 双向开口可进可出
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  //TODO: 异步的执行对列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  //TODO: 同步的执行队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  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;
  }
  ...

Dispatcher调度器里面主要是有这些东西:

maxRequests : //最大同时请求数
maxRequestsPerHost : //同时最大的相同Host的请求数

线程池ExecutorService: 负责异步去执行请求和等待请求
等待队列readyAsyncCalls : 没有执行的任务,添加到这里等待执行
执行队列runningAsyncCalls : 正在执行的异步任务队列
执行队列runningAsyncCalls : 正在执行的同步任务队列

线程池是一个核心数量为0,最大线程数为Integer.MAX_VALUE,非核心线程闲置60秒回收的线程池

再回头看client.dispatcher().enqueue(new AsyncCall(responseCallback))做了什么?

dispatcher().enqueue()

synchronized void enqueue(AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) <
                maxRequestsPerHost) {
            //TODO: 加入运行队列 并交给线程池执行
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            //TODO:  加入等候队列
            readyAsyncCalls.add(call);
        }
    }

调度器会做一些判断
(runningAsyncCalls.size() < maxRequests,如果同时进行的请求没有超过并发数 64,并且runningCallsForHost(call) < maxRequestsPerHost),同一个host正在运行的线程没有超过5条,就会加入到运行队列,并交给线程池执行,否则就加入等待队列readyAsyncCalls.add(call);

这里先看加入运行队列并且线程池去执行的流程,上面说到AsyncCallRunnable的子类,所以当线程池去执行它的时候,会调用它的run()方法,而它的run()方法NamedRunnable里面实现了,会调用execute()方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        // TODO: 责任链模式,拦截器链  执行请求
        //TODO: 拿到回调结果
        Response response = getResponseWithInterceptorChain();
        ... //TODO: 删除部分代码

        }
      } finally {
        //TODO: 移除队列
        client.dispatcher().finished(this);
      }
    }
  }

execute()里面会调用getResponseWithInterceptorChain()方法去连接网络,发起http请求并且拿到服务器响应结果,这里主要是分析调度器,先不看网络连接的实现

okhttp怎样执行等待队列里面的任务??

这里使用了一个巧妙的设计,使用try...finally来管理任务队列,最终都会进入finally里面,调用client.dispatcher().finished(this);把执行完的任务从执行队列里面移除,并且执行等待队列里面的任务

 void finished(AsyncCall call) {
        //TODO: 传入执行队列, 执行的任务 call
        finished(runningAsyncCalls, call, true);
    }

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //TODO: 移除队列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            //TODO: 检查执行 readyAsyncCalls 中的请求
            if (promoteCalls) promoteCalls();
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }
        //TODO: 如果线程闲置中, 调用run 执行起来
        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

这里会调用!calls.remove(call)把执行完的任务从执行队列里面移除,然后调用promoteCalls()方法,检查执行等待队列readyAsyncCalls里面的任务,并且判断是否需要唤醒闲置线程if (runningCallsCount == 0 && idleCallback != null)调用idleCallback.run();

promoteCalls()检查执行等待队列

 private void promoteCalls() {
        //TODO: 检查 运行队列 与 等待队列
        if (runningAsyncCalls.size() >= maxRequests) return; 
        if (readyAsyncCalls.isEmpty()) return;

        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            //TODO: 相同host的请求没有达到最大
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                //TODO: : 加入执行队列,并且去执行
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }
            if (runningAsyncCalls.size() >= maxRequests) return;
        }
    }

这里同样会先判断是否超过最大执行数量maxRequests,并且相同host的请求没有达到最大,就会加入到执行队列,并且去执行,完成等待队列切换到执行队列的流程

整个调度的流程就完了

下一篇Okhttp主流程源码浅析(2),浅析责任链,对okhttp主流程深一步了解

相关文章

网友评论

      本文标题:Okhttp主流程源码浅析(1)

      本文链接:https://www.haomeiwen.com/subject/fojweftx.html