分析OKHttp

整体架构

Interface——接口层:接受网络访问请求

Protocol——协议层:处理协议逻辑

Connection——连接层:管理网络连接,发送新的请求,接收服务器访问

Cache——缓存层:管理本地缓存

I/O——I/O层:实际数据读写实现

Inteceptor——拦截器层:拦截网络访问,插入拦截逻辑

官方文档是这样描述OkHttp

  • OkHttp is an HTTP client that’s efficient by default:
  • HTTP/2 support allows all requests to the same host to share a socket.
  • Connection pooling reduces request latency (if HTTP/2 isn’t available).
  • Transparent GZIP shrinks download sizes.
  • Response caching avoids the network completely for repeat requests.

OkHttp是一个高效的HTTP客户端,拥有以下特性:

  • HTTP/2支持对同一主机的所有请求共享套接字。
  • 使用连接池减少请求延迟(如果HTTP/2不可用)。
  • 透明GZIP减少下载大小。
  • 响应缓存可以完全避免网络重复请求。

核心类

  1. okhttp3.OkHttpClient:全局配置与网络操作入口

  2. okhttp3.Request:一次网络请求的url、body、header等

  3. okhttp3.Response:一次网络请求的响应

  4. okhttp3.RealCall:代表一个OkHttp封装的实际网络操作实例

  5. okhttp3.Dispatcher:操作okhttp3.RealCall,连接池,异步请求线程池封装

  6. okhttp3.Callback:异步请求的callback

  7. okhttp3.RealCall.AsyncCall#AsyncCall:对于okhttp3.Callback的封装,继承NamedRunnable实现Runnable接口,是OkHttp实现异步网络请求的重要组成部分

  8. okhttp3.internal.connection.Transmitter:

  9. okhttp3.internal.connection.Exchange:

  10. okhttp3.internal.Internal:

1.1 调用

OkHttp提供了两种调用方式:

  • 同步调用

  • 异步调用

1.1.1 同步调用

okhttp3.OkHttpClient#newCall(Request request)# execute() -> okhttp3.RealCall#execute

public Response execute() throws IOException {

        synchronized(this) {

            if (this.executed) {

                throw new IllegalStateException("Already Executed");

            }



            this.executed = true;

        }



        this.transmitter.timeoutEnter();

        this.transmitter.callStart();



        Response var1;

        try {

            this.client.dispatcher().executed(this);

            var1 = this.getResponseWithInterceptorChain();

        } finally {

            this.client.dispatcher().finished(this);

        }

        return var1;

    }
  1. 加锁,打上标记位(this.executed = true),避免多次调用RealCall#execute
  2. 将RealCall加入到同步队列
    3.this.client.dispatcher().executed(this)
    4.OkHttpClient.Dispatcher.runningSyncCalls.add(call)
    5.调用getResponseWithInterceptorChain实际执行HTTP请求
  3. 从同步队列中删除
  4. this.client.dispatcher().finished
    8.OkHttpClient.Dispatcher.runningSyncCalls.remove(call)
1.1.2 异步调用

okhttp3.OkHttpClient#newCall(Request request)# enqueue (Callback ) responseCallback) -> okhttp3.RealCall#enqueue

  1. [endif]加锁,打上标记位(this.executed = true),避免多次调用RealCall#execute

  2. 将Callback封装成AsyncCall,放入到异步执行队列中

    1. readyAsyncCalls#add
    2. AsyncCall继承NamedRunnable,也就实现了Runnable接口
    3. 调用Dispatcher#promoteAndExecute

2 OkHttp异步网络请求的实现

从1.1.2中已经有一个简单的异步请求分析,一个异步请求(AsyncCall)的AsyncCall#execute方法最终会被调用,在执行拦截器Chain后得到Response。

3 拦截器

   从第2节中,已经可以看到发起一个请求的重要代码是
   AsyncCall#execute(异步) OR RealCall#execute(同步),其中的核心是

1.获取并执行拦截器Chain得到Response- getResponseWithInterceptorChain

2.通知分发器任务完成- client.dispatcher().finished

  在不考虑异常的情况下,OkHttp发起一个HTTP请求,实际的网络操作都在getResponseWithInterceptorChain方法中体现。

3.1 构造拦截器链

Response getResponseWithInterceptorChain() throws IOException {

    // Build a full stack of interceptors.

    List<Interceptor> interceptors = new ArrayList<>();

    interceptors.addAll(client.interceptors());

    interceptors.add(new RetryAndFollowUpInterceptor(client));

    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, transmitter, null, 0,

        originalRequest, this, client.connectTimeoutMillis(),

        client.readTimeoutMillis(), client.writeTimeoutMillis());

    Response response = chain.proceed(originalRequest);

…

  }

其中主要逻辑分为两部分

1.创建一系列的拦截器,包括系统内置与用户自定义的拦截器

