介于自己的网络方面知识烂的一塌糊涂,所以准备写相关网络的文章,但是考虑全部写在一篇太长了,所以分开写,希望大家能仔细看,最好可以指出我的错误,让我也能纠正。
网络体系结构小结
相关网络知识点小结-TCP/UDP
相关网络知识点小结- http/https
相关网络知识点小结- socket/websocket/webservice
相关网络知识点小结- cookie/session/token(待写)
okhttp源码解析
Retrofit源码解析(待写)
这里提一个本文无关的小知识点,很多文章开头都会提到,我们以okhttp3.xxx版本来讲解,那怎么看当前最新的已经是几了呢?(主要以前也有人问过我在哪里查看xxx第三方库最新的版本,所以想到提一下这个)其实很简单,我们以okhttp为例:
首先我们来确定总体大纲:
所以我们大概就知道了okhttp一般的主要内容为这二大块。
讲解源码前,先写上okhttp基本使用,这样才更方便讲解源码:
String url = "http://www.baidu.com"; //'1. 生成OkHttpClient实例对象' OkHttpClient okHttpClient = new OkHttpClient(); //'2. 生成Request对象' Request request = new Request.Builder().url(url).build(); //'3. 生成Call对象' Call call = okHttpClient.newCall(request); //'4. 如果要执行同步请求:' try { call.execute(); } catch (IOException e) { e.printStackTrace(); } //'5. 如果要执行异步请求:' call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }); 复制代码
我们来看我们最刚开始的完整流程图:
然后配合上面第一步的okhttp基本使用,发现在执行同步和异步前,我们要先准备好 OkhttpClient
、 Request
、 Call
对象。我们一步步来看相关源码:
我们上面的代码实例化OkHttpClient对象的代码是:
OkHttpClient okHttpClient = new OkHttpClient(); 复制代码
我们进入查看:
发现OkHttpClient
除了空参数的构造函数,还有一个传入
Builder
的构造函数,而我们的
new OkHttpClient()
最终也是调用了传入
Builder
的构造函数,只不过传入默认的
Builder
对象值,如下图所示:
我们可以看到最后几个值:
...... connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; ...... 复制代码
默认的连接超时,读取超时,写入超时,都为10秒,然后还有其他等默认属性,那我们加入想要改变这些属性值呢,比如超时时间改为20秒,很简单。我们不使用默认的 Builder
对象即可:
OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.connectTimeout(20,TimeUnit.SECONDS); builder.readTimeout(20,TimeUnit.SECONDS); builder.writeTimeout(20,TimeUnit.SECONDS); OkHttpClient okHttpClient = builder.build(); //这里不能直接使用那个传入Builder对象的OkHttpClient的构造函数,因为该构造函数的方法不是public的 OkHttpClient okHttpClient = new OkHttpClient(builder);//这样是错误的 builder.build();的源码是: public OkHttpClient build() { return new OkHttpClient(this); } 复制代码
我们再回过头来看看OkHttpClient里面设置的属性值都有什么用:
Dispatch:分发器,后面会提到
Proxy:设置代理,通常为类型(http、socks)和套接字地址。参考文章: 直接使用Proxy创建连接
ProxySelector: 设置全局的代理,通过继承该类,设置具体代理的类型、地址和端口。参考文章: Java代理 通过ProxySelector设置全局代理
Protocol: 网络协议类,比如我们经常听到的http1.0、http1.1、http2.0协议等。
ConnectionSpec: 指定HTTP流量通过的套接字连接的配置。我们直接可以翻译该类头部的英文介绍,具体的内容原谅我也不是很懂:
Interceptor:拦截器,后面会提到
EventListener:指标事件的监听器。扩展此类以监视应用程序的HTTP调用的数量,大小和持续时间。所有start/connect/acquire事件最终都会收到匹配的end /release事件,要么成功(非null参数)要么失败(非null throwable)。每个事件对的第一个公共参数用于在并发或重复事件的情况下链接事件,例如dnsStart(call,domainName);和dnsEnd(call,domainName,inetAddressList); 我们可以看到一系列的xxxStart和xxxEnd方法:
CookieJar:向传出的HTTP请求添加cookie,收到的HTTP返回数据的cookie处理。
参考文章:okhttp3带cookie请求Cache:网络缓存,okhttp默认只能设置缓存GET请求,不缓存POST请求,毕竟POST请求很多都是交互的,缓存下来也没有什么意义。
我们看到Cache的构造函数,可以看到的是需要设置缓存文件夹,缓存的大小,还有一个是缓存内部的操作方式,因为缓存是需要写入文件的,默认操作使用的是Okio来操作。 参考文章:InternalCache:Okhttp内部缓存的接口,我们直接使用的时候不需要去实现这个接口,而是直接去使用上面的Cache类。
SocketFactory:从字面意思就看的出来,Android 自带的Socket的工厂类。
参考文章:类SocketFactory
SSLSocketFactory:Android自带的SSLSocket的工厂类。
参考文章:Java SSLSocket的使用
用SSLSocketFactory 连接https的地址CertificateChainCleaner:不是很了解,所以还是老样子,通过谷歌翻译,翻译该类的顶部备注说明:
HostnameVerifier:字面意思,Host name 验证,这个一个基础接口,而且只有一个方法:
/** * Verify that the host name is an acceptable match with * the server ‘s authentication scheme. * * @param hostname the host name * @param session SSLSession used on the connection to host * @return true if the host name is acceptable */ public boolean verify(String hostname, SSLSession session); 复制代码
我们查看Request代码:
public final class Request { final HttpUrl url; //网络请求路径 final String method; //get、post..... final Headers headers;//请求头 final @Nullable RequestBody body;//请求体 /** 你可以通过tags来同时取消多个请求。 当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。 之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。. */ final Map<Class<?>, Object> tags; ....... ....... ....... } 复制代码
这个估计很多人都清楚,如果对请求头请求体等不清楚的,可以看下以前我们这个系列的文章: Android技能树 — 网络小结(3)之HTTP/HTTPS
我们可以看到我们生成的Request实例,会传给OkHttpClient实例的newÇall方法:
Request request = new Request.Builder().url(url).build(); Call call = okHttpClient.newCall(request); call.execute();或者 call.enqueue(....); 复制代码
我们Request和OkHttpClient大致都了解过了,我们来具体看下newCall执行了什么和Call的具体内容。
Call类代码: @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } RealCall类代码: static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } 复制代码
我们可以看到,最后获取到的是RealCall的实例,同时把我们各种参数都配置好的OkHttpClient和Request都传入了。
所以后面 call.execute()/call.enqueue()
都是执行的RealCall的相对应的方法。但目前位置我们上面的图已经讲解好了,我这里再贴一次:
恭喜你,下次别人考你Okhttp前面的相关参数配置方面的代码你已经都理解了。
我们继续看我们的流程图下面的内容:
我们先来讲同步执行:
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); try { //'1. 执行了dispatcher的executed方法' client.dispatcher().executed(this); //'2. 调用了getResponseWithInterceptorChain方法' Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { //'3. 最后一定会执行dispatcher的finished方法' client.dispatcher().finished(this); } } 复制代码
我们一步步来具体看,第一步看Dispatcher类中的executed方法了:
/** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); } 复制代码
可以看到把我们的RealCall加入到了一个同步线程 runningSyncCalls
中,然后中间调用了 getResponseWithInterceptorChain
方法*(这个第二个操作我们会放在后面很具体的讲解),我们既然加入到了一个同步线程中,肯定用完了要移除,然后第三步finished方法会做处理:
/** Used by {@code Call#execute} to signal completion. */ void finished(RealCall call) { finished(runningSyncCalls, call, false); } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { //'if语句里面我们可以看到这里把我们的队列中移除了call对象' if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } 复制代码
我们先来看 RealCall
里面的 enqueue
代码:
@Override public void enqueue(Callback responseCallback) { //'1. 这里有个同步锁的抛异常操作' synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); //'2. 调用Dispatcher里面的enqueue方法' client.dispatcher().enqueue(new AsyncCall(responseCallback)); } 复制代码
我们一步步来看,第一个同步锁抛异常的操作,我们知道一个Call应对一个网络请求,加入你这么写是错误的:
Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {} @Override public void onResponse(Call call, Response response) throws IOException {} }); //'同一个call对象再次发起请求' call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {} @Override public void onResponse(Call call, Response response) throws IOException {} }); 复制代码
同一个Call对象,同时请求了二次。这时候就会进入我们的同步锁判断,只要一个执行过了,里面 executed会为true
,也就会抛出异常。
我们再来看第二步操作:
我们知道异步请求,肯定会代表很多请求都在各自的线程中去执行,那么我们在不看OkHttp源码前,让你去实现,你怎么实现,是不是第一个反应是使用线程池。
Java/Android线程池框架的结构主要包括3个部分 1.任务:包括被执行任务需要实现的接口类:Runnable 或 Callable 2.任务的执行器:包括任务执行机制的核心接口类Executor,以及继承自Executor的EexcutorService接口。 3.执行器的创建者,工厂类Executors 复制代码
具体可以参考: Android 线程池框架、Executor、ThreadPoolExecutor详解
client.dispatcher().enqueue(new AsyncCall(responseCallback));
,不再是像同步操作一样,直接把RealCall传入,而是传入一个 AsyncCall
对象。没错,按照我们上面提到的线程池架构,任务是使用 Runnable 或 Callable接口
,我们查看AsyncCall的代码:
final class AsyncCall extends NamedRunnable { ...... ...... } public abstract class NamedRunnable implements Runnable { ....... ....... } 复制代码
果然如我们预计,是使用了Runnable接口。
client.dispatcher().enqueue(new AsyncCall(responseCallback));
,不再是像同步操作一样,直接把RealCall传入,而是传入一个 AsyncCall
对象。
调用Dispatcher里面的enqueue方法:
synchronized void enqueue(AsyncCall call) { //'1. 判断当前异步队列里面的数量是否小于最大值,当前请求数是否小于最大值' if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //'2. 如果没有大于最大值,则将call加入到异步请求队列中' runningAsyncCalls.add(call); //'3. 并且运行call的任务' executorService().execute(call); } else { readyAsyncCalls.add(call); } } 复制代码
我么直接看第三步,按照我们上面提到过的Java/Android线程池框架的结构主要包括3个部分,可以看到执行我们的Runnable对象的,说明他是一个任务执行器,也就是Executor的继承类。说明executorService()返回了一个Executor的实现类,我们点进去查看:
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; } 复制代码
果然创建一个可缓存线程池,线程池的最大长度无限制,但如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
那我们知道是线程池执行了Runnable的任务,那我们只要具体看我们的okhttp的Runnable到底执行了什么即可:
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } ...... ...... ...... @Override protected void execute() { boolean signalledCallback = false; try { //'1. 我们可以发现最后线程池执行的任务就是getResponseWithInterceptorChain方法' 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) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { //'2. 最后再从Dispatcher里面的异步队列中移除' client.dispatcher().finished(this); } } } 复制代码
我们发现不管是异步还是同步,都是一样的三部曲:1.加入到Dispatcher里面的同步(或异步)队列,2.执行getResponseWithInterceptorChain方法,3.从Dispatcher里面的同步(或异步)队列移除。(只不过同步操作是直接运行了getResponseWithInterceptorChain方法,而异步是通过线程池执行Runnable再去执行getResponseWithInterceptorChain方法)
我们在前面已经知道了不管是异步请求还是同步请求,都会去执行 Dispatcher
的 getResponseWithInterceptorChain
操作:
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. //'1. 创建一个拦截器List' List<Interceptor> interceptors = new ArrayList<>(); //'2. 添加用户自己创建的应用拦截器' interceptors.addAll(client.interceptors()); //'3. 添加重试与重定向拦截器' interceptors.add(retryAndFollowUpInterceptor); //'4. 添加内容拦截器' interceptors.add(new BridgeInterceptor(client.cookieJar())); //'4. 添加缓存拦截器' interceptors.add(new CacheInterceptor(client.internalCache())); /'5. 添加连接拦截器' interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { //'6. 添加用户自己创建的网络拦截器' interceptors.addAll(client.networkInterceptors()); } //'7. 添加请求服务拦截器' interceptors.add(new CallServerInterceptor(forWebSocket)); //'8.把这些拦截器们一起封装在一个拦截器链条上面(RealInterceptorChain)' Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); //'9.然后执行链条的proceed方法' return chain.proceed(originalRequest); } 复制代码
我们先不管具体的拦截器的功能,我们先来看整体的执行方式,所以我们直接来看拦截器链条的工作模式:
public final class RealInterceptorChain implements Interceptor.Chain { //'我们刚才建立的放拦截器的队列' private final List<Interceptor> interceptors; //'当前执行的第几个拦截器序号' private final int index; ...... ...... ...... public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); ...... ...... ...... //'实例化了一个新的RealInterceptorChain对象,并且传入相同的拦截器List,只不过传入的index值+1' RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); //'获取当前index对应的拦截器里面的具体的某个拦截器, Interceptor interceptor = interceptors.get(index); //然后执行拦截器的intercept方法,同时传入新的RealInterceptorChain对象(主要的区别在于index+1了)' Response response = interceptor.intercept(next); ...... ...... ...... return response; } } 复制代码
我们可以看到在RealInterceptorChain类的proceed的方法里面,又去实例化了一个RealInterceptorChain类。很多人可能看着比较绕,没关系,我们举个例子简单说下就可以了:
Interceptor只负责处理自己的业务功能,比如我们这里是填充地址和手机号码,然后自己的任务结束就会调用拦截器链条,执行链条接下去的任务,其他跟Interceptor无关。
我们来看我们的拦截器和拦截器链条:
最后我们可以看到我们的result结果为:
这里额外提下:里面的拦截器里面的二个大步骤是可以交换顺序的,我先执行拦截链的方法,让它提前去执行下一个拦截器的操作,再拿相应的返回值做我这个拦截器的操作。比如还是刚才那个电话拦截器,我们调换了二个的顺序:
这样就会去先执行地址拦截器,然后拿到结果后再去处理电话拦截器的逻辑,所以最后的输出结果为:
这里我们懂了以后,我们再去看Okhttp前面提到的拦截器添加,拦截链的相关代码,是不是简单的一比,它的链接链的操作跟我们的基本架构一致,然后各自的拦截器无非就是处理各自的逻辑,对参数进行更改,发起请求等。所以我们的核心变成了OkHttp的各个拦截器到底做了什么逻辑。 (也就是我们提到的拦截器中的二个大操作的其中一步,自己的处理逻辑。)
看名字就知道这个拦截器的作用是重试和重定向的。
大家可以参考本文:
OKhttp源码解析---拦截器之RetryAndFollowUpInterceptor
我们来看 BridgeInterceptor
类的说明备注:
什么?看不懂英文,谷歌翻译走起:
简单来说,我们自己在Okhttp里面建立了一个Request请求对象,但是这个对象并不是直接就可以用来马上发送网络请求的,毕竟我们刚开始实例化Request的时候就简单的放入了Url,body等,很多参数都是没有设置的,所以我们还需要补充很多参数,然后发起网络请求,然后网络返回的参数,我们再把它封装成Okhttp可以直接使用的对象。
一句话概括: 将客户端构建的Request对象信息构建成真正的网络请求;然后发起网络请求,最后就是将服务器返回的消息封装成一个Response对象
参考文章:
OkHttp之BridgeInterceptor简单分析
缓存拦截器,简单来说就是有缓存就使用缓存。
参考文章:
Okhttp之CacheInterceptor简单分析
连接拦截器,顾名思义打开了与服务器的链接,正式开启了网络请求。
因为以前在文章: Android技能树 — 网络小结(4)之socket/websocket/webservice 提到过,我们的请求是通过Socket去访问的。
所以最终这个ConnectInterceptor也会去发起一个Socket连接请求。
参考文章:
OkHttp之ConnectInterceptor简单分析
我们曾经在文章 Android技能树 — 网络小结(2)之TCP/UDP 提过:
TCP要先建立通道,然后再发送数据。
上面的拦截器ConnectInterceptor已经帮我们把通道建立好了,所以在这个CallServerInterceptor拦截器里面,我们的任务就是发送相关的数据,
参考文章:
Okhttp之CallServerInterceptor简单分析
我们在流程图中看到了,除了OKHttp源码里面自带的拦截器,还有二种自定义拦截器,应用拦截器和网络拦截器。
使用代码:
okHttpClient = new OkHttpClient .Builder() .addInterceptor(appInterceptor)//Application拦截器 .addNetworkInterceptor(networkInterceptor)//Network拦截器 .build(); 复制代码
我们知道网络请求中间一定要经过一系列的拦截器,我们也可以自己写拦截器,然后对里面的参数做处理,比如我们对Request在拦截器中做某个写参数变更,然后再交给下一个拦截器。
而这二个自定义拦截器的位置,在我们前面分析获取拦截链的方法getResponseWithInterceptorChain中就提过了,现在再拿出来重新说一遍:
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); '最先添加用户的自定义APPlication拦截器' interceptors.addAll(client.interceptors()); '然后是一系列的Okhttp自带的拦截器' interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); '在最终跟服务器交互数据的CallServerInterceptor前,添加用户自定义的NetWork拦截器' '因为如果放在最后就没什么意义了。' if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); } 复制代码
参考文章:
OkHttp基本使用(五)自定义拦截器
OkHttp源码写的也比较仓促,特别后面的各个拦截器的源码分析就偷懒了,因为不然会引出一大段一大段的内容,就直接引用其他大佬的文章。如果哪里不对,欢迎大家指出。