Mark Xu 的博客

记录精彩的程序人生

OkHttp3 源码分析(一)

本篇文章对 OkHttp3 的源码进行分析,学习其设计思想。

OkHttpClient

首先从 OkHttpClient 的构建说起。由官方文档可知,在绝大多数 App 中,我们都应该只拥有一个 OkHttpClient 对象,这样所有的 Http 请求可以共用 Response 缓存、共用线程池以及共用连接池,达到节省延迟和内存的目的。

关于 OkHttpClient 我们暂时只关注两点:Builder 模式和 Call 接口。

Builder 模式

OkHttpClient 使用了建造者模式,我们可以使用 new OkHttpClient() 来构建默认配置的 OkHttpClient 对象,也可以使用 OkHttpClient.Builder 来构建自定义配置的客户端。在此处提及一下建造者模式的优点:

  • 客户端在初始化时只需初始化必须参数即可,其他参数可选
  • 和 Java bean 类型的初始化相比,可以保证初始化过程中对象的线程安全
  • 代码易编易读

所以当类的构造器有许多参数时,可以考虑使用 Builder 模式。

Call 接口

一个标准的 OkHttp 请求格式为 mClient.newCall(request).enqueue(callback); 那么我们先来从这里入手开始分析。

OkHttpClient 实现了 Call.Factory 接口:

public interface Call extends Cloneable {
    ...
    interface Factory {
	     Call newCall(Request request);
    }
}

具体实现如下:

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

在看 RealCall 之前先来看看 Call 接口,因为 RealCall 实现了 Call 接口,Call 是整个网络请求过程中的功能核心类,负责请求过程的执行、取消。其接口定义如下:

public interface Call extends Cloneable {
	// 返回原始请求
	Request request();
	// 同步执行请求
	Response execute() throws IOException;
	// 异步执行请求
	void enqueue(Callback responseCallback);
	// 退出未完成的请求
	void cancel();
	boolean isExecuted();
	boolean isCanceled();
	Call clone();
	// 工厂类
	interface Factory {
   	Call newCall(Request request);
   }
}

注释已经描述的很清晰了,接下来看看它的实现类 RealCall,先来看看刚才的 newRealCall 方法,newRealCall 是一个静态方法,根据 OkHttpClient 对象、Request 对象来创建最终执行任务的 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 的构造器返回了 RealCall 对象,后面我们执行的请求、取消等方法都是操作的这个 RealCall 对象。现在在看 OkHttp 的请求方法就很清晰了:

  • 首先使用建造者模式创建 OkHttpClient 对象
  • 由于 OkHttpClient 实现了 Call.Factory 接口(newCall 方法),调用 OkHttpClient 对象的 newCall 方法返回一个 RealCall 对象
  • 然后就可以操作 RealCall 对象执行请求、取消等一系列操作。

Get 请求

在 get 请求这一部分我们主要关注 Request 类、拦截器链条以及所应用到的责任链模式。

Request 类

一个标准的 OkHttp get 请求示例如下:

public void get(String url, Callback callback) {
    Request request = new Request.Builder()
        .url(url)
        .build();
    mClient.newCall(request).enqueue(callback);
}

可以看到在创建 RealCall 对象时会传递一个 Request 对象给其进行初始化,Request 顾名思义代表一个 http 请求,来看看 Request 的代码:

public final class Request {
	final HttpUrl url;
	final String method;
	final Headers headers;
	final @Nullable RequestBody body;
	final Object tag;
	// getters and setters...
	public static class Builder {
	...
	}
}

Request 的构建也使用了建造者模式,它的成员变量包括请求地址(url)、请求方法(method)、请求头(headers)、请求体(body)以及用于取消请求的 tag。

请求执行过程

execute 同步请求

在上面 OkHttpClient 的 newCall 实现方法中,OkHttpClient 将其自身以及 Request 对象都传递给了创建出来的 RealCall 对象。接着调用 execute 方法或 enqueue 方法执行请求,先来看看 RealCall 类 execute 方法的源码:

