Android开源框架:OkHttp3

1,248 阅读5分钟

1 简述

OKhttp3是一款高效的Http客户端,支持连接同一地址的链接共享同一个socket,通过连接池减少响应延迟,还有透明的GZIP压缩,请求缓存等优势,其核心主要有路由、连接协议、拦截器、代理、安全性认证、连接池、以及网络适配,移除或者转换请求或者回应的头部信息。

上张简易的流程图,我们再来看OkHttp3的原理。
OkHttp原理图.png 注:本文分析均基于"com.squareup.okhttp3:okhttp:3.14.9" 版本。

2 基本使用

下面是使用OkHttp进行GET请求的一个示例。

OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
    .url("https://api.github.com/")
    .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String result = response.body().string();
    }
});

从这个示例中,我们不难看出官方实现的一种设计美感在里面。
第一步,OkHttpClient okHttpClient = new OkHttpClient(); 我们把它类比为得到一个邮箱
第二步,我们开始写信,new Request.Builder().url("https://api.github.com/").build(); 信里面我们可以添加很多内容,以及最重要的寄信地址 URL
第三步,我们把信放入信箱,okHttpClient.newCall(request);
第四步,等待返回结果。
OkHttp设计很好的一个点就是开发者不用关心,请求怎么发出去?大量请求怎么维护的?返回结果如何处理?
只需要,“写好信”放过去,然后等待(异步\同步)返回结果即可。

3 原理剖析

下面我们以三个问题来看看OkHttp核心逻辑做了哪些事?

  • 请求去了哪里?
  • 大量请求如何被维护的?
  • 请求如何被处理?返回如何被接收?

3.1 请求去了哪里?大量请求如何被维护的?

call.enqueue(new Callback()往下跟源码,就会找到一个Dispatcher的类。

在这个类中有几个队列:

/** 异步请求的执行顺序的队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** 运行中的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** 运行中的同步请求队列(包含已取消,但是还没有结束的请求) */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

看代码我们就会发现,所有请求都在着三个队列中流转(注:线程池中的先不看)。
以异步为例:

void enqueue(AsyncCall call) {
    synchronized (this) {
        readyAsyncCalls.add(call); // 请求顺序加入队列

        // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
        // the same host.
        if (!call.get().forWebSocket) {
            AsyncCall existingCall = findExistingCallWithHost(call.host());
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
        }
    }
    promoteAndExecute();
}

promoteAndExecute() 方法

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        //获取等待中的任务队列
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
			// 超过可以同时运行的最大请求任务数64
            if (runningAsyncCalls.size() >= maxRequests) break;
            // 超过同一主机同时运行的最大请求任务数5
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;

            // 满足【运行队列任务数小于64】和【同一主机同时运行请求小于5】
            // 就从等待队列中移除,添加到【运行时队列中】
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        // 执行每一个异步请求
        asyncCall.executeOn(executorService());
    }

    return isRunning;
}

通过源码,我们了解到,异步请求均被放在了readyAsyncCalls,然后根据条件放到runningAsyncCalls 中,最后调用线程池执行每一个异步请求!

// 具体执行源码
void executeOn(ExecutorService executorService) {
    assert (!Thread.holdsLock(client.dispatcher()));
    boolean success = false;
    try {
        // 放到线程池执行
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        if (!success) {
            client.dispatcher().finished(this); // This call is no longer running!
        }
    }
}

// NamedRunnable 中调用
@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);
        }
    } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
            IOException canceledException = new IOException("canceled due to " + t);
            canceledException.addSuppressed(t);
            responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
    } finally {
        client.dispatcher().finished(this);
    }
}

看看OkHttp创建的具体的线程执行者!

public synchronized ExecutorService executorService() {
    if (executorService == null) {
        executorService = new ThreadPoolExecutor(0, 
                                                 Integer.MAX_VALUE, 
                                                 60, 
                                                 TimeUnit.SECONDS,
                                                 new SynchronousQueue<>(), 
                                                 Util.threadFactory("OkHttpDispatcher", false));
    }
    return executorService;
}

ThreadPoolExecutor 参数说明

  • int corePoolSize: 核心线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁。
  • int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理。
  • long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive。
  • TimeUnit unit: 时间单位,一般用秒。
  • BlockingQueue workQueue: 工作队列,先进先出。
  • ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)。

小结一下:
至此,我们以异步请求为例,弄清楚了 请求去了哪里? 请求怎么在队列中进行维护的?,那么同步请求也一目了然,直接别放到了runningSyncCalls队列中,直接执行。

3.2 请求如何被处理?返回如何被接收?

接下来我们看看请求是如何被一步一步处理并发给服务器的,这就不得不分析一波,OkHttp的经典的拦截器责任链。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> 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);
        }
    }
}
  • client.interceptors():用户自定义的 Interceptor。
  • RetryAndFollowUpInterceptor:负责失败重试以及重定向。
  • BridgeInterceptor:负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
  • CacheInterceptor:负责读取缓存以及更新缓存。
  • ConnectInterceptor:负责与服务器建立连接。
  • client.networkInterceptors():用户自定义的网络层 Interceptor
  • CallServerInterceptor:负责从服务器读取响应的数据。

从源码中我们大致看出一个请求在经历了几个拦截器的处理后,在CallServerInterceptor拦截阶段发出,并得到了返回,然后又从最底层的拦截器开始,一层层往上包装返回。所以,我们可以看到,每一个拦截器都会向上返回response。

@Override public Response intercept(Chain chain) throws IOException {
    // request阶段处理
    Request request = chain.request();
	...
	// 返回response
    return response;
}

最后
我们通过几个问题大致理清了OkHttp3在处理请求的过程中,如何在几个队列中处理请求?如何层层封装请求?如何封装返回?
关于里面更详细的一些细节,比如每个拦截器具体做了什么事情,博主后面会继续更新。