okHttp 框架源码学习

Retrofit,OkHttp,Okio Square 安卓平台网络层三板斧源码学习
基于 okhttp 3.9.0 版本 okhttp github 地址

使用方式

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}
  1. 构造一个 OkHttpClient
  2. 构造一个 Request
  3. 调用 OkHttpClient.newCall(Request request) 获得一个 Call 对象
  4. 执行 Call.execute() 获得 Response 对象
  5. 通过 Response.body() 获得 ResponseBody 对象

OkHttpClient 创建 http 请求源码分析。

OkHttpClient 和 OkHttpClient.Builder

OkHttpClient 对象的创建使用了『建造者模式』

    public Builder() {
        dispatcher = new Dispatcher();
        protocols = DEFAULT_PROTOCOLS;
        connectionSpecs = DEFAULT_CONNECTION_SPECS;
        eventListenerFactory = EventListener.factory(EventListener.NONE);
        proxySelector = ProxySelector.getDefault();
        cookieJar = CookieJar.NO_COOKIES;
        socketFactory = SocketFactory.getDefault();
        hostnameVerifier = OkHostnameVerifier.INSTANCE;
        certificatePinner = CertificatePinner.DEFAULT;
        proxyAuthenticator = Authenticator.NONE;
        authenticator = Authenticator.NONE;
        connectionPool = new ConnectionPool();
        dns = Dns.SYSTEM;
        followSslRedirects = true;
        followRedirects = true;
        retryOnConnectionFailure = true;
        connectTimeout = 10_000;
        readTimeout = 10_000;
        writeTimeout = 10_000;
        pingInterval = 0;
    }

OkHttpClient.Builder 主要用来设置超时时间、代理、缓存、拦截器等。

然后调用

    public OkHttpClient build() {
        return new OkHttpClient(this);
    }

创建 OkHttpClient

 OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    ……
}
Request 和 Request.Builder

Request 同样也是使用『建造者模式』

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
}

Request 主要为了设置 url 、请求方法(GET、POST等)、headers、请求体。

其中有个 tag 比较特殊

    /**
     * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag
     * is unspecified or null, the request is canceled by using the request itself as the tag.
     */
    public Builder tag(Object tag) {
        this.tag = tag;
        return this;
    }

根据注释可以看出 tag 主要用来取消请求。

如果发起 POST 请求,需要使用一个 RequestBody

okhttp_01.png

RequestBody 主要用来设置不同的 POST 请求内容(字节流、文件、字符串)

分析 Call 对象
client.newCall(request)

OkHttpClient 的 newCall

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

发现请求代理给了 RealCall

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

看下 RealCall 的构造函数

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
获得 Call.execute() 和 Call.enqueue(Callback responseCallback)

Call.execute() 负责同步请求。

Call.enqueue(Callback responseCallback) 负责异步请求。

@Override
public Response execute() throws IOException {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    try {
        client.dispatcher().executed(this);
        Response result = getResponseWithInterceptorChain();
        if (result == null) throw new IOException("Canceled");
        return result;
    } finally {
        client.dispatcher().finished(this);
    }
}

@Override
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

从上面代码可以看出 Call 对象会交给 Dispatcher 对象进行管理。

executed() 方法会把 Call 对象存放在 runningSyncCalls 队列

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

enqueue() 方法会把 Call 对象存放在 runningAsyncCalls 队列,如果队列已满则会被存放在 readyAsyncCalls 队列

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

然后会执行到 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);
}

这里可以看到添加了一些列的 Interceptor 对象。这些 intercept 分别负责网络请求、缓存、压缩等功能。

而这些 intercept 组合的方式就是『责任链模式』,而最后一个 CallServerInterceptor 会真正发起网络请求。

1. 首先会创建一个 RealInterceptorChain ,传入所有的 Interceptor,index = 0
2. 然后执行 RealInterceptorChain.proceed(Request request)
3. 再调用 RealInterceptorChain.proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
   RealConnection connection)
4. 创建一个新的 RealInterceptorChain ,传入 index 值 +1,对象名称为 next
5. 获取 interceptors 中 index 位置的 Interceptor,调用 Interceptor.intercept(next)
6. 在 interceptors 中添加的各种 Interceptor 的 intercept 中都会如下
    public Response intercept(Chain chain) throws IOException {
        ……
        chain.proceed(requestBuilder.build());
        // or
        realChain.proceed(request, streamAllocation, null, null);
        ……
    }
7. 其中 Chain.proceed() 方法又会重复执行 3、4、5、6 步骤,直到所有的 interceptors 被遍历。
8. 最后添加的 ConnectInterceptor 和 CallServerInterceptor 是发起网络请求的关键

总结以上流程如下

okhttp_02.png

发起网络请求

先看 ConnectInterceptor

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

其中 StreamAllocation 对象由 RetryAndFollowUpInterceptor 创建并传入到『责任链』中。

public Response intercept(Chain chain) throws IOException {
    ……
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
            call, eventListener, callStackTrace);

    ……
            response = realChain.proceed(request, streamAllocation, null, null);
    ……
    }
}

可以看出 ConnectInterceptor 主要作用就是通过 StreamAllocation 创建了一个 HttpCodec。

public HttpCodec newStream(
        OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
        RealConnection existingConnection = connection;

        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

        if (existingConnection != connection) {
            eventListener.connectionAcquired(call, connection);
        }

        synchronized (connectionPool) {
            codec = resultCodec;
            return resultCodec;
        }
    } catch (IOException e) {
        throw new RouteException(e);
    }
}

通过上面代码我们可以看出虽然返回的只是一个 HttpCodec 但是还会创建一个 RealConnection 。而 RealConnection 则是负责连接服务器发送请求的类。

findHealthyConnection() 方法会调用 findConnection() 方法

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                      boolean connectionRetryEnabled) throws IOException {
    ……
    RealConnection result;
    ……

    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(
            connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    ……
}

并且调用 RealConnection 的 connect() 方法进行连接

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
                    boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    ……
                connectSocket(connectTimeout, readTimeout, call, eventListener);
    ……
}

private void connectSocket(int connectTimeout, int readTimeout, Call call,
                           EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()
            : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
        Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    }
    ……
    
    try {
        source = Okio.buffer(Okio.source(rawSocket));
        sink = Okio.buffer(Okio.sink(rawSocket));
    } ……
}

这里可以看出 connect() 方法会简历一个 Socket 连接,并把 Socket 的输入/输出流交包装成 Okio 的 Source 和 Sink 对象。

然后到 CallServerInterceptor 中

public Response intercept(Chain chain) throws IOException {
    ……
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        ……
        //写入 request body
        ……
    }

    httpCodec.finishRequest(); // 通过 Socket OutputStream 发送请求

    ……
    if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(false);
    }
    Response response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();

    ……
    return response;
}

总计流程图如下

okhttp_03.png
okhttp_04.png

参考资料

OkHttp

拆轮子系列:拆 OkHttp

Okio 框架源码学习

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

推荐阅读更多精彩内容