@Override 
public Response execute() throws IOException {
    synchronized (this) {
        // 判断是否已执行过
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    // 回调事件监听器的开始执行方法
    eventListener.callStart(this);
    try {
        // 将当前 Call 加入 Dispatcher 的同步执行任务队列中
        client.dispatcher().executed(this);
        // 在当前线程中通过拦截器链条获取返回数据 Response
        Response result = getResponseWithInterceptorChain();
        if (result == null) throw new IOException("Canceled");
        // 返回请求结果
        return result;
    } catch (IOException e) {
        // 请求异常
        eventListener.callFailed(this, e);
        throw e;
    } finally {
        // 将 Call 从 Dispatcher 的同步执行队列移除
        client.dispatcher().finished(this);
    }
}

先根据 executed 变量判断这个 RealCall 是否已经执行过,执行过则抛出异常。接着回调当前 Call 的事件监听器告知请求开始执行,同时 Dispatcher 会在同步执行任务队列中记录当前 Call。然后就是在当前线程直接获取返回数据了,最后会将 Call 从 Dispatcher 的同步执行队列中移除。

getResponseWithInterceptorChain 方法

接下来重点看看在 getResponseWithInterceptorChain 方法中拦截器链条是如何工作的。

Response getResponseWithInterceptorChain() throws IOException {
	 // 添加所有需要的拦截器
    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);
}

可以看到,首先创建了一个拦截器的集合并添加了一些拦截器。接着根据这些拦截器和其他一些参数创建了 Chain 对象,调用了 Chain 对象的 proceed 方法拿到请求结果。这里拦截器链条是典型的责任链模式。接下来下面我们一点点分析来看:

拦截器链条

通过阅读官方 Wiki,先来看看拦截器链条的整体工作流程:

可以看到,一个完整的拦截器链条(Interceptor Chain)包括:应用拦截器、OkHttp 核心、网络拦截器以及最终的网络请求(CallServerInterceptor)。

拦截器(Interceptor)

先来看看拦截器是什么?来自 Interceptor 源码注释

观察,修改并优化性地缩短发出的请求,并返回相应的响应。拦截器通常会在请求或响应中添加,删除或转换头部。

Interceptor 接口源码如下:

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;

    interface Chain {
        ...
    }
}

仅有一个方法 intercept,接收一个 Chain 参数,并返回相应的响应结果。

OkHttp 默认提供了一些拦截器的实现,比如 CacheInterceptor 来实现缓存,我们也可以自己添加自定义的拦截器来实现特定功能,比如扇贝网开源的这个 Mock 库 mock-api 就是通过拦截器来实现的,大家可以看看其源码实现。

链条(Chain)

接下来看看 Chain:

interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    @Nullable Connection connection();
    Call call();
    int connectTimeoutMillis();
    Chain withConnectTimeout(int timeout, TimeUnit unit);
    int readTimeoutMillis();
    Chain withReadTimeout(int timeout, TimeUnit unit);
    int writeTimeoutMillis();
    Chain withWriteTimeout(int timeout, TimeUnit unit);
}

Chain 的实现类可以获取到当前 Reauest、Call、超时时间等等,以便传递给责任链中下一个处理者使用。

流程分析

1、由 getResponseWithInterceptorChain 方法源码最后一句 return chain.proceed(originalRequest);看到,请求最终的 Response 是调用 RealInterceptorChain 对象的 proceed 方法得到的。RealInterceptorChain 是一个具体的拦截器链条实现,包括了所有的应用拦截器、OkHttp 核心、网络拦截器以及最终网络请求的全部实现。

2、来看看 RealInterceptorChain 的 proceed 方法,其中比较关键的代码为下面这一段:

// 此处省略其他代码...
// Call the next interceptor in the chain.
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);
// 此处省略其他代码...

在这段代码里创建了一个新的 RealInterceptorChain 对象,index 加 1。并取到 index 为 0 的拦截器,也就是我们手动添加的拦截器里的第一个,没添加过就默认为 RetryAndFollowUpInterceptor。执行了第一个拦截器的 intercept 方法,并将刚刚创建的拦截器链条传入。这样子就完成了第一个拦截器的执行工作。

3、假设第一个拦截器为 RetryAndFollowUpInterceptor,我们来看看它的 intercept 方法拿到拦截器链条究竟做了些什么?来看关键代码:

