
由square公司贡献的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架,从Android4.4开始HttpURLConnection的底层实现采用的是okhttp。
项目地址:https://github.com/square/okhttp
须知,本文是基于okhttp3.14.9版本来进行分析,如果版本不一样,可以部分流程会与本文不一致,但是大体思想都是一样的。
val client = OkHttpClient.Builder().cache(Cache(File("/xxx"),111))//配置缓存
//...可以在初始化的时候配置一些相关的信息
.eventListener(object :EventListener(){
override fun callStart(call: Call) {
super.callStart(call)
}
})//一些回调的listener
.cookieJar(object :CookieJar{
override fun saveFromResponse(p0: HttpUrl, p1: MutableList) {
TODO("Not yet implemented")
}
override fun loadForRequest(p0: HttpUrl): MutableList {
TODO("Not yet implemented")
}
})
.proxy(Proxy.NO_PROXY)//不适应代理,优先级高于proxySelector
.proxySelector(object :ProxySelector(){
override fun select(uri: URI?): MutableList {
TODO("Not yet implemented")
}
override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
TODO("Not yet implemented")
}
})
.build()
val request = Request.Builder().url("https://www.baidu.com").get().build()//不传的话,默认是get请求
val call = client.newCall(request)
val result = call.execute()
调用流程
okhttp请求过程中最少需要解除okhttpclient、request、call、response,但是框架内部进行大量的逻辑处理。
所有的逻辑大部分集中在拦截器中,但是进入拦截器之前还需要依靠分发器来调配请求任务
分发器:内部维护队列与线程池、完成请求调配
拦截器:完成整个请求过程
okhttp创建时相关的源码,笔者就不说了,挺简单的,主要就是一个建造者模式,我们从okhttp的同步请求和异步请求来入手分析:
call的execute方法代表了同步请求,而enqueue方法代表了异步请求。两者唯一的区别在于一个会直接发起请求,而一个是使用okhttp内置的线程池来进行,这就会涉及到okhttp的任务分发器。
我们通过client.newCall(request)创建的call对象时,实际上时创建了realcall对象,call只是一个接口,而realcall是其实现类。我们先来看下enqueue方法(异步请求),里面的代码很简单,this.transmitter.callStart();这句其实会回调到我们在创建okhttpclient时配置的listener的回调方法,我们最应该重视的是最后一行代码,enqueue方法最后是调用了dispatcher分发器的enqueue方法。我们来看下dispatcher类,该类有3个执行队列以及线程池参数需要注意。
//异步请求同时存在的最大请求 private int maxRequests = 64; //异步请求同一域名同时存在的最大请求 private int maxRequestsPerHost = 5; @Nullable //闲置任务(没有请求时可执行一些任务,有开发者自己定义) private Runnable idleCallback; @Nullable //异步请求使用的线程池 private ExecutorService executorService; //异步请求等待执行队列 private final DequereadyAsyncCalls = new ArrayDeque(); //异步请求正在执行队列 private final Deque runningAsyncCalls = new ArrayDeque(); //同步请求正在执行队列 private final Deque runningSyncCalls = new ArrayDeque();
dispatcher类的enqueue方法有两处比较重要的代码,分别是findExistingCallWithHost方法和promoteAndExecute方法
private boolean promoteAndExecute() {
assert !Thread.holdsLock(this);
List executableCalls = new ArrayList();
boolean isRunning;
AsyncCall asyncCall;
synchronized(this) {
Iterator i = this.readyAsyncCalls.iterator();
//当正在执行的任务未超过最大限制64,同时 runningcal1sForriost(call)<maxRequestsPerHost 同-Host的请求不超过5个,则会添加到正在执行队列,同时提交给线程池,否则先加入等待队列。
//加入线程池直接执行没啥好说的,但是如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完-个请求后,会根据是否成功都会调用分发器的 finished方法,详见executeOn方法
while(true) {
if (i.hasNext()) {
asyncCall = (AsyncCall)i.next();
if (this.runningAsyncCalls.size() < this.maxRequests) {
if (asyncCall.callsPerHost().get() < this.maxRequestsPerHost) {
i.remove();
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
this.runningAsyncCalls.add(asyncCall);
}
continue;
}
}
isRunning = this.runningCallsCount() > 0;
break;
}
}
int i = 0;
for(int size = executableCalls.size(); i < size; ++i) {
asyncCall = (AsyncCall)executableCalls.get(i);
//重点
asyncCall.executeOn(this.executorService());
}
return isRunning;
}
void executeOn(ExecutorService executorService) {
assert !Thread.holdsLock(RealCall.this.client.dispatcher());
boolean success = false;
try {
executorService.execute(this);//最终会走到下面的execute方法
success = true;
} catch (RejectedExecutionException var8) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(var8);
RealCall.this.transmitter.noMoreExchanges(ioException);
this.responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
RealCall.this.client.dispatcher().finished(this);
}
}
}
protected void execute() {
boolean signalledCallback = false;
RealCall.this.transmitter.timeoutEnter();
try {
//getResponseWithInterceptorChain这个方法十分重要,无论是同步请求还是异步请求,response都是使用这个方法获取的,下面我们在分析这里面究竟做了什么事
Response response =
RealCall.this.getResponseWithInterceptorChain();
signalledCallback = true;
this.responseCallback.onResponse(RealCall.this, response);
} catch (IOException var8) {
if (signalledCallback) {
Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var8);
} else {
this.responseCallback.onFailure(RealCall.this, var8);
}
} catch (Throwable var9) {
RealCall.this.cancel();
if (!signalledCallback) {
IOException canceledException = new IOException("canceled due to " + var9);
canceledException.addSuppressed(var9);
this.responseCallback.onFailure(RealCall.this, canceledException);
}
throw var9;
} finally {
RealCall.this.client.dispatcher().finished(this);
}
}
//异步请求结束时调用
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
this.finished(this.runningAsyncCalls, call);
}
//同步请求结束时调用
void finished(RealCall call) {
this.finished(this.runningSyncCalls, call);
}
//将请求结束的call移除出正在执行的队列
private void finished(Deque calls, T call) {
Runnable idleCallback;
synchronized(this) {
if (!calls.remove(call)) {
throw new AssertionError("Call wasn't in-flight!");
}
idleCallback = this.idleCallback;
}
boolean isRunning = this.promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
异步请求的分析到此结束,下面我们来看下同步请求。
public Response execute() throws IOException {
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
this.transmitter.timeoutEnter();
this.transmitter.callStart();
Response var1;
try {
this.client.dispatcher().executed(this);
var1 = this.getResponseWithInterceptorChain();
} finally {
this.client.dispatcher().finished(this);
}
return var1;
}
synchronized void executed(RealCall call) {
this.runningSyncCalls.add(call);
}
同步请求最后也是进入到了dispatcher的executed方法,该方法只是将该请求放入runningSyncCalls队列中,之后调用了getResponseWithInterceptorChain方法获取response,十分简单,没什么好说的。
分发器前面我们提过,分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池来执行。那分发器中默认的线程池是如何定义的呢?为什么要这么定义?
public synchronized ExecutorService executorService() {
if (this.executorService == null) {
this.executorService = new ThreadPoolExecutor(
0, //核心线程数
2147483647, //最大线程数
60L, //空闲线程闲置时间
TimeUnit.SECONDS, //闲置时间单位
new SynchronousQueue(), //线程等待队列
Util.threadFactory("OkHttp Dispatcher", false)//线程创建工厂
);
}
return this.executorService;
}
在OkHttp的分发器中的线程池定义如上,其实就和 Executors.newCachedThreadPool()创建的线程一样。首先核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。而最大线程 Integer.MAX_VALUE 与等待队列 SynchronousQueue 的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。一般来说,等待队列 B1ockingQueue 有:ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue。
那么上面列举的三个等待队列有什么不同呢?
假设向线程池提交任务时,核心线程都被占用的情况下:
okhttp中请求会被交给责任链中的一个个拦截器。默认情况下有五大拦截器:
顾名思义,该拦截器的功能就是重试与重定向。第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求。
重试请求阶段发生了 RouteException 或者 IOException会进行判断是否重新发起请求。
RouteException
catch (RouteException e) {
//todo 路由异常,连接未成功,请求还没发出去
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
}
IOException
catch (IOException e) {
//todo 请求发出去了,但是和服务器通信失败了。(socket流正在读写数据的时候断开连接)
// ConnectionShutdownException只对HTTP2存在。假定它就是false
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
}
两个异常都是根据recover 方法判断是否能够进行重试,如果返回true,则表示允许重试。
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
//todo 1、在配置OkhttpClient是设置了不允许重试(默认允许),则一旦发生请求失败就不再重试
if (!client.retryOnConnectionFailure()) return false;
//todo 2、由于requestSendStarted只在http2的io异常中为true,先不管http2
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)
return false;
//todo 3、判断是不是属于重试的异常
if (!isRecoverable(e, requestSendStarted)) return false;
//todo 4、有没有可以用来连接的路由路线
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
所以首先使用者在不禁止重试的前提下,如果出现了某些异常,并且存在更多的路由线路,则会尝试换条线路进行请求的重试。其中某些异常是在isRecoverable中进行判断:
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 出现协议异常,不能重试
if (e instanceof ProtocolException) {
return false;
}
// requestSendStarted认为它一直为false(不管http2),异常属于socket超时异常,直接判定可以重试
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted;
}
// SSL握手异常中,证书出现问题,不能重试
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// SSL握手未授权异常 不能重试
if (e instanceof SSLPeerUnverifiedException) {
return false;
}
return true;
}
1、协议异常,如果是那么直接判定不能重试;(你的请求或者服务器的响应本身就存在问题,没有按照http协议来定义数据,再重试也没用)
2、超时异常,可能由于网络波动造成了Socket管道的超时,那有什么理由不重试?(后续还会涉及到路由)
3、SSL证书异常/SSL验证失败异常,前者是证书验证失败,后者可能就是压根就没证书,或者证书数据不正确,那还怎么重试
经过了异常的判定之后,如果仍然允许进行重试,就会再检查当前有没有可用路由路线来进行连接。简单来说,比如 DNS 对域名解析后可能会返回多个 IP,在一个IP失败后,尝试另一个IP进行重试。
重定向如果请求结束后没有发生异常并不代表当前获得的响应就是最终需要交给用户的,还需要进一步来判断是否需要重定向的判断。重定向的判断位于followUpRequest方法,该方法主要是根据不同的响应码,赋予不同的操作。
如果此方法返回空,那就表示不需要再重定向了,直接返回响应;但是如果返回非空,那就要重新请求返回的 Request,但是需要注意的是,
我们的 followup 在拦截器中定义的最大次数为20次。
总结
BridgeInterceptor,连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,比如设置请求内容长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作。这个拦截器相对比较简单,详细代码可查看BridgeInterceptor类的intercept方法。
补全请求头:
| 请求头 | 说明 |
|---|---|
| Content-Type | 请求体类型,如:application/x-www-form-urlencoded |
| Content-Length/Transfer-Encoding | 请求体解析方式 |
| Host | 请求的主机站点 |
| Connection: Keep-Alive | 保持长连接 |
| Accept-Encoding: gzip | 接受响应支持gzip压缩 |
| Cookie | cookie身份辨别 |
| User-Agent | 请求的用户信息,如:操作系统、浏览器等 |
在补全了请求头后交给下一个拦截器处理,得到响应后,主要干两件事情:
1、保存cookie,在下次请求则会读取对应的数据设置进入请求头,默认的CookieJar不提供实现,需要在构建okhttpclient时,手动配置。
2、如果使用gzip返回的数据,则使用GzipSource包装便于解析。
总结
桥接拦截器的执行逻辑主要就是以下几点
对用户构建的Request进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的Request
将符合网络请求规范的Request交给下一个拦截器处理,并获取Response
如果响应体经过了GZIP压缩,那就需要解压,再构建成用户可用的Response并返回。
CacheInterceptor,在发出请求前,判断是否命中缓存。如果命中则可以不请求,直接使用缓存的响应。(只会存在Get请求的缓存)
步骤为:
| networkRequest | cacheResponse | 说明 |
|---|---|---|
| Null | Not Null | 直接使用缓存 |
| Not Null | Null | 向服务器发起请求 |
| Null | Null | 直接gg, okhttp直接返回504 |
| Not Null | Not Null | 发起请求,若得到响应为304(无修改),则更新缓存响应并返回 |
在缓存拦截器中判断是否可以使用缓存还是请求服务器都是通过CacheStrategy判断。
Http的缓存我们可以按照行为将他们分为:强缓存和协商缓存 l
缓存策略
首先需要认识几个请求头和响应头
| 响应头 | 说明 | 例子 |
|---|---|---|
| Date | 消息发送的时间 | Date: Sat, 18 Nov 2028 06:17:41 GMT |
| Expires | 资源过期的时间 | Expires: Sat, 18 Nov 2028 06:17:41 GMT |
| Last-Modified | 资源最后修改时间 | Last-Modified: Fri, 22 Jul 2016 02:57:17 GMT |
| ETag | 资源在服务器的唯一标识 | ETag:"16df0-5383097a03d40’’ |
| Age | 服务器用缓存响应请求,该缓存从产生到现在经过多长时间(秒) | Age: 3825683 |
| Cache-Control | - | - |
| 请求头 | 说明 | 例子 |
|---|---|---|
| If-Modified-Since | 服务器没有在指定的时间后修改请求对应资源,返回304(无修改) | If-Modified Since: Fri, 22 Jul 2016 02:57:17 GMT |
| If-None-Match | 服务器将其与请求对应资源的 Etag 值进行比较,匹配返回304 | If-None-Match: “16df0-5383097a03d40” |
| Cache-Control | - | - |
其中 Cache-control可以在请求头存在,也能在响应头存在,对应的value可以设置多种组合:
假设存在max-age=100,min-fresh=20。这代表了用户认为这个缓存的响应,从服务器创建响应 到 能够缓存使用的时间为100-20=80s。但是如果max-stale=100。这代表了缓存有效时间80s过后,仍然允许使用
100s,可以看成缓存有效时长为180s。
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = this.cache != null ? this.cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
CacheStrategy strategy = (new Factory(now, chain.request(), cacheCandidate)).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (this.cache != null) {
this.cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
Util.closeQuietly(cacheCandidate.body());
}
if (networkRequest == null && cacheResponse == null) {
return (new 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();
} else if (networkRequest == null) {
return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();
} else {
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
if (networkResponse == null && cacheCandidate != null) {
Util.closeQuietly(cacheCandidate.body());
}
}
Response response;
if (cacheResponse != null) {
if (networkResponse.code() == 304) {
response = cacheResponse.newBuilder().headers(combine(cacheResponse.headers(), networkResponse.headers())).sentRequestAtMillis(networkResponse.sentRequestAtMillis()).receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();
networkResponse.body().close();
this.cache.trackConditionalCacheHit();
this.cache.update(cacheResponse, response);
return response;
}
Util.closeQuietly(cacheResponse.body());
}
response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();
if (this.cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
CacheRequest cacheRequest = this.cache.put(response);
return this.cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
this.cache.remove(networkRequest);
} catch (IOException var13) {
}
}
}
return response;
}
}
缓存拦截器大体上是根据一些响应码和以及上面的一些请求头、响应头做缓存策略。具体逻辑大家可以看下CacheStrategy类,这里就不一一说明。
总结
该拦截器的作用是:打开与目标服务器的连接,并执行下一个拦截器,它的源码比较简单,虽然代码量比较少,但是大部分都是封装到别的类里面去,这里只是调用而已。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package okhttp3.internal.connection;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Interceptor.Chain;
import okhttp3.internal.http.RealInterceptorChain;
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain)chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
}
这个拦截器中比较重要的是realconnection类——封装了一个socket连接池。如何复用socket连接呢?关键在于一个isEligible方法。
boolean isEligible(Address address, @Nullable Listroutes) { if (this.transmitters.size() < this.allocationLimit && !this.noNewExchanges) { if (!Internal.instance.equalsNonHost(this.route.address(), address)) { return false; } else if (address.url().host().equals(this.route().address().url().host())) { return true; } else if (this.http2Connection == null) { return false; } else if (routes != null && this.routeMatchesAny(routes)) { if (address.hostnameVerifier() != OkHostnameVerifier.INSTANCE) { return false; } else if (!this.supportsUrl(address.url())) { return false; } else { try { address.certificatePinner().check(address.url().host(), this.handshake().peerCertificates()); return true; } catch (SSLPeerUnverifiedException var4) { return false; } } } else { return false; } } else { return false; } }
这段代码大体的意思是:
总结
这个拦截器中所有的实现都是为了获得一份与目标拦截器的连接,获取对应的socket流,在获得结果之后不进行额外的处理。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package okhttp3.internal.http;
import java.io.IOException;
import java.net.ProtocolException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Interceptor.Chain;
import okhttp3.Response.Builder;
import okhttp3.internal.Util;
import okhttp3.internal.connection.Exchange;
import okio.BufferedSink;
import okio.Okio;
public final class CallServerInterceptor implements Interceptor {
private final boolean forWebSocket;
public CallServerInterceptor(boolean forWebSocket) {
this.forWebSocket = forWebSocket;
}
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain)chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//flushRequest,真正发生数据给服务器
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
BufferedSink bufferedRequestBody;
if (request.body().isDuplex()) {
exchange.flushRequest();
bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder.request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
int code = response.code();
if (code == 100) {
response = exchange.readResponseHeaders(false).request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
code = response.code();
}
exchange.responseHeadersEnd(response);
if (this.forWebSocket && code == 101) {
response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
} else {
response = response.newBuilder().body(exchange.openResponseBody(response)).build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0L) {
throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
} else {
return response;
}
}
}
整个拦截器就是完成http协议报文的封装以及解析,以及判断是否关闭http连接。
总结
真正与服务器进行通信,向服务器发送数据,解析读取的响应数据。
在使用OkHttp时,如果用户在创建 OkHttpClient 时,配置了 proxy 或者 proxyselector,则会使用配置的代理,并且 proxy 优先级高于 proxyselector。而如果末配置,则会获取机器配置的代理并使用。
因此,如果我们不需要自己的App中的请求走代理,则可以配貴一个 proxy (Proxy .NO_ PROxY),这样也可以避免被抓包。
okhttp中一共有三种类型的代理:
public enum Type {
DIRECT,//无代理
HTTP,//http代理
SOCKS//socks代理
};
对于Socks代理,在HTTP的场景下,代理服务器完成TCP数据包的转发工作;而Http代理服务器,在转发数据之外,还会解析HTTP的请求及响应,并根据请求及响应的内容做一些处理。
设置了SOCKS代理的情况下,创建Socket时,为其传入proxy,写代码时连接时还是以HTTP服务器为目标地址(实际上Socket肯定是与SOCKS代理服务器连);但是如果设置的是Http代理,创建的Socket是与Http代理服务器建立连接。
设置代理时,Http服务器的域名解析会被交给代理服务器执行。但是如果是设買了Http代理,会对Http代理服务器的域名使用 OkhttpClient 配置的dns解析代理服务器,Http服务器的域名解析被交给代理服务器解析。
Http代理也分成两种类型:普通代理与隧道代理,