2.创建RealInterceptorChain,并执行proceed方法得到Response

拦截器顺序是

  1. 自定义拦截器(interceptors)

  2. RetryAndFollowUpInterceptor

  3. BridgeInterceptor

  4. CacheInterceptor

  5. ConnectInterceptor

  6. 自定义网络拦截器(networkInterceptors)

  7. CallServerInterceptor

那么RealInterceptorChain#proceed做了什么呢?

// Call the next interceptor in the chain.

RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,index + 1, request, call, connectTimeout, readTimeout, writeTimeout);

Interceptor interceptor = interceptors.get(index);

Response response = interceptor.intercept(next);

return response;

可以看到主要也是两部分

  • 创建下一个RealInterceptorChain,指明下一个执行的拦截器索引为index + 1

  • 执行(调用intercept)索引为index的拦截器

接下来从系统内置第一个拦截器RetryAndFollowUpInterceptor为例,看看在intercept方法中做了什么。

从名字中我们可以看出,这个拦截器的主要作用应该是处理请求的重试与重定向。

3.2 内置拦截器责则

RetryAndFollowUpInterceptor
  • 在网络请求失败后进行重试

  • 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接

BridgeInterceptor
  • 设置内容长度、编码

  • 设置内容长度,内容编码

  • 添加cookie

  • 设置一些http head,例如User-Agent: okhttp/3.14.2; keep-Alive等、

CacheInterceptor

当网络请求有符合要求的Cache时直接返回Cache

当服务器返回内容有改变时更新当前cache

如果当前cache失效,删除

ConnectInterceptor
  • 负责找到一个合适的Connection。其中ExchangeCodec持有一个可用的Connection。

重点代码:

exchangeFinder->okhttp3.internal.connection.Transmitter#prepareToConnect->

由RetryAndFollowUpInterceptor触发

  ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);

  Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
CallServerInterceptor

负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。

3.3 请求拦截链的整体流程

--TODO

4 任务队列(异步请求线程池)

在前面的分析中已经提及Dispatcher类是用于操作okhttp3.RealCall,对连接池,异步请求线程池封装。

与任务队列相关的字段:

readyAsyncCalls:待执行异步任务队列

runningAsyncCalls:运行中异步任务队列

runningSyncCalls:运行中同步任务队列

executorService:任务队列线程池

从代码可以看到,构建了一个线程池corePoolSize是0,最大线程数是Integer.MAX_VALUE,空闲时间是60秒,阻塞队列使用SynchronousQueue,这个阻塞队列不储存元素。

因此该线程池是来一个请求就会创建一个线程,当线程空闲60s后会被回收,由于corePoolSize是0,那么当60s没有新的请求,所有的线程都会给回收。

5 缓存策略

TO DO

6 连接池

在OkHttp中使用okhttp3.ConnectionPool来管理连接池,在okhttp3.ConnectionPool内部使用okhttp3.internal.connection.RealConnectionPool delegate来实际管理连接池。

okhttp3.internal.connection.RealConnectionPool

主要字段:

Executor executor:用于清除失效连接线程池

int maxIdleConnections:最大闲置连接数

long keepAliveDurationNs:keep alive时间

Runnable cleanupRunnable:清除失效连接

boolean cleanupRunning:put的时候设置为true,清除失效连接之后设置为false

Deque<RealConnection> connections:产生的Connection

RouteDatabase routeDatabase:

主要方法:

int idleConnectionCount():统计idle的connection数

boolean transmitterAcquirePooledConnection:尝试复用一个Connection并关联上transmitter

void put(RealConnection connection):添加一个Connection,并触发cleanup线程

boolean connectionBecameIdle(RealConnection connection):当一个Connection空闲根据条件判断唤醒cleanup线程或者直接remove这个Connection

void evictAll():扫描并关闭所有空闲连接

long cleanup(long now):实际进行清除失效连接操作

int pruneAndGetAllocationCount():标记泄露的连接

void connectFailed():连接失败

相关类

okhttp3.internal.connection.RealConnection

封装物理连接

List<Reference<Transmitter>>的引用计数(TransmitterReference)

okhttp3.internal.connection.Transmitter

okhttp3.internal.connection.RouteDatabase

用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去

7 类分析

7.1 okhttp3.internal.connection.Transmitter

Bridge between OkHttp's application and network layers. This class exposes high-level application layer primitives: connections, requests, responses, and streams.

OkHttp应用和网络层的桥梁。这个类暴露高级应用层的原语:连接,请求,响应,流

附录

项目地址:https://github.com/square/okhttp

项目文档:https://github.com/square/okhttp/wiki

Easy OkHttp(对OkHttp的封装):https://gitee.com/yuanzizhenxin/easy-okhttp

源码分析(OkHttp 3.7):https://yq.aliyun.com/articles/78105

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

推荐阅读更多精彩内容