OkHttp是一套处理HTTP网络请求的依赖库。对于Android app来说,OkHttp现在几乎已经占据了所有的网络请求操作,Retrofit + OkHttp实现网络请求似乎成了一种标配。
首先看下OkHttp的基本使用 :
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url()
.build();
client.newCall(request).enqueue(new Callback) {
@Override
public void onFailure(Call call,IOException e){}
@Override
public void onResponse(Call call,Response response) throw IOException{}
};
除了直接new OkHttpClient之外,还可以使用内部工厂类Builder来设置OkHttpClient。
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(60,TimeUnit.SECONDS)//设置超时
.addInterceptor(interceptor)//添加拦截器
.proxy(proxy)//设置请求代理
.cache(cache);//设置策略缓存
OkHttpClient client = builder.build();
请求操作的起点从 OkHttpClient.newCall().enqueue()
方法开始:
这个方法会返回一个RealCall类型的对象,通过它将网络请求操作添加到请求队列中
@Override
public Call newCall(Request request){
return RealCall.newRealCall(this,request,false);
}
调用Dispatch的入队方法,执行一个异步网络请求的操作。
@Override
public void enqueue(Callback responseCallback){
...
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到,最终请求操作是委托给Dispatcher的enqueue方法内实现的。
Dispatcher是OkHttpClient的调度器 ,是一种门户模式。主要用来实现执行,取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在Dispatcher内部根据一定的策略,保证最大并发个数,同一host主机允许执行请求的线程个数等。
Dispatcher的enqueue方法的具体实现如下:
sync void enqueue(AsyncCall call){
if(runningAsyncCalls.size < maxRequest &&
runningCallsForHost(call) < maxRequestPerHost){
runningAsyncCalls.add(call);
executorService().execute(call);
}else{
readAsyncCalls.add(call);
}
}
实际上就是使用线程池执行了一个AsyncCall,而AsyncCall实现了Runnable接口,因此整个操作会在一个子线程中执行。
继续查看AsyncCall中的run方法:
final class AsyncCall extend NameRunnable {
@Override
public final void run(){
...
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) {
...
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
在run方法中执行了另一个execute方法,而真正获取请求结果的方法是在 getResponseWithInterceptorChain
方法中,从名字可以看出其内部是一个拦截器的调用链 ,具体如下:
在添加上述几个拦截器之前,会调用 client.interceptors
将开发人员设置的拦截器添加到列表当中。
对于Request的Head以及TCP链接,我们能控制修改的成分不多。所以主要说下CacheInterceptor和CallServerInterceptor 。
根据Request获取当前已有缓存的Response(有可能为null),并根据获取到的缓存Response,创建CacheStrategy对象。
//如果开发人员配置了自定义的缓存,优先从自定义缓存中读取Response。
Response cacheCandidate = cache != null
? cache.get(chain.request()) : null;
//创建缓存策略,通过它来判断某缓存是否有效
CacheStrategy strategy = new CacheStrategy.Factpry(now,chain.request(),cacheCandidate).get();
Request networkRequest = strategy.networkRequest();
Response cacheResponse = strategy.cacheResponse;
通过CacheStrategy判断当前缓存中的Response是否有效(比如是否过期),如果缓存Response可用则直接返回,否则调用chain.proceed()继续执行下一个拦截器,也就是发送网络请求从服务器获取远端Response。
//如果缓存无效,且禁止使用网络请求,直接返回空Response
if(networkRequest == null && cacheResponse == null){
return new Response.Builder()
...
.code(504)
.message("Unsatisfiable Request(only-if cached)")
...
.build();
}
//缓存在有效期内,则将缓存中的Response返回
if(networkRequest == null){
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.builder();
}
//最后如没缓存,或缓存失败,发网络请求获服务器Response
Response networkResponse = null;
try{
//执行下一个拦截器,发起网络请求
networkResponse = chain.proceed(networkRequest);
}finally{
...
}
如果从服务器端成功获取Response,再判断是否将此Response继续缓存
//通过网络请求获取最新Response数据
Response response = networkResponse.newBuilder()
.cacheResposne(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果开发人员有设置自定义cache,则将最新的数据缓存起来
if(cache != null){
if(HttpHeaders.hasBody(response)&&
CacheStrategy.isCacheable(response,networkRequest)){
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest,response);
}
//将response返回(有可能来自缓存,或来自网络请求)
return response;
}
通过上面分析缓存拦截器的流程可以看出,OkHttp只是规范了一套缓存策略,但具体使用何种方式将数据缓存到本地,以及如何从本地缓存中取出数据,都是由我们自定义实现,并通过OkHttpClient.Builder的cache方法设置。
OkHttp提供了一个默认的缓存类Cache.java,我们可以在构建OkHttpClient时,直接使用Cache来实现缓存功能。只需要指定缓存路径,以及最大可用空间即可,如下:
builder.cache(new Cache(getCacheDir(), 20 * 1024*1024));
OkHttpClient client = builder.build();
上述代码使用Android app内置cache目录作为缓存路径,并设置缓存可用最大空间为20M.实际上在Cache内部使用了DiskLruCach来实现具体的缓存功能。
DiskLruCach最终会以journal类型文件将需要缓存的数据保存到本地。如果感觉OkHttp自带的这套缓存策略太过复杂,我们可以设置使用DiskLruCach自己实现缓存机制。
CallServerInterceptor是OkHttp中最后一个拦截器,也是OkHttp中最核心的网路请求部分,其intercept方法如下:
@Override
public Response intercept(Chain chain) throws IOException{
//获取HttpCodec
RealInterceptorChain realChain = (RealInterceptorChain)chain;
HttpCodec httpCodec = realChain.httpStream();
//将请求头发送到服务端
realChain.exentListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadsEnd(realChain.call().request);
//如果有Body,则将Body也发送给服务端
Response.Builder responseBuilder = null;
...
//结束请求
httpCodec.finishRequest();
//发送请求数据
-------
-------
//获取请求结果
//读取响应头部数据
if(responseBuilder == null){
realChain.eventListener().responseHeadersStart(realChain.call(),request);
responseBuilder = httpCodec.readResponseHeaders(false);
}
//构建响应body
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMills)
.receivedResponseAtMillis(System.currentTimeMills())
.build();
...
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
return response;
}
主要分为2部分
仔细看刚才CallServerInterceptor中的intercept方法,可以发现在向服务端发送数据以及获取数据都是使用一个Okio的框架来实现的。Okio是Square个数打造的另一个轻量级IO库,他是OkHttp框架的基石。
我们在构建Response时,需要调用body()方法传入一个ResponseBody对象。ResponseBody内部封装了对请求结果的流读取操作。我们可以通过继承并扩展ResponseBody的方式获取网络请求的进度。
继承ResponseBody
private static class ProcessResponseBody extends ResponseBody{
private final ResponseBody responseBody;
private final ProcessListener processListener;
private BufferedSource bufferSource;
public ProcessResponseBody(ResponseBody ..., ProcessListener ...){
//构造函数
}
@Override
public BufferedSource source(){
if(bufferSource == null){
bufferSource=Okio.buffer(source(responseBody.source()));
}
return bufferSource;
}
private Source source(Source source){
return new ForwardingSource(source){
long totalBytesRead = 0L;
@Override
public long read(Buffer sink,long byteCount) throw IOException{
long bytesRead = super.read(sink,byteCount);
totalByteRead += byteRead != -1 ?byteRad : 0;
if(processListener != null){
processListener.update((100*totalByteRead) / responseBody.contentLength());
}
return byteRead;
}
}
}
}
其中processListener是一个自定义的进度监听器,通过它向上层汇报网络请求的进度。
自定义ProcessBarClient
private OkHttpClient client;
public static sync OkHttpClient getClient(){
if(client == null){
final File cacheDir = getExternalCacheDir();
client = new OkHttpClient.Builder()
.cache(new Cache(new File(...),60*1024*1024))
.build();
}
return client
}
private static OkHttpClient getProcessBarClient(final ProcessBarListener l){
return getClient().newBuilder().addNetworkInterceptor(new Interceptor(){
@Override
public Response intercept(Chain chain){
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProcessResponseBody(originalResponse.body().l))
.build();
}
}).build();
}
getClient可以根据项目的不同添加其他共同设置,比如timeout时间,DNS,Log日志interceptor等
getProcessBarClient通过添加一个拦截器,并且在intercept方法中将自定义的ProcessResponseBody传给body方法。
当通过getProcessBarClient发送网络请求时,OkHttpClient从服务端获取到数据之后,会不断调用ProcessResponseBody中的source方法,然后通过ProcessBarListener向上层通知请求进度的结果。
主要分析了OkHttp的源码实现: