<
OkHttp全面解析
>
上一篇

Java内存分配
下一篇

线程池深入学习

OkHttp全面解析

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() 方法开始:

newCall

这个方法会返回一个RealCall类型的对象,通过它将网络请求操作添加到请求队列

@Override
public Call newCall(Request request){
    return RealCall.newRealCall(this,request,false);
}

RealCall.enqueue

调用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

CacheInterceptor缓存拦截器

1.CacheInterceptor主要做以下几件事情:

  1. 根据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;
    
  2. 通过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{
        ...
    }
    
  3. 如果从服务器端成功获取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;
    }
    

2.通过Cache实现缓存功能

通过上面分析缓存拦截器的流程可以看出,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详解

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部分

  1. 向服务器端发送数据
  2. 从服务端获取相应数据并构建Response对象

OkHttp使用扩展

仔细看刚才CallServerInterceptor中的intercept方法,可以发现在向服务端发送数据以及获取数据都是使用一个Okio的框架来实现的。Okio是Square个数打造的另一个轻量级IO库,他是OkHttp框架的基石。

我们在构建Response时,需要调用body()方法传入一个ResponseBody对象。ResponseBody内部封装了对请求结果的流读取操作。我们可以通过继承并扩展ResponseBody的方式获取网络请求的进度。

  1. 继承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是一个自定义的进度监听器,通过它向上层汇报网络请求的进度。

    1. 自定义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的源码实现:

Top
Foot