Android 4.4之后,HttpURLConnection底层实现已被OkHttp替换。可以见得OkHttp的性能已经被Google所认同。对于为何会想深入了解该库的原因:因为它的最底层走到了java的Socket;利用向Socket写入特定的Http协议数据包,实现网络通信。学习该开源项目,对于网络的学历大有益处,除此之外OkHttp使用了缓存和线程池概念。总之个人觉得OkHttp开源项目可以作为学习网络通信一篇很好的教科书。
OkHttp的特点:
- 支持HTTP2/SPDY黑科技
- socket自动选择最好路线,并支持自动重连
- 拥有自动维护的socket连接池,减少握手次数
- 拥有队列线程池,轻松写并发
- 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
- 实现基于Headers的缓存策略
OkHttp的使用:
- 创建OkHttpClient对象:OkHttpClient client = new OkHttpClient();
- 创建网络请求:Request request = new Request.Builder() .url("http://sethfeng.github.io/index.html") .build();
- 得到Call对象:Call call = client.newCall(request); //实际创建的是一个RealCall对象,RealCall中有一个对client对象的引用
- 发送同步请求:Response response = call.excute();
- 发送异步请求:
call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { ... } @Override public void onResponse(Response response) throws IOException { ... } });
OkHttpClient工作原理初步分析
RealCall.class
先来看一下通过 client.newCall(request)得到的Call对象
newCall(request)@OkHttpClient.class
Call newCall(Request request) {
return new RealCall(this, request);
}
方法很简单就是利用调用newCall方法的OkHttpClient对象和newCall的输入参数构造一个RealCall对象。接下来看一下RealCall的构造器。
RealCall()@RealCall.class
protected RealCall(OkHttpClient client, Request originalRequest) {
this.client = client;
this.originalRequest = originalRequest;
}
构造器也很简单,就是对RealCall中的OkHttpClient和Request域进行赋值。
接下来看一下RealCall类中的execute和enqueue方法
execute@RealCall.class
public Response execute() throws IOException
{
try {
this.client.getDispatcher().executed(this); //同步请求不排队
//方法内部会执行synchronized void executed(Call call) { this.executedCalls.add(call); } 即把这次请求加入到分发器里
Response result = this.getResponseWithInterceptorChain(false);
if(result == null) {
throw new IOException("Canceled");
}
var2 = result;
} finally {this.client.getDispatcher().finished(this); }
...
return var2;
}
该方法完成一个同步请求,首先将这次的同步请求call添加到Dispatcher的工作队列中,随后调用getResponseWithInterceptorChain方法获取request对应的response。最后返回得到的response。
enqueue@RealCall.class
public void enqueue(Callback responseCallback) {
enqueue(responseCallback, false);
}
实际调用下面方法
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); //异步请求会排队!
}
内部类AsyncCall@RealCall.class
final class AsyncCall extends NamedRunnable{
protected void execute() {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
this.client.getDispatcher().finished(this);
}
}
enqueue则是将请求包装成一个异步请求,异步请求继承自Runnable的子接口,实现了一个execute异步方法,Dispatcher会在合适的时间调用该方法,注意这里的execute方法和前面的execute不是同一个方法。在execute方法内部会调用 getResponseWithInterceptorChain方法获得网络请求的返回值,随后利用回调方法,将结果发送给客户。
Dispatcher负责对客户请求进行处理,因此接下来分析一下Dispatcher的execute和enqueue方法。将在下一节分析getResponseWithInterceptorChain方法的底层实现。
Dispatcher.class
OkHttpClient中有一个Dispatcher类型的域,在构造OkHttpClient的时候会调用new Dispatcher()方法获得一个Dispatcher对象,因此我们直接看Dispatcher的源码。
Dispatcher()@Dispatcher.class
public Dispatcher() {
}
看到这个方法一脸的懵逼。什么也没做?不要方,既然这里没有做任何工作,那么有两种可能,Dispatcher的相关域要么在类加载时或者声明时就进行了初始化,要么就会在使用的时候再临时进行初始化,即延迟初始化,这也是一种优化。那我们直接看execute方法和enqueue方法
executed@Dispatcher.class
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
该方法很简单,就是将Recall请求添加到runningSyncCalls集合中。该集合在构造Dispatcher的时候就初始化好了Deque<RealCall> runningSyncCalls = new ArrayDeque<>();该集合代表着当前正在线程中运行的请求。有的童鞋是不是已经方了?这就完了?我们的请求在哪执行!!??不要忘了我们调用的RealCall的execute方法中,在this.client.getDispatcher().executed(this);语句后面继续调用Response result = this.getResponseWithInterceptorChain(false);方法,而实际的网络请求是在这里进行的。可以说Dispatch主要负责对客户的请求进行管理,留存,备份;重点在于利用线程池对异步请求的处理,同步请求它基本不干活。下面接着看enqueue方法。
enqueue()@Dispatcher.class
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// maxRequestsPerHost默认为5 maxRequests默认64
runningAsyncCalls.add(call);
executorService().execute(call); //交给线程池去执行 call中的execute方法
} else {
readyAsyncCalls.add(call);
//存入等待队列
//对于存入这里的请求,在方法promoteCalls()中会被取出,进行执行;
//任务执行完成后,调用finished的promoteCalls()函数,不管是异步还是同步请求,它们在执行完execute方法过后都会调用Dispatcher的finished方法
}
}
这个方法代码相对于execute就多了一些代码,不过逻辑很简单,首先判断集合runningSyncCalls的大小,即当前运行的请求数是否小于maxRequest;同时判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost。如果条件全为真,则直接将该请求加入到集合runningSyncCalls中,随后调用executorService().execute(call);对异步任务进行处理。否则将异步请求加入到等待异步执行队列readyAsyncCalls中。下面看看executorService方法
executorService()@Dispatcher.class
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
该方法返回了一个线程池,很熟悉吧;类似通过Executors.newCachedThreadPool()方法得到一个线程池。那么调用executorService().execute(call);方法最终会执行异步请求的execute方法,而异步请求的execute方法内部会调用getResponseWithInterceptorChain()方法获得response。
那么到此为止对于execute方法和enqueue方法的介绍就结束了。如果好奇点的童鞋,可能会问,如果异步请求被添加到readyAsyncCalls集合中,那么它何时会被执行呢?注意到,在RealCall的execute和enqueue方法执行完后都会执行this.client.getDispatcher().finished(this); 这样一条语句。那么我们来看下Dispatcher的finished方法。
finished()@Dispatcher.class
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();
}
方法很简单,将请求从runningAsyncCalls中移除出去,随后执行 promoteCalls()方法,接着看该方法源码
finished()@Dispatcher.class
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
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; // Reached max capacity.
}
}
在当前运行任务数大于maxRequests和等待执行异步任务数为空的两种情况下直接返回不进行任何操作。否则
从等待执行异步请求集合中获取到请求,判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost,为真则将该任务从等待执行异步请求集合中移出,存入runningAsyncCalls集合中,最后调用线程池执行器执行该异步请求的execute方法。到此为止我们对于Dispatcher的介绍就到此为止了。
对Dispatcher的总结如下:
- 该类中有两个集合分别为:runningAsyncCalls、readyAsyncCalls前者存放正在执行的请求,后者存放等待执行的请求
- 该类中有一个newCachedThreadPool线程执行器,利用该执行器来执行异步请求的execute方法。也就是说异步请求发送在非当前工作线程,即创建异步请求的线程,而是从线程池中获取一条线程执行网络请求。同步请求则直接是在当前工作线程中执行。
- 该类对异步请求的管理是通过maxRequests、maxRequestsPerHost进行控制的,前者控制线程池中同时运行的最大请求数,防止同时运行线程过多,造成OOM。后者限制了同一hostname下的请求数,防止一个应用占用的网络资源过多,优化用户体验。
文章的最后我们对okhttp中使用过程中遇到的Request、Response、OkHttpClient这几个类进行一下介绍。
Request.class
该类中有如下域
private final HttpUrl url; //目标地址
private final String method; //方法
private final Headers headers; //请求头,Headers.class里面维护了一个private final String[] namesAndValues;数据集
private final RequestBody body; //请求表单
private final Object tag; //标签
private volatile URI javaNetUri; // Lazily initialized.
private volatile CacheControl cacheControl; // Lazily initialized.
只能通过Builder方法构建Request对象。
Builder()@Builder.class@Request.class
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
默认创建的是Get方法
Build()@Builder.class@Request.class
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
调用Request构造器,创建Request对象。
Response.class
类中有如下域
private final Request request; //对应的request
private final Protocol protocol; //对应的Http协议
private final int code; //返回状态码
private final String message; //Http状态对应的消息
private final Handshake handshake; //TLS握手协议Transport Layer Security
private final Headers headers; //返回响应头
private final ResponseBody body; //Http表单
private Response networkResponse; //来源于网络的Response,如果响应来自缓存,则该值为null
private Response cacheResponse; //来自缓存的响应
private final Response priorResponse; //在redirect或者授权改变的时候,该结果不为空
private volatile CacheControl cacheControl; // Lazily initialized.
Okhttp中有client.internalCache()和client.connectionPool()两个重要的概念,前者管理网络访问的缓存信息,后者用于存储已链接的RealConnection(该RealConnection已经跟对应的hostname完成了三次握手)。下面我们看一下创建Cache和ConnectionPool这两个对象的OkHttpClient对象。
OkHttpClient.class
Internal.instance@OkHttpClient.class
static {
Internal.instance = new Internal() {
@Override public void addLenient(Headers.Builder builder, String line) {
builder.addLenient(line);
}
@Override public void addLenient(Headers.Builder builder, String name, String value) {
builder.addLenient(name, value);
}
@Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {
builder.setInternalCache(internalCache);
}
@Override public InternalCache internalCache(OkHttpClient client) {
return client.internalCache();
}
@Override public boolean connectionBecameIdle(
ConnectionPool pool, RealConnection connection) {
return pool.connectionBecameIdle(connection);
}
@Override public RealConnection get(
ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
return pool.get(address, streamAllocation);
}
@Override public void put(ConnectionPool pool, RealConnection connection) {
pool.put(connection);
}
@Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
return connectionPool.routeDatabase;
}
@Override
public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
((RealCall) call).enqueue(responseCallback, forWebSocket);
}
@Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
return ((RealCall) call).engine.streamAllocation;
}
@Override
public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
tlsConfiguration.apply(sslSocket, isFallback);
}
@Override public HttpUrl getHttpUrlChecked(String url)
throws MalformedURLException, UnknownHostException {
return HttpUrl.getChecked(url);
}
};
}
这一段代码用static修饰,表明在加载OkHttpClient类时就会对Internal.instance进行初始化操作。
internalCache() @OkHttpClient.class
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
cache域在我们构造OkHttpClient的时候是没有被初始化的,因此如果我们没有通过调用Builder的cache方法设置cache值的话,该方法返回的对象实际上是一个不支持任何缓存操作的对象,说着说该对象的所有方法为空。因此如果需要OkHttpClient支持缓存,需要我们写一个Cache对象并在构造OkHttpClient的时候将其传给OkHttpClient。
cache()@Builder.class@OkHttpClient.class
public Builder cache(Cache cache) {
this.cache = cache;
this.internalCache = null;
return this;
}
完成cache的初始化,如果不调用该方法那么OkHttpClient默认不提供Cache功能。对于Cahce更为详细的介绍在后面的章节我们会进行详细介绍。《OkHttp深入学习(三)——Cache》
connectionPool()@OkHttpClient.class
public ConnectionPool connectionPool() {
return connectionPool;
}
connectionPool的初始化是在构建OkHttpClient时创建的,调用的构造器为new ConnectionPool()。对于ConnectionPool更为详细的介绍在后面的章节我们会进行详细介绍。《
OkHttp深入学习(二)——网络》