首页 > 编程知识 正文

OKHttp 原理

时间:2023-05-04 22:42:52 阅读:240962 作者:1078

OkHttp 是一个高性能的网络库,主要功能如下:

支持底层协议(HTTPS / HTTP1.1 / HTTP2 / WebSocket),包括连接池、连接复用、缓存控制等维护任务队列线程池,支持友好并发访问提供拦截器链,实现 Request 和 Response 分层处理

OkHttp 中涉及到的设计模式

建造者模式。构建 OkHttpClient工厂模式。OkHttpClient 实现了 Call.Factory,用于生产 Call 产品(RealCall)享元模式。通过线程池、连接池共享对象责任链模式。将不同功能的拦截器形成一个链

同步请求

RealCall 表示被执行的请求,调用 execute 方法执行同步请求,最终会把 RealCall 放到 Dispatcher 的同步双端队列中顺序执行,执行结束后调用 finish 方法,把 RealCall 请求从 Dispatcher 中移除,无论是同步还是异步请求,最终都会调用 getResponseWithInterceptorChain 方法,在 OkHttp 中,拦截器链会依次完成相应功能

Call 对象只能执行一次,否则会抛出异常

@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.dispatcher().executed(this); return getResponseWithInterceptorChain(); } synchronized void executed(RealCall call) { runningSyncCalls.add(call); }

异步请求

AsyncCall 表示被执行的请求,调用 enqueue 方法执行异步请求,如果当前所有请求数(64)和单一 host 请求数(5)满足要求,则把 AsyncCall 放到 Dispatcher 的 Running 队列中顺序执行,否则放到 waiting 队列中,AsyncCall 执行结束后调用 finish 方法,把当前 AsyncCall 从 running 队列中移除并执行线程调度

@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.dispatcher().enqueue(new AsyncCall(responseCallback)); } synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } @Override protected void execute() { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { responseCallback.onResponse(RealCall.this, response); } } }

Dispatcher 调度器:维护请求状态、维护线程池(减少创建线程的开销、减少线程过少造成 CPU 闲置、减少线程过多时内存和线程切换的开销)

private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { synchronized (this) { calls.remove(call) if (promoteCalls) promoteCalls(); } } private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; if (readyAsyncCalls.isEmpty()) return; for (Iterator<AsyncCall> 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; } }

拦截器链

逻辑大致分为两部分:

创建一系列拦截器,并将其放入拦截器数组中。这部分拦截器即包括用户自定义的拦截器也包括框架内置的拦截器创建一个拦截器链 RealInterceptorChain,并执行拦截器链的 proceed 方法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); return chain.proceed(originalRequest);}public final class RealInterceptorChain implements Interceptor.Chain { private final List<Interceptor> interceptors; private final StreamAllocation streamAllocation; private final HttpCodec httpCodec; private final RealConnection connection; private final int index; private final Request request; private int calls; public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request) { public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { ... RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); ... return response; }}

Chain.proceed 方法做了两件事

创建下一个拦截链。传入 index + 1 使得下一个拦截器链只能从下一个拦截器开始访问执行索引为 index 的 intercept 方法,并将下一个拦截器链传入该方法

拦截器的 intercept 方法所执行的逻辑大致分为三部分

在发起请求前对 request 进行处理调用下一个拦截器,获取 response对 response 进行处理,返回给上一个拦截器

Okhttp 中除了用户自定义的拦截器外还内置了若干核心拦截器,具体功能如下

RetryAndFollowUpInterceptor

在网络请求失败后进行重试当服务器返回 3XX(重定向) 时直接发起新的请求,并在条件允许情况下复用当前连接

BridgeInterceptor

设置内容长度,内容编码设置 GZIP 压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦添加 Cookie设置其他 Header 数据,如 User-Agent、Host、Connection = Keep-alive 等

CacheInterceptor

当网络请求有符合要求的 Cache 时直接返回 Cache当服务器返回内容有改变时更新当前 cache如果当前cache失效,删除

ConnectInterceptor

为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接

CallServerInterceptor

负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回

HTTP2连接复用规则

RealConnection 描述一个物理 Socket 连接,连接池中维护多个 RealConnection 实例。由于HTTP2 支持多路复用,一个 RealConnection 可以支持多个网络访问请求,所以 OkHttp 又引入了 StreamAllocation 来描述一个实际的网络请求。通过 RealConnection 中的 StreamAllocation 弱引用队列是否为 0 来判断当前 RealConnection 是否空闲

通过 ConnectionPool 来管理连接池,ConnectionPool 有一个独立的线程 cleanupRunnable 来清理连接池,其触发时机有两个:当连接池新增连接时、当连接闲置时(connectionBecameIdle 接口被调用时)搜集所有的空闲连接并记录 KeepAlive 时间,如果空闲连接超过 5 个或者某个连接的 KeepAlive 时间大于五分钟,就移除 KeepAlive 时间最长的连接,立刻再次扫描如果目前空闲连接不多于 5 个且 KeepAlive 时间都不大于 5 分钟,就返回最大 KeepAlive 的剩余的时间,供下次清理如果(全部都是活跃的连接),就返回默认的 keep-alive 时间,也就是 5 分钟后再执行清理

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。