@Override 
public Response intercept(Chain chain) throws IOException {
	Request request = chain.request();
	// 向下转型为 RealInterceptorChain 对象
	RealInterceptorChain realChain = (RealInterceptorChain) chain;
	// ...
	while (true) {
		// 执行 RealInterceptorChain 对象的 proceed 方法
		try {
			response = realChain.proceed(request, streamAllocation, null, null);
			releaseConnection = false;
		} catch (IOException e) {
			// ...
		}
	}
	// ...
}

可以看到,RetryAndFollowUpInterceptor 在它的 intercept 方法中调用了刚刚传递过来的 Chain 的 proceed 方法。由于刚才 index 已经加 1,所以这时就会调用整个拦截器链条里的第二个拦截器,就像剥洋葱一样,依此循环。直到最终调用到 CallServerInterceptor 完成网络请求。

从整个流程中也可以看出,拦截器链条是非常经典的责任链模式。Android 中另一个责任链模式的经典应用就是事件分发机制,详情可以看这两篇博文:Android 事件分发机制源码分析(一)触摸事件Android 事件分发机制源码分析(二)按键事件

enqueue 异步请求

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        // 判断是否已经执行过
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    // 回调事件监听器
    eventListener.callStart(this);
    // 将当前 Call 加入 Dispatcher 的异步执行任务队列中
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

直接将 Call 加入到了 Dispatcher 的异步执行队列中。

先来看看 AsyncCall 的代码:

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
        super("OkHttp %s", redactedUrl());
        this.responseCallback = responseCallback;
    }

    ...

    @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 {
            // 将当前 Call 从 Dispatcher 的异步执行队列中移除
            client.dispatcher().finished(this);
        }
    }
}

可以看到,在 exeucte 方法中,依旧是调用 getResponseWithInterceptorChain 方法获取到请求 Response,并回调 Callback 的相应方法。

其中 AsyncCall 继承自 NamedRunnable,NamedRunnable 实现如下:

public abstract class NamedRunnable implements Runnable {
    protected final String name;

    public NamedRunnable(String format, Object... args) {
        this.name = Util.format(format, args);
    }

    @Override 
    public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
            execute();
        } finally {
            Thread.currentThread().setName(oldName);
        }
    }

    protected abstract void execute();
}

NamedRunnable 的 run 方法会调用 execute 方法,所以 AsyncCall 被执行时,execute 方法就会调用。那么 AsyncCall 何时会被执行呢?接着看传入 AsyncCall 后的操作。

Dispatcher 的 enqueue 方法如下:

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

其中 maxRequests 的值为 64,maxRequestsPerHost 的值为 5。可以看到,先判断当前异步执行队列 Call 数量是否达到最大请求数量 64,以及当前主机正在处理的请求数 是否大于 5,没有达到最大数量时,直接将当前 Call 加入线程池队列进行执行,否则先添加到另一个队列进行等待。

Post 请求

关于 post 请求,我们重点关注 RequestBody。

一个标准的 post String 请求如下所示:

private static final MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");

public void postString() {
    Request request = new Request.Builder()
        .url(url)
        .post(RequestBody.create(MEDIA_TYPE_TEXT, "test_string"))
        .build();
    mClient.newCall(request).enqueue(callback);
}

RequestBody

在上面的 post 请求示例中可以看到,我们在构建 Request 对象时,有一个 post(RequestBody) 方法,会在这个方法中为 Request 的请求体赋值。

RequestBody 是一个抽象类,源码组成很简单,由两个抽象方法、一个普通方法和一些静态工厂方法组成:

public abstract class RequestBody {
    // 返回媒体类型
    public abstract @Nullable MediaType contentType();
    // 返回向 sink 写入数据的长度,未知则为 -1
    public long contentLength() throws IOException {
        return -1;
  	 }
    // 把当前请求的内容写入 sink
    public abstract void writeTo(BufferedSink sink) throws IOException;
    ...
    // 一些静态工厂方法
}

在看请求体的使用流程前,我们先来看看 RequestBody 的几个静态工厂方法,这几个方法也同时对应着我们 post 请求的类型。

OkHttp 的底层实现基于 Okio 库,关于 Okio 库可以看这篇文章进行简单了解。Android IO 简化之 Okio 库

RequestBody 的创建

