目录
RealCall.getResponseWithInterceptorChain
CallServerInterceptor.intercept
介绍
OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。
适用于 Android 和 Java 应用程序的 HTTP 和 HTTP/2 客户端。
OkHttp的4.0.x版本已经全部由java替换到了Kotlin,API的一些使用也会有些不同。
要求
支持的版本
4.0.x :Android 5.0+(API 级别 21+)和 Java 8+。
3.12.x :Android 2.3+(API 级别 9+)和 Java 7+。平台可能不支持 TLSv1.2。(2021-12-31不再支持)
OkHttp有一个库的依赖Okio,用于高性能I/O一个小型library。它适用于 Okio 1.x(用 Java 实现)或 Okio 2.x(升级到 Kotlin)。
本文使用的OkHttp的版本为3.14.2,不是不会接入高版本,主要是4.0.x版本已经全部由java替换到了Kotlin,Kotlin不太熟怕理解错了,误导人民群众。
- dependencies {
- //本文使用
- implementation 'com.squareup.okio:okio:1.15.0'
- implementation 'com.squareup.okhttp3:okhttp:3.14.2'
-
- //高版本
- // define a BOM and its version
- implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
-
- // define any required OkHttp artifacts without version
- implementation("com.squareup.okhttp3:okhttp")
- implementation("com.squareup.okhttp3:logging-interceptor")
- }
-
-
网络请求流程分析
OkHttp 经过几次迭代后,已经发生了很多变化。更好的 WebSocket 支持、更多的 Interceptor 责任链,甚至连最核心的 HttpEngine 也变成了 HttpCodec。本文会重新梳理整个网络请求的流程,以及实现机制。
先看下 OkHttp 的基本使用:
- public void okHttp(String url){
- //创建OkHttpClient对象
- OkHttpClient client = new OkHttpClient();
- //创建Request
- Request request = new Request.Builder()
- .url(url)
- .build();
- //创建Call对象client.newCall(request)
- //通过execute()方法获得请求响应的Response对象
- client.newCall(request).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {}
-
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- if(response.isSuccessful()){
- String result = response.body().string();
- //处理UI需要切换到UI线程处理
- }
- }
- });
- }
除了直接 new OkHttpClient 之外,还可以使用内部工厂类 Builder 来设置 OkHttpClient。如下所示:
- public void buildHttp(String url){
- OkHttpClient.Builder builder = new OkHttpClient.Builder();
- builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
- .addInterceptor(interceptor) //拦截器
- .proxy(proxy) //设置代理
- .cache(cache); //设置缓存
- Request request = new Request.Builder()
- .url(url)
- .build();
- builder.build().newCall(request).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {}
-
- @Override
- public void onResponse(Call call, Response response) throws IOException {}
- });
- }
请求操作的起点从 OkHttpClient.newCall().enqueue() 方法开始
OkHttpClient.newCall
- @Override public Call newCall(Request request) {
- return RealCall.newRealCall(this, request, false /* for web socket */);
- }
RealCall.newRealCall.java
-
- 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.transmitter = new Transmitter(client, call);
- return call;
- }
这个方法会返回一个 RealCall 对象,通过它将网络请求操作添加到请求队列中。
RealCall.enqueue
- @Override public void enqueue(Callback responseCallback) {
- synchronized (this) {
- if (executed) throw new IllegalStateException("Already Executed");
- executed = true;
- }
- transmitter.callStart();
- client.dispatcher().enqueue(new AsyncCall(responseCallback));
- }
client.dispatcher()返回Dispatcher,调用 Dispatcher 的 enqueue 方法,执行一个异步网络请求的操作。
Dispatcher 是 OkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。
Dispatcher.enqueue
- void enqueue(AsyncCall call) {
- synchronized (this) {
- readyAsyncCalls.add(call);
- if (!call.get().forWebSocket) {
- AsyncCall existingCall = findExistingCallWithHost(call.host());
- if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
- }
- }
- promoteAndExecute();
- }
实际上就是使用线程池执行了一个 AsyncCall,而 AsyncCall 继承了 NamedRunnable,NamedRunnable 实现了 Runnable 接口,因此整个操作会在一个子线程(非 UI 线程)中执行。
NamedRunnable
- /**
- * Runnable implementation which always sets its thread name.
- */
- 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();
- }
在 run 方法中执行了 一个抽象方法 execute 这个抽象方法被 AsyncCall 实现。
AsyncCall.execute
- @Override protected void execute() {
- boolean signalledCallback = false;
- transmitter.timeoutEnter();
- try {
- Response response = getResponseWithInterceptorChain();
- 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 {
- responseCallback.onFailure(RealCall.this, e);
- }
- } finally {
- client.dispatcher().finished(this);
- }
- }
从上面看出而真正获取请求结果的方法是在 getResponseWithInterceptorChain 方法中,从名字也能看出其内部是一个拦截器的调用链。
RealCall.getResponseWithInterceptorChain
- Response getResponseWithInterceptorChain() throws IOException {
- // Build a full stack of interceptors.
- List
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());
-
- boolean calledNoMoreExchanges = false;
- try {
- Response response = chain.proceed(originalRequest);
- if (transmitter.isCanceled()) {
- closeQuietly(response);
- throw new IOException("Canceled");
- }
- return response;
- } catch (IOException e) {
- calledNoMoreExchanges = true;
- throw transmitter.noMoreExchanges(e);
- } finally {
- if (!calledNoMoreExchanges) {
- transmitter.noMoreExchanges(null);
- }
- }
- }
Interceptor:拦截器是一种强大的机制,可以监视、重写和重试调用。
每一个拦截器的作用如下:
-
BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。
-
CacheInterceptor:负责 HTTP 请求的缓存处理。
-
ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接。
-
CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。
-
RetryAndFollowUpInterceptor:此拦截器从故障中恢复,并根据需要执行重定向。如果呼叫被取消,它可能会引发IOException。
在添加上述几个拦截器之前,会调用 client.interceptors 将开发人员设置的拦截器添加到列表当中。
对于 Request 的 Head 以及 TCP 链接,我们能控制修改的成分不是很多。所以咱们了解 CacheInterceptor 和 CallServerInterceptor。
CacheInterceptor 缓存拦截器
CacheInterceptor 主要做以下几件事情:
1、根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response,创建 CacheStrategy 对象。
2、 通过 CacheStrategy 判断当前缓存中的 Response 是否有效(比如是否过期),如果缓存 Response 可用则直接返回,否则调用 chain.proceed() 继续执行下一个拦截器,也就是发送网络请求从服务器获取远端 Response。
3、如果从服务器端成功获取 Response,再判断是否将此 Response 进行缓存操作。
CacheInterceptor.intercept
- @Override public Response intercept(Chain chain) throws IOException {
- //根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response
- Response cacheCandidate = cache != null
- ? cache.get(chain.request())
- : null;
- //获取当前时间
- long now = System.currentTimeMillis();
- //创建 CacheStrategy 对象
- //通过 CacheStrategy 来判断缓存是否有效
- CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
- Request networkRequest = strategy.networkRequest;
- Response cacheResponse = strategy.cacheResponse;
- if (cache != null) {
- cache.trackResponse(strategy);
- }
- if (cacheCandidate != null && cacheResponse == null) {
- closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
- }
- //如果我们被禁止使用网络,并且缓存不足,则失败。返回空相应(Util.EMPTY_RESPONSE)
- if (networkRequest == null && cacheResponse == null) {
- return new Response.Builder()
- .request(chain.request())
- .protocol(Protocol.HTTP_1_1)
- .code(504)
- .message("Unsatisfiable Request (only-if-cached)")
- .body(Util.EMPTY_RESPONSE)
- .sentRequestAtMillis(-1L)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build();
- }
-
- // 如果缓存有效,缓存 Response 可用则直接返回
- if (networkRequest == null) {
- return cacheResponse.newBuilder()
- .cacheResponse(stripBody(cacheResponse))
- .build();
- }
- //没有缓存或者缓存失败,则发送网络请求从服务器获取Response
- Response networkResponse = null;
- try {
- //执行下一个拦截器,networkRequest
- //发起网络请求
- networkResponse = chain.proceed(networkRequest);
- } finally {
- //如果我们在I/O或其他方面崩溃,请不要泄漏cache body。
- if (networkResponse == null && cacheCandidate != null) {
- closeQuietly(cacheCandidate.body());
- }
- }
- 。。。
- //通过网络获取最新的Response
- Response response = networkResponse.newBuilder()
- .cacheResponse(stripBody(cacheResponse))
- .networkResponse(stripBody(networkResponse))
- .build();
- //如果开发人员有设置自定义cache,则将最新response缓存
- if (cache != null) {
- if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
- // Offer this request to the cache.
- CacheRequest cacheRequest = cache.put(response);
- return cacheWritingResponse(cacheRequest, response);
- }
- //返回response(缓存或网络)
- return response;
- }
通过 Cache 实现缓存功能
通过上面缓存拦截器的流程可以看出,OkHttp 只是规范了一套缓存策略,但是具体使用何种方式将数据缓存到本地,以及如何从本地缓存中取出数据,都是由开发人员自己定义并实现,并通过 OkHttpClient.Builder 的 cache 方法设置。
OkHttp 提供了一个默认的缓存类 Cache.java,我们可以在构建 OkHttpClient 时,直接使用 Cache 来实现缓存功能。只需要指定缓存的路径,以及最大可用空间即可,如下所示:
- OkHttpClient.Builder builder = new OkHttpClient.Builder();
- builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
- 拦截器
- .addInterceptor(new Interceptor() {
- @Override
- public Response intercept(Chain chain) throws IOException {
- return null;
- }
- })
- //设置代理
- .proxy(new Proxy(Proxy.Type.HTTP,null))
- //设置缓存
- //AppGlobalUtils.getApplication() 通过反射得到Application实例
- //getCacheDir内置 cache 目录作为缓存路径
- //maxSize 10*1024*1024 设置最大缓存10MB
- .cache(new Cache(AppGlobalUtils.getApplication().getCacheDir(),
- 10*1024*1024));
Cache 内部使用了 DiskLruCach 来实现具体的缓存功能,如下所示:
- /**
- * Create a cache of at most {@code maxSize} bytes in {@code directory}.
- */
- public Cache(File directory, long maxSize) {
- this(directory, maxSize, FileSystem.SYSTEM);
- }
-
- Cache(File directory, long maxSize, FileSystem fileSystem) {
- this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
- }
DiskLruCache 最终会将需要缓存的数据保存在本地。如果感觉 OkHttp 自带的这套缓存策略太过复杂,我们可以设置使用 DiskLruCache 自己实现缓存机制。
LRU:是近期最少使用的算法(缓存淘汰算法),它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。
CallServerInterceptor 详解
CallServerInterceptor 是 OkHttp 中最后一个拦截器,也是 OkHttp 中最核心的网路请求部分。
CallServerInterceptor.intercept
- @Override public Response intercept(Chain chain) throws IOException {
- //获取RealInterceptorChain
- RealInterceptorChain realChain = (RealInterceptorChain) chain;
- //获取Exchange
- Exchange exchange = realChain.exchange();
- Request request = realChain.request();
-
- long sentRequestMillis = System.currentTimeMillis();
-
- exchange.writeRequestHeaders(request);
-
- boolean responseHeadersStarted = false;
- Response.Builder responseBuilder = null;
- if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
- if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
- exchange.flushRequest();
- responseHeadersStarted = true;
- exchange.responseHeadersStart();
- responseBuilder = exchange.readResponseHeaders(true);
- }
-
- if (responseBuilder == null) {
- if (request.body().isDuplex()) {
- exchange.flushRequest();
- BufferedSink bufferedRequestBody = Okio.buffer(
- exchange.createRequestBody(request, true));
- request.body().writeTo(bufferedRequestBody);
- } else {
- // Write the request body if the "Expect: 100-continue" expectation was met.
- BufferedSink bufferedRequestBody = Okio.buffer(
- exchange.createRequestBody(request, false));
- request.body().writeTo(bufferedRequestBody);
- bufferedRequestBody.close();
- }
- } else {
- exchange.noRequestBody();
- if (!exchange.connection().isMultiplexed()) {
- // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
- // from being reused. Otherwise we're still obligated to transmit the request body to
- // leave the connection in a consistent state.
- exchange.noNewExchangesOnConnection();
- }
- }
- } else {
- exchange.noRequestBody();
- }
-
- if (request.body() == null || !request.body().isDuplex()) {
- exchange.finishRequest();
- }
-
- 上面是向服务器端发送请求数据
-
- -----强大的分割线----------
-
- 下面是从服务端获取相应数据
- 并构建 Response 对象
-
- if (!responseHeadersStarted) {
- exchange.responseHeadersStart();
- }
-
- if (responseBuilder == null) {
- responseBuilder = exchange.readResponseHeaders(false);
- }
-
- Response response = responseBuilder
- .request(request)
- .handshake(exchange.connection().handshake())
- .sentRequestAtMillis(sentRequestMillis)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build();
-
- int code = response.code();
- if (code == 100) {
- // server sent a 100-continue even though we did not request one.
- // try again to read the actual response
- response = exchange.readResponseHeaders(false)
- .request(request)
- .handshake(exchange.connection().handshake())
- .sentRequestAtMillis(sentRequestMillis)
- .receivedResponseAtMillis(System.currentTimeMillis())
- .build();
-
- code = response.code();
- }
-
- exchange.responseHeadersEnd(response);
-
- if (forWebSocket && code == 101) {
- // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
- response = response.newBuilder()
- .body(Util.EMPTY_RESPONSE)
- .build();
- } else {
- response = response.newBuilder()
- .body(exchange.openResponseBody(response))
- .build();
- }
- 。。。
- return response;
- }
小结
首先 OkHttp 内部是一个门户模式,所有的下发工作都是通过一个门户 Dispatcher 来进行分发。
然后在网络请求阶段通过责任链模式,链式的调用各个拦截器的 intercept 方法。重点介绍了 2 个比较重要的拦截器:CacheInterceptor 和 CallServerInterceptor。它们分别用来做请求缓存和执行网络请求操作。
评论记录:
回复评论: