OkHttp
OkHttp 是 Square 的一款应用于 Android 和 Java 的 Http 和 Http/2 客户端。使用的时候只需要在 Gradle 里面加入下面一行依赖即可引入:
implementation 'com.squareup.okhttp3:okhttp:3.11.0'复制代码
下面我们以 Form 类型的请求为例来看下 OkHttp 的 API 设计逻辑:
OkHttpClient internalHttpClient = new OkHttpClient();FormBody.Builder formBodyBuilder = new FormBody.Builder();RequestBody body = formBodyBuilder.build();Request.Builder builder = new Request.Builder().url("host:port/url").post(body);Request request = builder.build();Response response = internalHttpClient.newCall(request).execute();String retJson = response.body().string();复制代码
这里我们先用了 FormBody 的构建者模式创建 Form 类型请求的请求体,然后使用 Request 的构建者创建完整的 Form 请求。之后,我们用创建好的 OkHttp 客户端 internalHttpClient 来获取一个请求,并从请求的请求体中获取 Json 数据。
根据 OkHttp 的 API,如果我们希望发送一个 Multipart 类型的请求的时候就需要使用 MultipartBody 的构建者创建 Multipart 请求的请求体。然后同样使用 Request 的构建者创建完整的 Multipart 请求,剩下的逻辑相同。
除了使用上面的直接实例化一个 OkHttp 客户端的方式,我们也可以使用 OkHttpClient 的构建者 OkHttpClient.Builder 来创建 OkHttp 客户端。
所以,我们可以总结:
- OkHttp 为不同的请求类型都提供了一个构建者方法用来创建请求体 RequestBody;
- 因为请求体只是整个请求的一部分,所以,又要用 Request.Builder 构建一个请求对象 Request;
- 这样我们得到了一个完整的 Http 请求,然后使用 OkHttpClient 对象进行网络访问得到响应对象 Response。
源码分析
一个请求的大致流程
构建完请求对象Request
之后,需要创建出RealCall
对象,并把 OkHttpClient 对象和 Request 对象作为参数传入进去:
@Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */);}复制代码
RealCall 调用内部的静态方法 newRealCall 在其中创建一个 RealCall 实例并将其返回:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call;}复制代码
当返回了 RealCall 之后,我们又会调用它的 execute() 方法来获取响应结果,下面是这个方法的定义:
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); try { // 加入到一个双端队列中 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 { client.dispatcher().finished(this); }}复制代码
这里我们会用 client 对象(实际也就是上面创建 RealCall 的时候传入的 OkHttpClient)的 dispatcher() 方法来获取一个 Dispatcher 对象,并调用它的 executed() 方法来将当前的 RealCall 加入到一个双端队列中,下面是 executed(RealCall) 方法的定义,这里的 runningSyncCalls 的类型是 Deque:
synchronized void executed(RealCall call) { runningSyncCalls.add(call);}复制代码
让我们回到上面的 execute() 方法,在把 RealCall 加入到双端队列之后,我们又调用了 getResponseWithInterceptorChain() 方法,下面就是该方法的定义。
Response getResponseWithInterceptorChain() throws IOException { // 添加一系列拦截器,注意添加的顺序 Listinterceptors = 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);}复制代码
这里,我们创建了一个列表对象之后把 client 中的拦截器、重连拦截器、桥拦截器、缓存拦截器、网络连接拦截器和服务器请求拦截器等依次加入到列表中。然后,我们用这个列表创建了一个拦截器链。这里使用了责任链设计模式,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果。显然,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里。
这里我们遇到了很多的新类,比如 RealCall、Dispatcher 以及责任链等。下文中,我们会对这些类之间的关系以及责任链中的环节做一个分析,而这里我们先对整个请求的流程做一个大致的梳理。下面是这个过程大致的时序图:
分发器 Dispatcher
上面我们提到了 Dispatcher 这个类,它的作用是对请求进行分发。以最开始的示例代码为例,在使用 OkHttp 的时候,我们会创建一个 RealCall 并将其加入到双端队列中。但是请注意这里的双端队列的名称是 runningSyncCalls,也就是说这种请求是同步请求,会在当前的线程中立即被执行。所以,下面的 getResponseWithInterceptorChain() 就是这个同步的执行过程。而当我们执行完毕的时候,又会调用 Dispatcher 的 finished(RealCall) 方法把该请求从队列中移除。所以,这种同步的请求无法体现分发器的“分发”功能。
除了同步的请求,还有异步类型的请求:当我们拿到了 RealCall 的时候,调用它的 enqueue(Callback responseCallback) 方法并设置一个回调即可。该方法会执行下面这行代码:
client.dispatcher().enqueue(new AsyncCall(responseCallback));复制代码
即使用上面的回调创建一个 AsyncCall 并调用 enqueue(AsyncCall)。这里的 AsyncCall 间接继承自 Runnable,是一个可执行的对象,并且会在 Runnable 的 run() 方法里面调用 AsyncCall 的 execute() 方法。AsyncCall 的 execute() 方法与 RealCall 的 execute() 方法类似,都使用责任链来完成一个网络请求。只是后者可以放在一个异步的线程中进行执行。
当我们调用了 Dispatcher 的 enqueue(AsyncCall) 方法的时候也会将 AsyncCall 加入到一个队列中,并会在请求执行完毕的时候从该队列中移除,只是这里的队列是 runningAsyncCalls 或者 readyAsyncCalls。它们都是一个双端队列,并用来存储异步类型的请求。它们的区别是,runningAsyncCalls 是正在执行的队列,当正在执行的队列达到了限制的时候,就会将其放置到就绪队列 readyAsyncCalls 中:
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); }}复制代码
当把该请求加入到了正在执行的队列之后,我们会立即使用一个线程池来执行该 AsyncCall。这样这个请求的责任链就会在一个线程池当中被异步地执行了。这里的线程池由 executorService() 方法返回:
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService;}复制代码
显然,当线程池不存在的时候会去创建一个线程池。除了上面的这种方式,我们还可以在构建 OkHttpClient 的时候,自定义一个 Dispacher,并在其构造方法中为其指定一个线程池。下面我们类比 OkHttp 的同步请求绘制了一个异步请求的时序图。你可以通过将两个图对比来了解两种实现方式的不同:
以上就是分发器 Dispacher 的逻辑,看上去并没有那么复杂。并且从上面的分析中,我们可以看出实际请求的执行过程并不是在这里完成的,这里只能决定在哪个线程当中执行请求并把请求用双端队列缓存下来,而实际的请求执行过程是在责任链中完成的。
责任链的执行过程
在典型的责任链设计模式里,很多对象由每一个对象对其下级的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
有两个地方需要我们注意:
- 是当创建一个责任链 RealInterceptorChain 的时候,我们传入的第 5 个参数是 0。该参数名为 index,会被赋值给 RealInterceptorChain 实例内部的同名全局变量。
- 当启用责任链的时候,会调用它的 proceed(Request) 方法。
下面是 proceed(Request) 方法的定义:
@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 { if (index >= interceptors.size()) throw new AssertionError(); // ... // 调用责任链的下一个拦截器 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;}复制代码
注意到这里使用责任链进行处理的时候,会新建下一个责任链并把 index+1 作为下一个责任链的 index。然后,我们使用 index 从拦截器列表中取出一个拦截器,调用它的 intercept() 方法,并把下一个执行链作为参数传递进去。
这样,当下一个拦截器希望自己的下一级继续处理这个请求的时候,可以调用传入的责任链的 proceed() 方法;如果自己处理完毕之后,下一级不需要继续处理,那么就直接返回一个 Response 实例即可。因为,每次都是在当前的 index 基础上面加 1,所以能在调用 proceed() 的时候准确地从拦截器列表中取出下一个拦截器进行处理。
我们还要注意的地方是之前提到过重试拦截器,这种拦截器会在内部启动一个 while 循环,并在循环体中调用执行链的 proceed() 方法来实现请求的不断重试。这是因为在它那里的拦截器链的 index 是固定的,所以能够每次调用 proceed() 的时候,都能够从自己的下一级执行一遍链条。下面就是这个责任链的执行过程:
重试和重定向:RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 主要用来当请求失败的时候进行重试,以及在需要的情况下进行重定向。我们上面说,责任链会在进行处理的时候调用第一个拦截器的 intercept() 方法。如果我们在创建 OkHttp 客户端的时候没有加入自定义拦截器,那么 RetryAndFollowUpInterceptor 就是我们的责任链中最先被调用的拦截器。
@Override public Response intercept(Chain chain) throws IOException { // ... // 注意这里我们初始化了一个 StreamAllocation 并赋值给全局变量,它的作用我们后面会提到 StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; // 用来记录重定向的次数 int followUpCount = 0; Response priorResponse = null; while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response; boolean releaseConnection = true; try { // 这里从当前的责任链开始执行一遍责任链,是一种重试的逻辑 response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // 调用 recover 方法从失败中进行恢复,如果可以恢复就返回true,否则返回false if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // 重试与服务器进行连接 boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // 如果 releaseConnection 为 true 则表明中间出现了异常,需要释放资源 if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // 使用之前的响应 priorResponse 构建一个响应,这种响应的响应体 body 为空 if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder().body(null).build()) .build(); } // 根据得到的响应进行处理,可能会增加一些认证信息、重定向或者处理超时请求 // 如果该请求无法继续被处理或者出现的错误不需要继续处理,将会返回 null Request followUp = followUpRequest(response, streamAllocation.route()); // 无法重定向,直接返回之前的响应 if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } // 关闭资源 closeQuietly(response.body()); // 达到了重定向的最大次数,就抛出一个异常 if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } // 这里判断新的请求是否能够复用之前的连接,如果无法复用,则创建一个新的连接 if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; }}复制代码
以上的代码主要用来根据错误的信息做一些处理,会根据服务器返回的信息判断这个请求是否可以重定向,或者是否有必要进行重试。如果值得去重试就会新建或者复用之前的连接在下一次循环中进行请求重试,否则就将得到的请求包装之后返回给用户。这里,我们提到了 StreamAllocation 对象,它相当于一个管理类,维护了服务器连接、并发流和请求之间的关系,该类还会初始化一个 Socket 连接对象,获取输入/输出流对象。同时,还要注意这里我们通过 client.connectionPool() 传入了一个连接池对象 ConnectionPool。这里我们只是初始化了这些类,但实际在当前的方法中并没有真正用到这些类,而是把它们传递到下面的拦截器里来从服务器中获取请求的响应。
BridgeInterceptor
桥拦截器 BridgeInterceptor 用于从用户的请求中构建网络请求,然后使用该请求访问网络,最后从网络响应当中构建用户响应。相对来说这个拦截器的逻辑比较简单,只是用来对请求进行包装,并将服务器响应转换成用户友好的响应:
public final class BridgeInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); // 从用户请求中获取网络请求构建者 Request.Builder requestBuilder = userRequest.newBuilder(); // ... // 执行网络请求 Response networkResponse = chain.proceed(requestBuilder.build()); // ... // 从网络响应中获取用户响应构建者 Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest); // ... // 返回用户响应 return responseBuilder.build(); }}复制代码
使用缓存:CacheInterceptor
缓存拦截器会根据请求的信息和缓存的响应的信息来判断是否存在缓存可用,如果有可以使用的缓存,那么就返回该缓存该用户,否则就继续责任链来从服务器中获取响应。当获取到响应的时候,又会把响应缓存到磁盘上面。以下是这部分的逻辑:
public final class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); // 根据请求和缓存的响应中的信息来判断是否存在缓存可用 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()); } // 请求不使用网络并且不使用缓存,相当于在这里就拦截了,没必要交给下一级(网络请求拦截器)来执行 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(); } // 该请求使用缓存,但是不使用网络:从缓存中拿结果,没必要交给下一级(网络请求拦截器)执行 if (networkRequest == null) { return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build(); } Response networkResponse = null; try { // 这里调用了执行链的处理方法,实际就是交给自己的下一级来执行了 networkResponse = chain.proceed(networkRequest); } finally { if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } // 这里当拿到了网络请求之后调用,下一级执行完毕会交给它继续执行,如果使用了缓存就把请求结果更新到缓存里 if (cacheResponse != null) { // 服务器返回的结果是304,返回缓存中的结果 if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); cache.trackConditionalCacheHit(); // 更新缓存 cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); // 把请求的结果放进缓存里 if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }}复制代码
对缓存,这里我们使用的是全局变量 cache,它是 InternalCache 类型的变量。InternalCache 是一个接口,在 OkHttp 中只有一个实现类 Cache。在 Cache 内部,使用了 DiskLruCache 来将缓存的数据存到磁盘上。DiskLruCache 以及 LruCache 是 Android 上常用的两种缓存策略。前者是基于磁盘来进行缓存的,后者是基于内存来进行缓存的,它们的核心思想都是 Least Recently Used,即最近最少使用算法。
连接复用:ConnectInterceptor
连接拦截器 ConnectInterceptor 用来打开到指定服务器的网络连接,并交给下一个拦截器处理。这里我们只打开了一个网络连接,但是并没有发送请求到服务器。从服务器获取数据的逻辑交给下一级的拦截器来执行。虽然,这里并没有真正地从网络中获取数据,而仅仅是打开一个连接,但这里有不少的内容值得我们去关注。因为在获取连接对象的时候,使用了连接池 ConnectionPool 来复用连接。
public final class ConnectInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); }}复制代码
这里的 HttpCodec 用来编码请求并解码响应,RealConnection 用来向服务器发起连接。它们会在下一个拦截器中被用来从服务器中获取响应信息。下一个拦截器的逻辑并不复杂,这里万事具备之后,只要它来从服务器中读取数据即可。可以说,OkHttp 中的核心部分大概就在这里,所以,我们就先好好分析一下,这里在创建连接的时候如何借助连接池来实现连接复用的。
根据上面的代码,当我们调用 streamAllocation 的 newStream() 方法的时候,最终会经过一系列的判断到达 StreamAllocation 中的 findConnection() 方法。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { // ... synchronized (connectionPool) { // ... // 尝试使用已分配的连接,已经分配的连接可能已经被限制创建新的流 releasedConnection = this.connection; // 释放当前连接的资源,如果该连接已经被限制创建新的流,就返回一个Socket以关闭连接 toClose = releaseIfNoNewStreams(); if (this.connection != null) { // 已分配连接,并且该连接可用 result = this.connection; releasedConnection = null; } if (!reportedAcquired) { // 如果该连接从未被标记为获得,不要标记为发布状态,reportedAcquired 通过 acquire() 方法修改 releasedConnection = null; } if (result == null) { // 尝试供连接池中获取一个连接 Internal.instance.get(connectionPool, address, this, null); if (connection != null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; } } } // 关闭连接 closeQuietly(toClose); if (releasedConnection != null) { eventListener.connectionReleased(call, releasedConnection); } if (foundPooledConnection) { eventListener.connectionAcquired(call, result); } if (result != null) { // 如果已经从连接池中获取到了一个连接,就将其返回 return result; } boolean newRouteSelection = false; if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) { newRouteSelection = true; routeSelection = routeSelector.next(); } synchronized (connectionPool) { if (canceled) throw new IOException("Canceled"); if (newRouteSelection) { // 根据一系列的 IP 地址从连接池中获取一个链接 Listroutes = routeSelection.getAll(); for (int i = 0, size = routes.size(); i < size; i++) { Route route = routes.get(i); // 从连接池中获取一个连接 Internal.instance.get(connectionPool, address, this, route); if (connection != null) { foundPooledConnection = true; result = connection; this.route = route; break; } } } if (!foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); } // 创建一个新的连接,并将其分配,这样我们就可以在握手之前进行终端 route = selectedRoute; refusedStreamCount = 0; result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } // 如果我们在第二次的时候发现了一个池连接,那么我们就将其返回 if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; } // 进行 TCP 和 TLS 握手 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; // 将该连接放进连接池中 Internal.instance.put(connectionPool, result); // 如果同时创建了另一个到同一地址的多路复用连接,释放这个连接并获取那个连接 if (result.isMultiplexed()) { socket = Internal.instance.deduplicate(connectionPool, address, this); result = connection; } } closeQuietly(socket); eventListener.connectionAcquired(call, result); return result;}复制代码
该方法会被放置在一个循环当中被不停地调用以得到一个可用的连接。它优先使用当前已经存在的连接,不然就使用连接池中存在的连接,再不行的话,就创建一个新的连接。所以,上面的代码大致分成三个部分:
- 判断当前的连接是否可以使用:流是否已经被关闭,并且已经被限制创建新的流;
- 如果当前的连接无法使用,就从连接池中获取一个连接;
- 连接池中也没有发现可用的连接,创建一个新的连接,并进行握手,然后将其放到连接池中。
在从连接池中获取一个连接的时候,使用了 Internal 的 get() 方法。Internal 有一个静态的实例,会在 OkHttpClient 的静态代码快中被初始化。我们会在 Internal 的 get() 中调用连接池的 get() 方法来得到一个连接。
从上面的代码中我们也可以看出,实际上,我们使用连接复用的一个好处就是省去了进行 TCP 和 TLS 握手的一个过程。因为建立连接本身也是需要消耗一些时间的,连接被复用之后可以提升我们网络访问的效率。
CallServerInterceptor
服务器请求拦截器 CallServerInterceptor 用来向服务器发起请求并获取数据。这是整个责任链的最后一个拦截器,这里没有再继续调用执行链的处理方法,而是把拿到的响应处理之后直接返回给了上一级的拦截器:
public final class CallServerInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; // 获取 ConnectInterceptor 中初始化的 HttpCodec HttpCodec httpCodec = realChain.httpStream(); // 获取 RetryAndFollowUpInterceptor 中初始化的 StreamAllocation StreamAllocation streamAllocation = realChain.streamAllocation(); // 获取 ConnectInterceptor 中初始化的 RealConnection RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); realChain.eventListener().requestHeadersStart(realChain.call()); // 在这里写入请求头 httpCodec.writeRequestHeaders(request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } // 在这里写入请求体 if (responseBuilder == null) { realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); // 写入请求体 request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); } else if (!connection.isMultiplexed()) { streamAllocation.noNewStreams(); } } httpCodec.finishRequest(); 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(); // 读取响应体 int code = response.code(); if (code == 100) { responseBuilder = httpCodec.readResponseHeaders(false); response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } realChain.eventListener().responseHeadersEnd(realChain.call(), response); if (forWebSocket && code == 101) { response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } // ... return response; }}复制代码
连接管理:ConnectionPool
与请求的缓存类似,OkHttp 的连接池也使用一个双端队列来缓存已经创建的连接:
private final Dequeconnections = new ArrayDeque<>();复制代码
OkHttp 的缓存管理分成两个步骤,一边当我们创建了一个新的连接的时候,我们要把它放进缓存里面;另一边,我们还要来对缓存进行清理。在 ConnectionPool 中,当我们向连接池中缓存一个连接的时候,只要调用双端队列的 add() 方法,将其加入到双端队列即可,而清理连接缓存的操作则交给线程池来定时执行。
在 ConnectionPool 中存在一个静态的线程池:
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));复制代码
每当我们向连接池中插入一个连接的时候就会调用下面的方法,将连接插入到双端队列的同时,会调用上面的线程池来执行清理缓存的任务:
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; // 使用线程池执行清理任务 executor.execute(cleanupRunnable); } // 将新建的连接插入到双端队列中 connections.add(connection);}复制代码
这里的清理任务是 cleanupRunnable,是一个 Runnable 类型的实例。它会在方法内部调用 cleanup() 方法来清理无效的连接:
private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } }};复制代码
下面是 cleanup() 方法:
long cleanup(long now) { int inUseConnectionCount = 0; int idleConnectionCount = 0; RealConnection longestIdleConnection = null; long longestIdleDurationNs = Long.MIN_VALUE; synchronized (this) { // 遍历所有的连接 for (Iteratori = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); // 当前的连接正在使用中 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; // 如果找到了一个可以被清理的连接,会尝试去寻找闲置时间最久的连接来释放 long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { // 该连接的时长超出了最大的活跃时长或者闲置的连接数量超出了最大允许的范围,直接移除 connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { // 闲置的连接的数量大于0,停顿指定的时间(等会儿会将其清理掉,现在还不是时候) return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { // 所有的连接都在使用中,5分钟后再清理 return keepAliveDurationNs; } else { // 没有连接 cleanupRunning = false; return -1; } } closeQuietly(longestIdleConnection.socket()); return 0;}复制代码
在从缓存的连接中取出连接来判断是否应该将其释放的时候使用到了两个变量 maxIdleConnections 和 keepAliveDurationNs,分别表示最大允许的闲置的连接的数量和连接允许存活的最长的时间。默认空闲连接最大数目为5个,keepalive 时间最长为5分钟。
上面的方法会对缓存中的连接进行遍历,以寻找一个闲置时间最长的连接,然后根据该连接的闲置时长和最大允许的连接数量等参数来决定是否应该清理该连接。同时注意上面的方法的返回值是一个时间,如果闲置时间最长的连接仍然需要一段时间才能被清理的时候,会返回这段时间的时间差,然后会在这段时间之后再次对连接池进行清理。
以上就是我们对 OkHttp 内部网络访问的源码的分析。当我们发起一个请求的时候会初始化一个 Call 的实例,然后根据同步和异步的不同,分别调用它的 execute() 和 enqueue() 方法。虽然,两个方法一个会在当前的线程中被立即执行,一个会在线程池当中执行,但是它们进行网络访问的逻辑都是一样的:通过拦截器组成的责任链,依次经过重试、桥接、缓存、连接和访问服务器等过程,来获取到一个响应并交给用户。其中,缓存和连接两部分内容是重点,因为前者涉及到了一些计算机网络方面的知识,后者则是 OkHttp 效率和框架的核心。
forWebSocket
代表是否支持WebSocket
;
RealCall.eventListener
代表什么;
- execute()方法时候在将请求放入执行队列之前执行
eventListener.callStart(this)
- execute()方法时候在请求失败时执行
eventListener.callFailed(this, e)
用户自定义的拦截器在系统拦截器之前执行; client.interceptors()
,retryAndFollowUpInterceptor
,BridgeInterceptor
,CacheInterceptor
,ConnectInterceptor
,client.networkInterceptors()
,CallServerInterceptor
在请求入队之前就判断是否超出最大请求数与对于同一个主机请求数;
ThreadPoolExecutor为啥设置核心线程数为0,并且对列为SynchronousQueue;
Retrofit
本质上,Retrofit 使用了 Java 的动态代理,内部使用 OkHttp 来进行网络访问,并且可以通过指定 “请求适配器” 和 “类型转换器” 来完成方法参数到 OkHttp 的请求的转换,以及 OkHttp 响应到用户指定的实体类型的转换。
基本使用
Retrofit 设计的一个好的地方就是它把我们上面提到的 “请求适配器” 和 “类型转换器” 使用策略模式解耦出来。用户可以根据自己的需求通过实现指定的接口来自定义自己的类型转换器。所以,当我们使用 Gson 和 RxJava2 转换器的时候,就需要指定下面三个依赖:
api 'com.squareup.retrofit2:retrofit:2.4.0'api 'com.squareup.retrofit2:converter-gson:2.4.0'api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'复制代码
然后,我们需要根据自己的 API 接口的信息,在代码里用一个接口来对该 API 接口进行声明:
public interface WXInfoService { @GET("/sns/userinfo") ObservablegetWXUserInfo( @Query("access_token") String accessToken, @Query("openid") String openId);}复制代码
这里的 WXUserInfo 是由该 API 接口返回的 Json 生成的 Java 对象。然后,我们可以像下面这样获取一个该接口的代理对象:
WXInfoService wXInfoService = new Retrofit.Builder() .baseUrl("https://api.weixin.qq.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build().create(WXInfoService.class);复制代码
然后,我们就可以使用该对象并调用其方法来获取接口返回的信息了:
Disposable disposable = wxInfoService.getWXUserInfo(accessToken, openId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(wxUserInfo -> { /*...拿到结果之后进行处理...*/ });复制代码
动态代理
上面我们使用 Retrofit 进行网络请求,实际其内部使用 OkHttp 来完成网络请求的,然后,使用我们传入的 “类型转换器” 把响应转换成我们指定的类型。定义了一个接口并调用了该接口的方法,然后就拿到了请求的结果,这看上去非常简洁,而这其中的最功不可没的就是动态代理。
当我们使用 Retrofit.Builder 的 create() 方法获取一个 WXInfoService 实例的时候,实际返回的是经过代理之后的对象。该方法内部会调用 Proxy 的静态方法 newProxyInstance() 来得到一个代理之后的实例。为了说明这个方法的作用,我们写了一个例子:
public static void main(String...args) { Service service = getProxy(Service.class); String aJson = service.getAInfo(); System.out.println(aJson); String bJson = service.getBInfo(); System.out.println(bJson);}private staticT getProxy(final Class service) { InvocationHandler h = (proxy, method, args) -> { String json = "{}"; if (method.getName().equals("getAInfo")) { json = "{A请求的结果}"; } else if (method.getName().equals("getBInfo")) { json = "{B请求的结果}"; } return json; }; return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class []{service}, h);}复制代码
该程序的输出结果是:
{A请求的结果}{B请求的结果}复制代码
在上面的这个例子中,我们先使用 getProxy() 获取一个代理之后的实例,然后依次调用它的 getAInfo() 和 getBInfo() 方法,来模拟调用 A 接口和 B 接口的情形,并依次得到了 A 请求的结果和 B 请求的结果。上面的效果近似于我们使用 Retrofit 访问接口的过程。为了说明这个过程中发生了什么,我们需要先了解一下这里的 newProxyInstance() 方法:
public static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler h) { // ...}复制代码
该方法接收三个参数:第一个是类加载器;第二个是接口的 Class 类型;第三个是一个处理器,你可以将其看作一个用于回调的接口。当我们的代理实例触发了某个方法的时候,会调用该回调接口的方法进行处理。InvocationHandler 是一个接口,它内部定义了一个方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;复制代码
该方法也接收三个参数:第一个是触发该方法的代理实例;第二个是代理类触发的方法;第三个是触发的方法的参数。invoke() 方法的返回结果会作为代理类的方法执行的结果。
所以,当了解了 newProxyInstance() 方法的定义之后,我们可以做如下总结:当我们使用 newProxyInstance() 方法获取了一个代理实例 service 并调用其 getAInfo() 方法之后,该方法的信息和参数信息会分别通过 method 和 args 传入到 h 的 invoke() 中。所以,最终的效果就是,当我们调用 service 的 getAInfo() 时候会触发 h 的 invoke()。然后,在该方法中我们根据 method 得知触发的方法是 getAInfo。于是,我们把它对应的请求从 invoke() 方法中返回,并作为 service.getAInfo() 的返回结果。
所以,我们可以总结 Retrofit 的大致工作流程:当我们获取了接口的代理实例,并调用它的 getWXUserInfo() 方法之后,该 API 的请求参数会传递到代理类的 InvocationHandler.invoke() 方法中。然后在该方法中,我们将其转换成 OkHttp 的 Request,然后使用 OkHttp 进行访问。当拿到结果之后,我们使用传入的 “转换器” 将响应转换成接口指定的 Java 类型。
Retrofit 的源码解析
创建 Retrofit
根据上面的例子,当使用 Retrofit 的时候,首先我们需要使用 Retrofit 的构建者来创建 Retrofit 的实例。这里有几个重要的方法需要提及一下:
addConverterFactory 方法
该方法用来向 Retrofit 中添加一个 Converter.Factory。Converter.Factory,顾名思义是一种工厂模式,它是一个接口需要实现两个重要的方法。每个方法需要返回一个转换器:一个是某种数据类型到请求体的转换器,另一个是响应体到我们需要的数据类型的转换器。当我们使用 Gson 来完成这个转换,那么我们就需要使用 GsonConverterFactory.create() 来得到一个适用于 Gson 的 Converter.Factory。
public Builder addConverterFactory(Converter.Factory factory) { converterFactories.add(checkNotNull(factory, "factory == null")); return this;}复制代码
addCallAdapterFactory 方法
CallAdapter.Factory 用于获取 CallAdapter 对象, CallAdapter 对象用于把原生的 OkHttp 的 Call 转换成我们指定的请求类型。比如,转换成 RxJava2 的 Observable 类型。下面是该方法的定义:
public Builder addCallAdapterFactory(CallAdapter.Factory factory) { callAdapterFactories.add(checkNotNull(factory, "factory == null")); return this;}复制代码
build 方法
当根据用户的自定义设置完了参数之后,就可以调用 build() 方法,来获取一个 Retrofit 的实例。在该方法中会将上述的工厂实例添加到一个列表中,然后根据请求、响应的类型来获取工厂实例,然后分别获取一个转换器或者适配器。
适配器 CallAdapter 和转换器 Converter 的作用
从上面我们看出,CallAdapter 主要用来将某个请求转换成我们指定的类型。比如,在我们最开始的例子中,要将请求转换成 Observable<WXUserInfo>
。如果转换之后的请求是 Observable 类型的,那么当我们对转换后的请求进行订阅的时候,就启动了 OkHttp 的网络请求过程。
在进行网络请求之前会先使用 Converter 将请求的参数转换成一个 RequestBody。这里将其作为一个接口的好处是便于解耦。比如,上面我们用 Gson 来完成转换过程,你也可以通过自定义转换器来使用其他的框架,比如 Moshi 等。当拿到了响应之后,我们又会再次使用 Converter 来将响应体 ResponseBody 转换成我们要求的类型。比如,上面的例子中应该转换为 WXUserInfo。
最后,OkHttp 那里得到的响应会在 CallAdapter 方法内部被包装成 Observable 并返回给观察者。这样,我们就拿到了这个请求的结果。
从上面我们看出,Retrofit 设计的非常妙的地方就在于上面的两个过程的解耦(策略模式+工厂模式)。一次是将请求转换成 Observable 的过程,一次是将请求体和响应体转换成 OkHttp 要求的 RequestBody 和 ResponseBody 的过程。对于前者,不论我们使用的是 RxJava 1 还是 RxJava 2,只要传入一个 CallAdapter 即可。对于后者,不论我们使用哪种 Json 转换框架,只要实现了 Converter 接口皆可。
获取代理实例
划分平台:Platform
创建了 Retrofit 的实例之后,我们就可以使用它的 create() 方法来获取代理之后的服务实例。下面是这个方法的定义。在这里,我们会先根据 validateEagerly 变量来判断是否立即对传入的服务接口的方法进行解析。然后,我们使用 Proxy 的静态方法获取一个代理实例。
publicT create(final Class service) { Utils.validateServiceInterface(service); // 这里的 validateEagerly 在 Retrofit 构建的时候设置 if (validateEagerly) { // 是否立即对 Service 方法的内容进行解析 eagerlyValidateMethods(service); } // 获取代理实例 return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class [] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // 该方法是 Object 的方法,直接触发该方法 if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } // 如果是 default 方法,那么使用该 Java8 平台的方法执行 if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } // 获取服务方法的信息,并将其包装成 ServiceMethod ServiceMethod
这里的 eagerlyValidateMethods() 方法定义如下:
private void eagerlyValidateMethods(Class service) { // 获取程序当前运行的平台 Platform platform = Platform.get(); for (Method method : service.getDeclaredMethods()) { // 判断该方法是否是 default 方法 if (!platform.isDefaultMethod(method)) { loadServiceMethod(method); } }}复制代码
它的作用是立即对服务接口的方法进行解析,并将解析之后的结果放进一个缓存中。这样,当这个服务方法被触发的时候,直接从缓存当中获取解析之后的 ServiceMethod 来使用即可。该方法会先会根据当前程序运行的平台来决定是否应该加载服务的方法。因为,Java 8 之后,我们可以为接口增加 default 类型的方法,所以,如果是 default 类型的话,我们不会调用 loadServiceMethod() 进行解析,而是调用 Java8 平台的 invokeDefaultMethod() 来处理。在 invokeDefaultMethod() 中,会根据传入的信息创建一个实例并使用反射触发它的方法。此时,就间接地触发了该 default 方法。
判断平台的时候,使用了如下这段代码:
platform.isDefaultMethod(Method)复制代码
这里的 platform 是调用 Platform.get() 的时候得到的。它会在 get() 方法中尝试使用反射去获取一个只有 Java8 平台才具有的类,以此来判断是否是 Java8 的环境。在 Retrofit 中,提供了 Java8 和 Android 两个类来区分所在的平台,并会根据运行环境来决定返回哪个实例。
从上面我们看出,Platform 算是一种策略的设计模式,以根据平台的不同做不同的处理。但在当前的版本中,它的主要作用是对 default 类型的方法进行处理。
解析服务方法:ServiceMethod
上面我们提到过 loadServiceMethod() 方法,它的主要作用:首先会尝试从缓存当中获取该方法对应的 ServiceMethod 实例,如果取到的话,就将其返回;否则,就使用构建者模式创建一个并放进缓存中,然后将其返回。
ServiceMethod loadServiceMethod(Method method) { // 从缓存中进行获取 ServiceMethod result = serviceMethodCache.get(method); if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { // 创建ServiceMethod实例 result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); } } return result;}复制代码
ServiceMethod 的构建过程比较简单,只需要把当前的 Retrofit 实例和服务方法 method 传入进去,然后调用它的 build() 方法就完成了整个创建过程。在 build() 方法中,会完成对 method 的解析,比如根据注解判断是什么类型的请求,根据方法的参数来解析请求的请求体等等。ServiceMethod 内部的变量主要是与请求的相关的信息,同时它也提供了一些方法,用于获取 OkHttp 的请求和响应。
所以,ServiceMethod 的作用是缓存服务方法对应的请求信息,这样下次我们就不需要再次解析了。同时,它提供了以下几个方法,它们的主要作用是用来从 ServiceMethod 中获取请求相关的信息。
toCall() 用来获取用于 OkHttp 请求的 Call 对象:
okhttp3.Call toCall(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); // ... return callFactory.newCall(requestBuilder.build());}复制代码
toResponse(ResponseBody) 用来把 OkHttp 得到的响应体转换成 Java 对象等(在示例中是WXUserInfo):
R toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body);}复制代码
adapt(Call<R>)
用来将 OkHttp 的请求转换成我们的服务方法的返回类型(在示例中是Observable<WXUserInfo>
):
T adapt(Callcall) { return callAdapter.adapt(call);}复制代码
请求封装:OkHttpCall
解析完毕服务方法之后,我们得到了 ServiceMethod 实例。然后,我们使用它来创建 OkHttpCall 实例。这里的 OkHttpCall 实现了 Retrofit 中定义的 Call 接口,会在方法内调用 ServiceMethod 的 toCall() 方法来获取 OkHttp 中的 Call 对象,然后使用它进行网络访问。当拿到了请求的结果之后又使用 ServiceMethod 的 toResponse() 把响应转换成我们指定的类型。下面是该类中的几个比较重要的方法:
execute() 方法,用来同步执行网络请求:
@Overridepublic Responseexecute() throws IOException { okhttp3.Call call; synchronized (this) { // ... call = rawCall; if (call == null) { try { // 创建 OkHttp 的 Call 实例 call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); creationFailure = e; throw e; } } } if (canceled) { call.cancel(); } // 同步执行请求,并解析结果 return parseResponse(call.execute());}复制代码
createRawCall() 用来创建 OkHttp 的 Call 实例:
// 使用 serviceMethod 的 toCall 方法获取 OkHttp 的 Call 实例private okhttp3.Call createRawCall() throws IOException { okhttp3.Call call = serviceMethod.toCall(args); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call;}复制代码
parseResponse() 用来将 OkHttp 的响应转换成我们接口中定义的类型。比如,在我们的例子中,返回的是 Observable<WXUserInfo>
:
// 使用 serviceMethod 的 toResponse 方法获取 OkHttp 的 Response 实例ResponseparseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); // ... ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody); try { // 使用 serviceMethod 的 toResponse 方法获取 OkHttp 的 Response 实例 T body = serviceMethod.toResponse(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { catchingBody.throwIfCaught(); throw e; }}复制代码
Retrofit 的工作过程
从触发代理类的方法到拿到响应的结果,这一整个过程中,都有哪些类的哪些方法参与,以及它们在什么时候,扮演什么样的角色。
我们将 Retrofit 的请求的过程分成三个过程来进行说明:
- 创建代理实例的过程:在这个过程中主要是调用 Proxy.newProxyInstance() 来获取一个代理实例。相关的主要参数是 validateEagerly,我们会使用它来决定是否立即对传入的接口的方法进行解析。不论我们什么时候进行解析,都会把解析的结果缓存起来。
- 触发代理方法的过程:触发代理方法是整个请求的第二过程,这个时候,我们调用了 WXInfoService 代理实例的 getWXUserInfo() 方法。此时,会触发 InvocationHandler 的 invoke() 方法。在该方法内部会调用 ServiceMethod 的构建者模式来创建 ServiceMethod 实例。当调用构建者模式的 build() 方法的时候,会对方法 getWXUserInfo() 的信息进行解析。然后,使用 ServiceMethod 实例创建 OkHttpCall。最后,使用 ServiceMethod 实例的 adapt() 方法将 OkHttpCall 实例转换成
Observable<WXUserInfo>
。此时,会使用 CallAdapter 的 adapt() 方法来完成适配过程。 - 执行网络请求的过程:拿到了
Observable<WXUserInfo>
之后,需要对其进行订阅才能触发网络请求。相关的逻辑在 CallAdapter 中完成。首先,它会根据你使用同步还是异步的来决定使用哪个执行器。这里存在两个执行器,它们的区别是一个会在内部调用 OkHttpCall 的 enqueue(),另一个会在执行器中调用 OkHttpCall 的 execute() 方法。不论调用 enqueue() 还是 execute(),都会先使用 OkHttpCall 的 toCall() 方法获取一个 Call 请求。获取请求的过程中会使用 Converter 来将某个实例转换成请求体。拿到了请求之后,使用该请求来进行网络访问。当从网络中拿到了响应之后,会使用 Converter 来将响应体转换成对象。这样,拿到了实际的结果之后,就会调用 Observer 的 onNext() 方法把结果通知给观察者。
从上文中可以看出来,Retrofit 设计的几个值得我们借鉴的地方:
- 使用运行时注解和反射简化请求描述,但是考虑到反射的效率比较低,所以将一次反射之后的结果缓存起来,以便于下次使用。
- 动态代理:使用接口描述请求的好处是它简洁,而且 “描述” 本来就是它的责任。但是,一般我们需要去实现接口才能使用。而这里告诉我们,使用动态代理一样可以使用接。
- 解耦:从我们上面的图中也可以看出来,Retrofit 的设计的思路是比较清晰的。它将一个请求的几个过程解耦出来。首先是我们 Observable 到请求的转换,这里使用适配器来完成;然后是请求体和响应体的转换,基本就是 Json 的转换,使用转换器来完成。这样,不论你使用 RxJava 1 还是 RxJava 2,不论是 Gson 还是 FastXml 都可以和 Retrifut 配合使用。