OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。
对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。
适用于 Android 和 Java 应用程序的 HTTP 和 HTTP/2 客户端。OkHttp的4.0.x版本已经全部由java替换到了Kotlin,API的一些使用也会有些不同。
支持的版本
4.0.x :Android 5.0+(API 级别 21+)和 Java 8+。
3.12.x :Android 2.3+(API 级别 9+)和 Java 7+。平台可能不支持 TLSv1.2。(2021-12-31不再支持)
OkHttp有一个库的依赖Okio,用于高性能I/O一个小型library。它适用于 Okio 1.x(用 Java 实现)或 Okio 2.x(升级到 Kotlin)。
本文使用的OkHttp的版本为3.14.2,不是不会接入高版本,主要是4.0.x版本已经全部由java替换到了Kotlin,Kotlin不太熟怕理解错了,误导人民群众。
dependencies{//本文使用implementation'com.squareup.okio:okio:1.15.0'implementation'com.squareup.okhttp3:okhttp:3.14.2'//高版本// define a BOM and its versionimplementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))// define any required OkHttp artifacts without versionimplementation("com.squareup.okhttp3:okhttp")implementation("com.squareup.okhttp3:logging-interceptor")}
OkHttp 经过几次迭代后,已经发生了很多变化。更好的 WebSocket 支持、更多的 Interceptor 责任链,甚至连最核心的 HttpEngine 也变成了 HttpCodec。本文会重新梳理整个网络请求的流程,以及实现机制。
先看下 OkHttp 的基本使用:
publicvoidgetHttp(Stringurl){//创建OkHttpClient对象OkHttpClientclient=newOkHttpClient();//创建RequestRequestrequest=newRequest.Builder().url(url).build();//创建Call对象client.newCall(request)//通过execute()方法获得请求响应的Response对象client.newCall(request).enqueue(newCallback(){@OverridepublicvoidonFailure(Callcall,IOExceptione){}@OverridepublicvoidonResponse(Callcall,Responseresponse)throwsIOException{if(response.isSuccessful()){Stringresult=response.body().string();//处理UI需要切换到UI线程处理}}});}
除了直接 new OkHttpClient 之外,还可以使用内部工厂类 Builder 来设置 OkHttpClient。如下所示:
publicvoidbuildHttp(Stringurl){OkHttpClient.Builderbuilder=newOkHttpClient.Builder();builder.connectTimeout(15,TimeUnit.SECONDS)//设置超时.addInterceptor(interceptor)//拦截器.proxy(proxy)//设置代理.cache(cache);//设置缓存Requestrequest=newRequest.Builder().url(url).build();builder.build().newCall(request).enqueue(newCallback(){@OverridepublicvoidonFailure(Callcall,IOExceptione){}@OverridepublicvoidonResponse(Callcall,Responseresponse)throwsIOException{}});}
请求操作的起点从 OkHttpClient.newCall().enqueue() 方法开始:
@OverridepublicCallnewCall(Requestrequest){returnRealCall.newRealCall(this,request,false/* for web socket */);}RealCall.newRealCall.javastaticRealCallnewRealCall(OkHttpClientclient,RequestoriginalRequest,booleanforWebSocket){// Safely publish the Call instance to the EventListener.RealCallcall=newRealCall(client,originalRequest,forWebSocket);call.transmitter=newTransmitter(client,call);returncall;}
这个方法会返回一个 RealCall 对象,通过它将网络请求操作添加到请求队列中。
@Overridepublicvoidenqueue(CallbackresponseCallback){synchronized(this){if(executed)thrownewIllegalStateException("Already Executed");executed=true;}transmitter.callStart();client.dispatcher().enqueue(newAsyncCall(responseCallback));}
client.dispatcher()返回Dispatcher,调用 Dispatcher 的 enqueue 方法,执行一个异步网络请求的操作。
Dispatcher 是 OkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。
voidenqueue(AsyncCallcall){synchronized(this){readyAsyncCalls.add(call);if(!call.get().forWebSocket){AsyncCallexistingCall=findExistingCallWithHost(call.host());if(existingCall!=null)call.reuseCallsPerHostFrom(existingCall);}}promoteAndExecute();}
实际上就是使用线程池执行了一个 AsyncCall,而 AsyncCall 继承了 NamedRunnable,NamedRunnable 实现了 Runnable 接口,因此整个操作会在一个子线程(非 UI 线程)中执行。
/** * Runnable implementation which always sets its thread name. */publicabstractclassNamedRunnableimplementsRunnable{protectedfinalStringname;publicNamedRunnable(Stringformat,Object...args){this.name=Util.format(format,args);}@Overridepublicfinalvoidrun(){StringoldName=Thread.currentThread().getName();Thread.currentThread().setName(name);try{execute();}finally{Thread.currentThread().setName(oldName);}}protectedabstractvoidexecute();}
在 run 方法中执行了 一个抽象方法 execute 这个抽象方法被 AsyncCall 实现。
@Overrideprotectedvoidexecute(){booleansignalledCallback=false;transmitter.timeoutEnter();try{Responseresponse=getResponseWithInterceptorChain();signalledCallback=true;responseCallback.onResponse(RealCall.this,response);}catch(IOExceptione){if(signalledCallback){// Do not signal the callback twice!Platform.get().log(INFO,"Callback failure for "+toLoggableString(),e);}else{responseCallback.onFailure(RealCall.this,e);}}finally{client.dispatcher().finished(this);}}
从上面看出而真正获取请求结果的方法是在 getResponseWithInterceptorChain 方法中,从名字也能看出其内部是一个拦截器的调用链。
ResponsegetResponseWithInterceptorChain()throwsIOException{// Build a full stack of interceptors.Listinterceptors=newArrayList<>();interceptors.addAll(client.interceptors());interceptors.add(newRetryAndFollowUpInterceptor(client));interceptors.add(newBridgeInterceptor(client.cookieJar()));interceptors.add(newCacheInterceptor(client.internalCache()));interceptors.add(newConnectInterceptor(client));if(!forWebSocket){interceptors.addAll(client.networkInterceptors());}interceptors.add(newCallServerInterceptor(forWebSocket));Interceptor.Chainchain=newRealInterceptorChain(interceptors,transmitter,null,0,originalRequest,this,client.connectTimeoutMillis(),client.readTimeoutMillis(),client.writeTimeoutMillis());booleancalledNoMoreExchanges=false;try{Responseresponse=chain.proceed(originalRequest);if(transmitter.isCanceled()){closeQuietly(response);thrownewIOException("Canceled");}returnresponse;}catch(IOExceptione){calledNoMoreExchanges=true;throwtransmitter.noMoreExchanges(e);}finally{if(!calledNoMoreExchanges){transmitter.noMoreExchanges(null);}}}
Interceptor:拦截器是一种强大的机制,可以监视、重写和重试调用。
每一个拦截器的作用如下:
BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。
CacheInterceptor:负责 HTTP 请求的缓存处理。
ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接。
CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。
RetryAndFollowUpInterceptor:此拦截器从故障中恢复,并根据需要执行重定向。如果呼叫被取消,它可能会引发IOException。
在添加上述几个拦截器之前,会调用 client.interceptors 将开发人员设置的拦截器添加到列表当中。
对于 Request 的 Head 以及 TCP 链接,我们能控制修改的成分不是很多。所以咱们了解 CacheInterceptor 和 CallServerInterceptor。
CacheInterceptor 主要做以下几件事情: a. 根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response,创建 CacheStrategy 对象。
@OverridepublicResponseintercept(Chainchain)throwsIOException{//根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 ResponseResponsecacheCandidate=cache!=null?cache.get(chain.request()):null;//获取当前时间longnow=System.currentTimeMillis();//创建 CacheStrategy 对象//通过 CacheStrategy 来判断缓存是否有效CacheStrategystrategy=newCacheStrategy.Factory(now,chain.request(),cacheCandidate).get();RequestnetworkRequest=strategy.networkRequest;ResponsecacheResponse=strategy.cacheResponse;if(cache!=null){cache.trackResponse(strategy);}if(cacheCandidate!=null&&cacheResponse==null){closeQuietly(cacheCandidate.body());// The cache candidate wasn't applicable. Close it.}//如果我们被禁止使用网络,并且缓存不足,则失败。返回空相应(Util.EMPTY_RESPONSE)if(networkRequest==null&&cacheResponse==null){returnnewResponse.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(Util.EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(System.currentTimeMillis()).build();}// 如果缓存有效,缓存 Response 可用则直接返回if(networkRequest==null){returncacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();}//没有缓存或者缓存失败,则发送网络请求从服务器获取ResponseResponsenetworkResponse=null;try{//执行下一个拦截器,networkRequest//发起网络请求networkResponse=chain.proceed(networkRequest);}finally{//如果我们在I/O或其他方面崩溃,请不要泄漏cache body。if(networkResponse==null&&cacheCandidate!=null){closeQuietly(cacheCandidate.body());}}。。。//通过网络获取最新的ResponseResponseresponse=networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();//如果开发人员有设置自定义cache,则将最新response缓存if(cache!=null){if(HttpHeaders.hasBody(response)&&CacheStrategy.isCacheable(response,networkRequest)){// Offer this request to the cache.CacheRequestcacheRequest=cache.put(response);returncacheWritingResponse(cacheRequest,response);}//返回response(缓存或网络)returnresponse;}
通过上面缓存拦截器的流程可以看出,OkHttp 只是规范了一套缓存策略,但是具体使用何种方式将数据缓存到本地,以及如何从本地缓存中取出数据,都是由开发人员自己定义并实现,并通过 OkHttpClient.Builder 的 cache 方法设置。
OkHttp 提供了一个默认的缓存类 Cache.java,我们可以在构建 OkHttpClient 时,直接使用 Cache 来实现缓存功能。只需要指定缓存的路径,以及最大可用空间即可,如下所示:
OkHttpClient.Builderbuilder=newOkHttpClient.Builder();builder.connectTimeout(15,TimeUnit.SECONDS)//设置超时拦截器.addInterceptor(newInterceptor(){@OverridepublicResponseintercept(Chainchain)throwsIOException{returnnull;}})//设置代理.proxy(newProxy(Proxy.Type.HTTP,null))//设置缓存//AppGlobalUtils.getApplication() 通过反射得到Application实例//getCacheDir内置 cache 目录作为缓存路径//maxSize 10*1024*1024 设置最大缓存10MB.cache(newCache(AppGlobalUtils.getApplication().getCacheDir(),10*1024*1024));
Cache 内部使用了 DiskLruCach 来实现具体的缓存功能,如下所示:
/** * Create a cache of at most {@code maxSize} bytes in {@code directory}. */publicCache(Filedirectory,longmaxSize){this(directory,maxSize,FileSystem.SYSTEM);}Cache(Filedirectory,longmaxSize,FileSystemfileSystem){this.cache=DiskLruCache.create(fileSystem,directory,VERSION,ENTRY_COUNT,maxSize);}
DiskLruCache 最终会将需要缓存的数据保存在本地。如果感觉 OkHttp 自带的这套缓存策略太过复杂,我们可以设置使用 DiskLruCache 自己实现缓存机制。
LRU:是近期最少使用的算法(缓存淘汰算法),它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。
CallServerInterceptor 是 OkHttp 中最后一个拦截器,也是 OkHttp 中最核心的网路请求部分。
@OverridepublicResponseintercept(Chainchain)throwsIOException{//获取RealInterceptorChainRealInterceptorChainrealChain=(RealInterceptorChain)chain;//获取ExchangeExchangeexchange=realChain.exchange();Requestrequest=realChain.request();longsentRequestMillis=System.currentTimeMillis();exchange.writeRequestHeaders(request);booleanresponseHeadersStarted=false;Response.BuilderresponseBuilder=null;if(HttpMethod.permitsRequestBody(request.method())&&request.body()!=null){if("100-continue".equalsIgnoreCase(request.header("Expect"))){exchange.flushRequest();responseHeadersStarted=true;exchange.responseHeadersStart();responseBuilder=exchange.readResponseHeaders(true);}if(responseBuilder==null){if(request.body().isDuplex()){exchange.flushRequest();BufferedSinkbufferedRequestBody=Okio.buffer(exchange.createRequestBody(request,true));request.body().writeTo(bufferedRequestBody);}else{// Write the request body if the "Expect: 100-continue" expectation was met.BufferedSinkbufferedRequestBody=Okio.buffer(exchange.createRequestBody(request,false));request.body().writeTo(bufferedRequestBody);bufferedRequestBody.close();}}else{exchange.noRequestBody();if(!exchange.connection().isMultiplexed()){// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection// from being reused. Otherwise we're still obligated to transmit the request body to// leave the connection in a consistent state.exchange.noNewExchangesOnConnection();}}}else{exchange.noRequestBody();}if(request.body()==null||!request.body().isDuplex()){exchange.finishRequest();}上面是向服务器端发送请求数据-----强大的分割线----------下面是从服务端获取相应数据并构建Response对象if(!responseHeadersStarted){exchange.responseHeadersStart();}if(responseBuilder==null){responseBuilder=exchange.readResponseHeaders(false);}Responseresponse=responseBuilder.request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();intcode=response.code();if(code==100){// server sent a 100-continue even though we did not request one.// try again to read the actual responseresponse=exchange.readResponseHeaders(false).request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();code=response.code();}exchange.responseHeadersEnd(response);if(forWebSocket&&code==101){// Connection is upgrading, but we need to ensure interceptors see a non-null response body.response=response.newBuilder().body(Util.EMPTY_RESPONSE).build();}else{response=response.newBuilder().body(exchange.openResponseBody(response)).build();}。。。returnresponse;}
1、OkHttp 内部是一个门户模式,所有的下发工作都是通过一个门户 Dispatcher 来进行分发。
2、网络请求阶段通过责任链模式,链式的调用各个拦截器的 intercept 方法。本文重点介绍了 2 个比较重要的拦截器:CacheInterceptor(请求缓存) 和 CallServerInterceptor(执行网络请求)。
下一篇:【MySQL】调控 字符集