Android知识总结
OkHttp介绍
OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。
优点:
- 支持 Http1、Http2、Quic 以及WebSocket;
- 连接池复用底层TCP(Socket),减少请求延时;
- 无缝的支持GZIP减少数据流量;
- 缓存响应数据减少重复的网络请求;
- 请求失败自动重试主机的其他 ip,自动重定向。
一、okhttp请求过程
1、GET请求
同步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
异步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
2、POST请求
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
二、Request、Response类详解
- 1、Request、Response分别抽象成请求和响应。
- 2、其中Request包括Headers和RequestBody,而RequestBody是abstract类,他的子类是有FormBody (表单提交的)和 MultipartBody(文件上传),分别对应了两种不同的MIME类型
FormBody :"application/x-www-form-urlencoded"
MultipartBody:"multipart/"+xxx. - 3、其中Response包括Headers和ResponseBody,而ResponseBody是abstract类,所以他的子类也是有两个:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。
- 4、由于RFC协议规定,所以所有的头部信息不是随便写的,request的header与response的header的标准都不同。OKHttp的封装类Request和Response为了应用程序编程方便,会把一些常用的Header信息专门提取出来,作为局部变量。比如contentType,contentLength,code,message,cacheControl,tag...它们其实都是以name-value对的形势,存储在网络请求的头部信息中。
三、使用流程
在使用OkHttp发起一次请求时,对于使用者最少存在
OkHttpClient
、Request
与 Call
三个角色。其中OkHttpClient 和 Request 的创建可以使用它为我们提供的 Builder (建造者模式)。而 Call 则是把 Request 交给 OkHttpClient 之后返回的一个已准备好执行的请求。
建造者模式
:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。实例化OKHttpClient和Request的时候,因为有太多的属性需要设置,而且开发者的需求组合千变万化,使用建造者模式可以让用户不需要关心这个类的内部细节,配置好后,建造者会帮助我们按部就班的初始化表示对象
同时OkHttp在设计时采用的门面模式
,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端OkHttpClient统一暴露出来。
OkHttpClient 中全是一些配置,比如代理的配置、ssl证书的配置等。而 Call 本身是一个接口,我们获得的实现为: 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;
}
Call 的 execute 代表了同步请求,而 enqueue 则代表异步请求。两者唯一区别在于一个会直接发起网络请求,而另一个使用OkHttp内置的线程池来进行。这就涉及到OkHttp的任务分发器。
四、Dispatcher 分发器
在OKHttp中,它使用Dispatcher
作为任务的调度器 ,分发器就是来调配请求任务的来维护请求的状态,内部会包含一个线程池。可以在创建 OkHttpClient 时,传递我们自己定义的线程池来创建分发器,用于执行请求。
这个Dispatcher中的成员有:
// 最大并发请求数为64
private int maxRequests = 64;
//每个主机最大请求数为5
private int maxRequestsPerHost = 5;
//闲置任务(没有请求时可执行一些任务,由使用者设置)
private @Nullable Runnable idleCallback;
//异步请求使用的线程池
private @Nullable ExecutorService executorService;
//异步请求等待执行队列
private final Deque readyAsyncCalls = new ArrayDeque<>();
//异步请求正在执行队列
private final Deque runningAsyncCalls = new ArrayDeque<>();
//同步请求正在执行队列
private final Deque runningSyncCalls = new ArrayDeque<>();
五、执行请求
1、OkHttpClient的newCall方法里面new了RealCall的对象,但是RealCall的构造函数需要传入一个OKHttpClient对象和Request对象(PS:第三个参数false表示不是webSokcet).因此RealCall包装了Request对象。所以RealCall可以很方便地使用这两个对象。
2、RealCall里面的两个关键方法是:execute 和 enqueue。分别用于同步和异步得执行网络请求。
2.1、同步请求
在RealCall#execute
里面执行同步请求:
@Override
public Response execute() throws IOException {
//首先出现一个同步代码块,对当前对象加锁,通过一个标志位executed判断该对象的execute
//方法是否已经执行过,如果执行过就抛出异常;这也就是同一个Call只能执行一次的原因
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//这个是用来捕获okhttp的请求堆栈信息
captureCallStackTrace();
try {
//调用Dispatcher的executed方法,将请求放入分发器
client.dispatcher().executed(this);
//通过拦截器连获取返回结果Response
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
//调用dispatcher的finished方法,回收同步请求
client.dispatcher().finished(this);
}
}
然后调用Dispatcher#execute
方法,把call同步同步请求加入到runningSyncCalls
。
/**
此方法也为同步方法
直接加入到运行中同步请求队列中
*/
synchronized void executed(RealCall call) {
//加入到同步运行中队列
runningSyncCalls.add(call);
}
2.2、异步请求
首先也是加同步,判断这个Call是否执行过;然后实例化了一个AsyncCall,最后调用分发器的enqueue方法。
执行RealCall#enqueue
的方法
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//实例化一个线程AsyncCall交给分发器,由分发器中的线程池执行这个线程
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
执行Dispatcher#enqueue
异步请求:
/**
此方法为同步方法,因为runningAsyncCalls和readyAsyncCalls使用的ArrayDeque,然而ArrayDeque是非线程安全的,所以需要同步。
如果运行中的异步请求队列的请求数小于最大请求数且当前请求对应的host下对应的请求数小于maxRequestsPerHost,那么就进队列,并且通过线程池立即执行。
*/
synchronized void enqueue(AsyncCall call) {
// 运行队列中的请求数小于maxRequests且相同host的运行中请求数小于maxRequestsPerHost,下面会贴runningCallsForHost()的代码
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 加入到运行中队列
runningAsyncCalls.add(call);
// 使用线程池执行请求,下面会贴出executorService初始化的过程。
executorService().execute(call);
} else {
// 加入就绪队列中
readyAsyncCalls.add(call);
}
}
/*获取同一hostde的最大请求数 */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) result++;
}
return result;
}
由上可见runningAsyncCalls.size() < maxRequests
判断条件主要是解决client的压力,runningCallsForHost(call) < maxRequestsPerHost
的判断条件主要是解决service的压力。
通过Dispatcher#executorService
来创建一个ThreadPoolExecutor
,然后把call加到线程池里面:
/**创建线程池,并保证线程安全,无等待并且保证高并发和最大吞吐量
* 第一个 corePoolSize 一直存在于线程池中的线程数量(除非allowCoreThreadTimeOut为true),
* 当是0,说明该线程池没有核心线程,所有线程都是工作线程,即所有线程超过一定空闲时间会被回收
* 第二个参数是Integer.MAX_VALUE,即最大线程数,虽然设置值这么大,但是无须担心性能消耗过大问题,因为有队列去维护请求数
* 第三个参数是60,即工作线程空闲60s后就会被回收
* 第四个参数 unit 上面那时间的单位
* 第五个参数 workQueue 通过execute方法发送的任务,会先被缓存在这个队列中
* 第六个参数threadFactory 创建线程的工厂
*/
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
然后通过threadPool来执行execute
方法来执行call任务。我们知道执行任务是执行里面的run方法,但是我们看RealCall#AsyncCall
里面只有一个execute
方法。然后我们再看父类NamedRunnable
里面有run方法,execute
方法也是在里面封装的。代码见下:
//异步请求线程
final class AsyncCall extends NamedRunnable {
// 构建OKHTTP 请求的 Callback
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
//主机名
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
//当分发器的线程池执行该对象时,该方法被调用
@Override
protected void execute() {
//保证onFailure只被回调一次
boolean signalledCallback = false;
try {
//通过拦截器获取返回结果
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
//如果请求被取消,回调onFailure
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
// 正常情况,调用onResponse
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
// 如果上面回调过,这里就不再进行回调,保证onFailure只会被调用一次
if (signalledCallback) {
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//通知分发器请求结束,从队列中移除该请求
client.dispatcher().finished(this);
}
}
}
NamedRunnable实现了Runnable接口,从这里可以看出AsyncCall确实是在子线程执行网络请求
/**
* Runnable implementation which always sets its thread name.
*/
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
//设置当前线程的名字
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
//执行任务
protected abstract void execute();
}
RealCall#AsyncCall#execute
还有一个重要方法是getResponseWithInterceptorChain
添加拦截器,通过拦截器可以将一个流式工作分解为可配置的分段流程,既增加了灵活性也实现了解耦,关键还可以自有配置,非常完美。
//依次执行拦截器链中的拦截器获取结果
Response getResponseWithInterceptorChain() throws IOException {
List interceptors = new ArrayList<>();
//添加自定义拦截器
interceptors.addAll(client.interceptors());
//添加重试和重定向拦截器
interceptors.add(retryAndFollowUpInterceptor);
//添加桥接拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//添加缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//添加链接拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加网络拦截器
interceptors.addAll(client.networkInterceptors());
}
//添加连接服务器拦截器,主要负责将我们的Http请求写进网络的IO流中
interceptors.add(new CallServerInterceptor(forWebSocket));
//构建拦截器链依次执行每一个拦截器
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//发起请求
return chain.proceed(originalRequest);
}
加入线程池直接执行没啥好说的了,但是如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完一个请求后,都会调用分发器的 Dispatcher#finished
方法。
//异步请求调用
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
//同步请求调用
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private void finished(Deque calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//不管异步还是同步,执行完后都要从队列移除(runningSyncCalls/runningAsyncCalls)
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();
}
}
然后我们看Dispatcher#promoteCalls
方法,里面把readyAsyncCalls
里面的任务添加到runningAsyncCalls
里面并交给线程池执行。
/**
根据maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来调整
runningAsyncCalls(运行中的异步请求)队列和readyAsyncCalls(就绪状态的异步请求)队列。
使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。
*/
private void promoteCalls() {
//运行中的异步请求队列的请求数大于最大请求数,那么就没必要去将就绪状态的请求移动到运行中。
//其实就是说,如果有超过最大请求数的请求正在运行,是不需要将其移出队列的,继续运行完即可。
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
//如果就绪的队列为空,那就更没有必要移动了,因为都没有。
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//遍历就绪队列
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
//取出一个请求
AsyncCall call = i.next();
//如果当前请求对应的host下,没有超过maxRequestsPerHost,那么将其从就绪队列中移除,并加入在运行队列。
if (runningCallsForHost(call) < maxRequestsPerHost) {
//移除
i.remove();
//加入运行队列
runningAsyncCalls.add(call);
//立即执行该请求
executorService().execute(call);
}
//如果运行队列已经到达了最大请求数上限,如果还有就绪中的请求,直接退出。
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
2.3、分发器线程池
前面我们提过,分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池来执行。那分发器中默认的线程池是如何定义的呢?为什么要这么定义?
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(
0, //核心线程
Integer.MAX_VALUE, //最大线程
60, //空闲线程闲置时间
TimeUnit.SECONDS, //闲置时间单位
new SynchronousQueue(), //线程等待队列
Util.threadFactory("OkHttp Dispatcher", false) //线程创建工厂
);
}
return executorService;
}
在OkHttp的分发器中的线程池定义如上,其实就和Executors.newCachedThreadPool()
创建的线程一样。首先核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。而最大线程 Integer.MAX_VALUE
与等待队列 SynchronousQueue
的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。一般来说,等待队列 BlockingQueue
有: ArrayBlockingQueue
、LinkedBlockingQueue
与 SynchronousQueue
。
假设向线程池提交任务时,核心线程都被占用的情况下:
-
ArrayBlockingQueue
:基于数组的阻塞队列,初始化需要指定固定大小。
当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。 -
LinkedBlockingQueue
:基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。
当指定大小后,行为就和 ArrayBlockingQueu 一致。而如果未指定大小,则会使用默认的 Integer.MAX_VALUE 作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。 -
SynchronousQueue
: 无容量的队列。
使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合 Integer.MAX_VALUE 就实现了真正的无等待。
但是需要注意的时,我们都知道,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个数。那么当设置最大线程数为 Integer.MAX_VALUE 时,OkHttp同时还有最大请求任务执行个数: 64的限制。这样即解决了这个问题同时也能获得最大吞吐。
2.4、取消任务
public synchronized void cancelAll() {
//取消异步请求等待执行队列
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
//取消异步请求正在执行队列
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
//取消同步请求正在执行队列
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
@Override
public void cancel() {
//调用重定向拦截器的取消方法
retryAndFollowUpInterceptor.cancel();
}
六、拦截器流程
而OkHttp中的 getResponseWithInterceptorChain() 中经历的流程为
请求会被交给责任链中的一个个拦截器。默认情况下有五大拦截器:
- RetryAndFollowUpInterceptor
第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
- BridgeInterceptor
补全请求,并对响应进行额外处理
- CacheInterceptor
请求前查询缓存,获得响应并判断是否需要缓存
- ConnectInterceptor
与服务器完成TCP连接
- CallServerInterceptor
与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)*
七、OKHTTP类详解
-
大体核心类主要下图:
核心类.png -
最后给大家看一下整体的流程图
流程.png
推荐两篇文章
https://blog.csdn.net/qq_30993595/article/details/87475028
https://www.jianshu.com/p/6166d28983a2