Android源码-一文带你搞懂OkHttp,一年后斩获腾讯T3

50 阅读3分钟

client = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .build();

同步请求

public void okHttpSync() { Request request = new Request.Builder() .url("www.baidu.com") .build(); Call call = client.newCall(request); try { Response response = call.execute(); if (response.isSuccessful()) { System.out.println("response.code()==" + response.code()); System.out.println("response.heard()==" + response.headers()); System.out.println("response.message()==" + response.message()); System.out.println("res==" + response.body().string()); } } catch (IOException e) { e.printStackTrace(); } }

异步请求

public void okHttpAsync() { Request request = new Request.Builder() .url("www.baidu.com") .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { needCancelled.set(true); System.out.println("url==" + call.request().url()); }

@Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { System.out.println("response.code()==" + response.code()); System.out.println("response.heard()==" + response.headers()); System.out.println("response.message()==" + response.message()); System.out.println("res==" + response.body().string()); needCancelled.set(true); } } }); }

OkHttp核心执行流程是怎样?

关键类功能说明

代码执行流程

  1. 通过Builder模式统一构建OkHttpClient对象
  2. 通过Call,实现类RealCall进行请求发送
  3. RealCall通过调用了Dispatcher的execute()及enqueue()方法进行同步及异步的请求
  4. 最终调用ReallCall的getResponseWithInterceptorChain()方法进行拦截链的拦截
  5. 依次通过重定向拦截器、桥接拦截器、缓存拦截器、连接拦截器、网络拦截器依次进行处理
  6. 最后通过intercept的return往回返回Response,最终返回给客户端请求的结果

OkHttp如何进行线程调度控制?

线程调度

在Dispatcher中维护了一个线程池,异步的请求会将任务加入到线程池中。

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; }

默认的最大并发数为maxRequests=64,如果超过限制会加入到等待队列中,执行异步的方法如下

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

最后线程池执行AsyncCall中的execute()方法,如下

@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 { client.dispatcher().finished(this); } }

队列机制

Dispathcer中维护了3个队列,分别为异步等待队列、异步执行队列、同步执行队列。

/** Ready async calls in the order they'll be run. */ private final Deque readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque runningSyncCalls = new ArrayDeque<>();

不管是同步还是异步,最终在finally块都会调用dispatcher的finished方法,会移除掉该队列任务,最后实现如下

int runningCallsCount; Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; }

if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); }

在finish中会再调用promoteCalls方法,会重新检索准备中的队列,将队列加入到线程中

private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next();

if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); }

if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }

OkHttp的拦截器及调用链是怎么执行?

调用链执行流程

通过上述的分析,我们知道不管同步还是异步,最终调用到的都是RealCall的getResponseWithInterceptorChain()方法,如下:

Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List 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); }

其中定义了拦截器集合及RealInterceptorChain拦截链,具体执行了拦截链的proceed方法,如下:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it. if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)

  • " must retain the same host and port"); }

// If we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)

  • " must call proceed() exactly once"); }

// 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);

// Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor

  • " must call proceed() exactly once"); }

// Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); }

if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); }

return response; }

  1. 先判断是否超过list的size,如果超过则遍历结束,如果没有超过则继续执行
  2. calls+1
  3. new了一个RealInterceptorChain,其中然后下标index+1
  4. 从list取出下一个interceptor对象
  5. 执行interceptor的intercept方法

总结一下就是每一个RealInterceptorChain对应一个interceptor,然后每一个interceptor再产生下一个RealInterceptorChain,直到List迭代完成。

拦截器

从上面的调用关系可以看出除了红色圈出的拦截器之外都是系统提供的拦截器,这整个过程是递归的执行过程,在 CallServerInterceptor 中得到最终的 Response 之后,将 response 按递归逐级进行返回,期间会经过 NetworkInterceptor 最后到达 Application Interceptor 。

OkHttp是如何进行数据缓存?

缓存策略

OkHttp使用了CacheInterceptor拦截器进行数据缓存的控制使用了CacheStrategy实现了上面的流程图,它根据之前缓存的结果与当前将要发送Request的header进行策略,并得出是否进行请求的结果。根据输出的networkRequest和cacheResponse的值是否为null给出不同的策略,如下:

networkRequest cacheResponse result 结果 null null only-if-cached (表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误) null non-null 不进行网络请求,直接返回缓存,不请求网络 non-null null 需要进行网络请求,而且缓存不存在或者过去,直接访问网络 non-null non-null Header中包含ETag/Last-Modified标签,需要在满足条件下请求,还是需要访问网络

缓存算法

通过分析CacheInterceptor拦截器的intercept方法,我们可以发现具体的缓存都是使用了Cache类进行,最后具体的实现在DiskLruCache类中。缓存实际上是一个比较复杂的逻辑,单独的功能块,实际上不属于OKhttp上的功能,实际上是通过是http协议和DiskLruCache做了处理。LinkedHashMap可以实现LRU算法,并且在这个case里,它被用作对DiskCache的内存索引

OkHttp的连接池复用机制是怎么样?

链路

RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过 RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路。 另外StreamAllocation类为流的桥梁,在RetryAndFollowUpInterceptor中进行初始化,在ConnectInterceptor中进行newStream操作,具体的连接拦截器代码如下:

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); }

newStream创建留最后会调用到findConnection方法,这里面是连接复用的关键,如果再连接池中找到能复用的连接,则直接返回。 否则将RealConnection加入到链接池ConnectionPool中,具体代码如下:

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { boolean foundPooledConnection = false; RealConnection result = null; Route selectedRoute = null; Connection releasedConnection; Socket toClose; synchronized (connectionPool) { if (released) throw new IllegalStateException("released"); if (codec != null) throw new IllegalStateException("codec != null"); if (canceled) throw new IOException("Canceled");

// Attempt to use an already-allocated connection. We need to be careful here because our // already-allocated connection may have been restricted from creating new streams. releasedConnection = this.connection; toClose = releaseIfNoNewStreams(); if (this.connection != null) { // We had an already-allocated connection and it's good. result = this.connection; releasedConnection = null; } if (!reportedAcquired) { // If the connection was never reported acquired, don't report it as released! releasedConnection = null; }

if (result == null) { // Attempt to get a connection from the pool. 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);

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料,免费分享给大家。 (包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

领取方式:关注+点赞+点击我的GitHub 免费获取

image