RequsetBody 的 create 方法重载如下:

// 通过 byte 数组
public static RequestBody create(final @Nullable MediaType contentType, final byte[] content);
// 通过 byte 数组
public static RequestBody create(final @Nullable MediaType contentType, final byte[] content, final int offset, final int byteCount);
// 通过 String
public static RequestBody create(@Nullable MediaType contentType, String content);
// 通过 ByteString
public static RequestBody create(final @Nullable MediaType contentType, final ByteString content);
// 通过文件
public static RequestBody create(final @Nullable MediaType contentType, final File file);

其中第一个方法调用了第二个方法,所以我们只看下面四种方法即可。

通过 byte 数组

public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,final int offset, final int byteCount) {
    if (content == null) throw new NullPointerException("content == null");
    Util.checkOffsetAndCount(content.length, offset, byteCount);
    return new RequestBody() {
        @Override public @Nullable MediaType contentType() {
            return contentType;
        }

        @Override public long contentLength() {
            return byteCount;
        }

        @Override public void writeTo(BufferedSink sink) throws IOException {
            sink.write(content, offset, byteCount);
        }
    };
}

代码很简单,首先判断是非空,接着检查数组边界,最后返回一个 RequestBody 的匿名内部类对象。

通过 String

public static RequestBody create(@Nullable MediaType contentType, String content) {
    Charset charset = Util.UTF_8;
    if (contentType != null) {
        charset = contentType.charset();
        if (charset == null) {
            charset = Util.UTF_8;
            contentType = MediaType.parse(contentType + "; charset=utf-8");
        }
    }
    byte[] bytes = content.getBytes(charset);
    return create(contentType, bytes);
}

如果未指定字符编码格式,则默认为 UTF_8 格式。接着根据字符编码格式将 String 内容转换为 byte 数组,并调用方法一进行 RequestBody 对象的创建。

通过 ByteString

public static RequestBody create(final @Nullable MediaType contentType, final ByteString content) {
    return new RequestBody() {
            @Override public @Nullable MediaType contentType() {
            return contentType;
        }

        @Override public long contentLength() throws IOException {
            return content.size();
        }

        @Override public void writeTo(BufferedSink sink) throws IOException {
            sink.write(content);
        }
    };
}

对于 ByteString 类型,Sink 可以直接写入。

通过文件

public static RequestBody create(final @Nullable MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
        @Override public @Nullable MediaType contentType() {
            return contentType;
        }

        @Override public long contentLength() {
            return file.length();
        }

        @Override public void writeTo(BufferedSink sink) throws IOException {
            Source source = null;
            try {
                source = Okio.source(file);
                sink.writeAll(source);
            } finally {
                Util.closeQuietly(source);
            }
        }
    };
}

File 也可以直接转换为输出流,直接写入 sink。

综上可以看到,我们可以通过二进制、字符串、文件以及 ByteString 来创建 RequestBody 对象,这些也同时是我们 post 请求可以提交的类型。

RequestBody 还有两个子类 FormBody 和 MultipartBody,我们来看看。

FormBody

FormBody 通常用来在请求体中像提交表单一样提交数据,示例如下:

public void postForm() {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "tom")
        .build();
    Request request = new Request.Builder()
        .url(url)
        .post(formBody)
        .build();
    mClient.newCall(request).enqueue(callback);
}

FormBody 依旧采用了 Builder 模式来构建,这样可以很方便的添加表单中的键值对数据。

MultipartBody

当我们要提交键值对数据时,可以使用 FormBody 来构建。但是假如我们需要一次提交多条信息,比如一张图片和一条评论,此时 FormBody 就无法满足需求了,需要使用到 RequestBody 的另一个子类 MultipartBody。

MultipartBody 的每一块都是一个请求体,通过 Content-Type 的 boundary 分隔符进行分割。

一个请求示例如下:

public void postMultipartBody() {
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("name", "Tom")
        .addFormDataPart("avatar", "avatar.png", RequestBody.create(
                MEDIA_TYPE_PNG, new File("web/avatar.png")))
        .build();

    Request request = new Request.Builder()
        .url(url)
        .post(requestBody)
        .build();
    mClient.newCall(request).enqueue(callback);
}

