Okhttp主流程源码浅析(1)

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主流程深一步了解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 前几天在在一本书上看到这样一句话 如果仅从微观的视角关注每一个单独的点,可能会因为看不到整体而迷失方向。 所以不会...
    Utte阅读 677评论 0 0
  • 1.OkHttp源码解析(一):OKHttp初阶2 OkHttp源码解析(二):OkHttp连接的"前戏"——HT...
    隔壁老李头阅读 15,682评论 36 72
  • 关于okhttp是一款优秀的网络请求框架,关于它的源码分析文章有很多,这里分享我在学习过程中读到的感觉比较好的文章...
    蕉下孤客阅读 3,599评论 2 38
  • 原创声明:本文参加“423简书故事节”,本人承诺文章内容为原创。 心容出生在1976年,改革开放的那一年。 在农村...
    酉玖呵阅读 470评论 0 8
  • 人最有意思的生活状态就是,明明觉得生活每天单调如一,似乎并没有很多有意义的时刻,但当我打开空间说说翻开过去每一天的...
    江边走阅读 169评论 0 0