OKHttp 源码分析

基本使用

  1. 异步GET

     Request.Builder builder = new Request.Builder().url("https://www.baidu.com/");
     builder.method("GET",null);
     Request build = builder.build();
     OkHttpClient client = new OkHttpClient();
     Call call = client.newCall(build);
     call.enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
    
         }
    
         @Override
         public void onResponse(Call call, Response response) throws IOException {
             System.out.println(response.body().string());
         }
     }); 
    
  2. 异步POST

     FormBody.Builder builder = new FormBody.Builder().add("XXX", "XXX");
     FormBody build = builder.build();
     Request request = new Request.Builder().url("https://.........").post(build).build();
     OkHttpClient client = new OkHttpClient();
     Call call = client.newCall(request);
     call.enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
    
         }
    
         @Override
         public void onResponse(Call call, Response response) throws IOException {
             System.out.println(response.body().string());
         }
     });
    

用法都非常简单,只用简单的调用一下,就能实现所想需要实现的GET,POST请求。

源码分析

我们还是从请求处理开始分析起:

Call call = client.newCall(request);

我们通常就会这样声明一个 Call 对象 然后执行异步 enqueue() 或者同步 execute()方法。

点进 newCall 里面:

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

这里实际就是返回了 RealCall 这个对象, 而接下来 网络请求 实际上就是调用 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));
}

从上面可以得知请求是从 dispatcher 完成的。

接下来分析 dispatcher

在 Dispatcher.java 下

先看看这个类所定义的变量吧

最大并发请求数
private int maxRequests = 64;
每个主机最大的请求数
private int maxRequestsPerHost = 5;

/** Executes calls. Created lazily. */
消费者线程池
private @Nullable ExecutorService executorService;

/** Ready async calls in the order they'll be run. */
将要运行的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
正在运行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
正在运行的同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

由此可以看出他维护个各种队列,把不同的Call放置相对应的队列中去,队列中的请求最后会一个个的进行访问。

接下来看看构造函数

/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;

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

public Dispatcher() {
}

构造函数有两个,一个构造函数是带参的,这个参数就是自己实现的线程池,如果我们调用不带参的构造函数时,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;
}

从上面看到他会创建一个默认的线程池 。

那么接下来在异步请求的时候会调用enqueue() 方法 ,而从下面我们可以看到 当异步请求队列 数量小于 64 并且正在运行的请求主机数小于5时,会把请求放置在 runningAsyncCalls 中 并在线程池中执行。 否则就加入到 readyAsyncCalls 中进行缓存。

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
}

然后就会执行以下方法

executorService().execute(call);

executorService()会返回一个线程池 , 如果使用的是默认线程池的话:那么在 ThreadPoolExecutor 内 ,所要传进去的是 Runnable 这个接口,

public void execute(Runnable command) {
    .....   
}

再看看 AsyncCall 这个类吧 :

AsyncCall 是 RealCall 的 内部类 。 他继承了NamedRunnable,而NamedRunnable正是实现了Runnable方法。

在NamedRunnable里面实现了run() 方法 ,run() 里面调用了他的一个 抽象方法 :

protected abstract void execute();

那么子类就只要实现 execute 方法就可以了:

所以就回到了 AsyncCall 中实现的 execute() 方法

@Override protected void execute() {
  try{
  ......
  } finally {
    client.dispatcher().finished(this);
  }
}

先来看看这个方法最后都做了什么吧 :

无论如何都会调用到 client.dispatcher().finished(this); 这个方法

进去 finished 方法 ,我们看到最后调用的方法为以下:

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      
      注 1   
      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();
    }
}

从上面可以看到 calls 是 runningAsyncCalls ,首先他先从队列移除,然后执行
promoteCalls() ,随后将更新 runningCallsCount 和 idleCallback 。

主要的方法就是 promoteCalls()

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

从for语句可以明显看出 因为 runningAsyncCalls 的请求被移除了 , 那么就会从 readyAsyncCalls 中拿出请求向 runningAsyncCalls 补充 。

接着我们回到 execute() 方法 ,看看其中实现了什么:

@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) {
    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);
  }
}

从名字可以看出 Response 就是请求的结果, 而网络请求交给了
getResponseWithInterceptorChain()

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    return chain.proceed(originalRequest);
}

构建了一大堆拦截器,最后 创建了 RealInterceptorChain 这个 对象,他实际作用就是一个 拦截链 。

最后把请求交给了拦截链的proceed () 方法:

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
}

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
    
    .....
    
    注 1 
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    .....
    
    return response;
}

注 1 : 从上面可以看出了这个拦截链会不断的调用下一个拦截器。从而现在分析一下所添加的拦截器吧。

在getResponseWithInterceptorChain() 方法中,里面就是添加了一堆拦截器

interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
  interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));

在拿取拦截链里面的拦截器,然后一一执行。

retryAndFollowUpInterceptor -- 这拦截器主要是做重试,网络错误,以及请求重定向的一些操作。

BridgeInterceptor -- 这个拦截器,主要把用户的请求转换为网络的请求,负责对Request和Response报文进行加工。

CacheInterceptor -- 缓存拦截器

ConnectInterceptor -- 连接拦截器,主要是处理连接服务器,以及http , https的包装

CallServerInterceptor -- 服务拦截器,主要是发送(write、input)、读取(read、output)数据。也是拦截器的最后一个环节,这里就真正拿到了网络的结果了。

拦截器会不断的调用下一个拦截器,最后全部执行完。在每个拦截器里面,你可以看到类似的代码 : Response networkResponse = chain.proceed(...); 这就是调用了下一个拦截器。

拦截器所要执行的内容基本就如上的简述了。

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

推荐阅读更多精彩内容