由于 MultipartBody 的 MediaType 类型默认为 Multipart.MIX,我们需要手动设置为 MultipartBody.FORM 类型。

MultipartBody 源码如下:

public final class MultipartBody extends RequestBody {
    ...
    private final ByteString boundary;
    private final MediaType originalType;
    private final MediaType contentType;
    private final List<Part> parts;

    MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {...}
    ...
    @Override 
    public void writeTo(BufferedSink sink) throws IOException {
        writeOrCountBytes(sink, false);
    }

    private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
       ...

        // 循环写入每一个请求体
        for (int p = 0, partCount = parts.size(); p < partCount; p++) {
            Part part = parts.get(p);
            Headers headers = part.headers;
            RequestBody body = part.body;

            sink.write(DASHDASH);
            sink.write(boundary);
            sink.write(CRLF);

            // 写入请求头
            if (headers != null) {
                for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
                    sink.writeUtf8(headers.name(h))
                        .write(COLONSPACE)
                        .writeUtf8(headers.value(h))
                        .write(CRLF);
                }
            }

            // 写入 Content-Type
            MediaType contentType = body.contentType();
            if (contentType != null) {
                sink.writeUtf8("Content-Type: ")
                    .writeUtf8(contentType.toString())
                    .write(CRLF);
            }

            // 写入 Content-Length
            long contentLength = body.contentLength();
            if (contentLength != -1) {
                sink.writeUtf8("Content-Length: ")
                    .writeDecimalLong(contentLength)
                    .write(CRLF);
            } else if (countBytes) {
                // We can't measure the body's size without the sizes of its components.
                byteCountBuffer.clear();
                return -1L;
            }

            sink.write(CRLF);

            if (countBytes) {
                byteCount += contentLength;
            } else {
                // 写入请求体
                body.writeTo(sink);
            }

            sink.write(CRLF);
        }

        sink.write(DASHDASH);
        sink.write(boundary);
        sink.write(DASHDASH);
        sink.write(CRLF);

        ...

        return byteCount;
    }
}

public static final class Builder {...}

public static final class Part {...}

在调用 writeTo 方法时,会循环写入每一个请求体的内容。

MultipartBody 依旧采用了 Builder 模式,并对每个请求体进行了抽象为 Part 类。MultipartBody 的 builder 提供了以下几种方法添加 Part 对象:

// 方法一
public Builder addPart(RequestBody body);
// 方法二
public Builder addPart(Headers headers, RequestBody body);
// 方法三
public Builder addFormDataPart(String name, String value);
// 方法四
public Builder addFormDataPart(String name, String filename, RequestBody body);
// 方法五
public Builder addPart(Part part);

前面四种方法都会根据传入的参数构建出 Part 对象后,调用方法五添加到请求体集合中。我们调用时常用方法三和方法四。

先来看看方法三和方法四的源码如下:

public Builder addFormDataPart(String name, String value) {
    return addPart(Part.createFormData(name, value));
}

public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) {
    return addPart(Part.createFormData(name, filename, body));
}

都是分别调用了 Part.createFormData 方法创建 Part 对象,然后添加到 Multipart 所维护的 Part 集合列表。那么上面两种创建 Part 方法的重载有什么不同呢?

public static Part createFormData(String name, String value) {
    return createFormData(name, null, RequestBody.create(null, value));
}

public static Part createFormData(String name, @Nullable String filename, RequestBody body) {
    if (name == null) {
        throw new NullPointerException("name == null");
    }
    // 拼接请求头
    StringBuilder disposition = new StringBuilder("form-data; name=");
    appendQuotedString(disposition, name);

    // 拼接请求头
    if (filename != null) {
        disposition.append("; filename=");
        appendQuotedString(disposition, filename);
    }

    // 由请求头和请求体创建 Part 对象
    return create(Headers.of("Content-Disposition", disposition.toString()), body);
}

可以看到,第一个方法调用了第二个方法,文件名称传值 null,创建 RequestBody 时媒体类型传值 null,仅仅由 value 参数创建出 RequestBody。

而第二个方法则主要用于上传文件。

未完待续!

参考文档

OkHttp 之 MultipartBody 上传文件

OKHttp 源码解析

留下你的脚步