分析原因
最近被问到okhttp 在性能上面 和 HttpUrlConnection ,volley 等框架有什么优势,回答不上来,其实之前看过 okhttp源码,一知半解,也没有做记录,现在知道后悔了
OkHttp 使用方式
OkHttp官网地址:http://square.github.io/okhttp/
OkHttp GitHub地址:https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:3.14.2'
需要在清单文件声明访问Internet的权限,如果使用缓存,那还得声明写外存的权限
异步Get请求
/**
* 异步GET请求:
* new OkHttpClient;
* 构造Request对象;
* 通过前两步中的对象构建Call对象;
* 通过Call#enqueue(Callback)方法来提交异步请求;
*/
private void asyncGetRequests() {
String url = "https://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//默认就是GET请求
.build();
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 {
String result = response.body().string();
}
});
}
这一连串的接口调用,看看使用了多少设计模式
1.Builder 模式
获取一个自定义的 OkHttpClient 对象
OkHttpClient okHttpClient=new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
OkHttpClient 有两个构造方法
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
......
}
第一个 是 public 的,外部可以使用
第二个 只能相同包名的类才可以使用
不管使用哪个构造方法,都是通过builder 进行初始化
看看OkHttpClient.Builder 的构造函数
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
......
writeTimeout = 10_000;
pingInterval = 0;
}
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
......
this.writeTimeout = okHttpClient.writeTimeout;
this.pingInterval = okHttpClient.pingInterval;
}
第一个 自己对所有配置进行初始化
第二个 通过 另外一个OkHttpClient 对自己进行初始化
看看OkHttpClient.Builder.build 方法
public OkHttpClient build() {
return new OkHttpClient(this);
}
实际上就是 调用了 OkHttpClient的第二个构造方法
OkHttpClient初始化的流程就是 这样
2.Builder 模式 使用的原因
Builder 模式的优势在哪
我们初始化一个类,并初始化他的配置,可以有几种方式
(1)直接写一个大的构造方法,所有参数都传进去
OkHttpClient(Dispatcher dispatcher,Proxy proxy,......,int writeTimeout) {
this.dispatcher = dispatcher;
this.proxy = proxy;
......
}
这种方式的缺点是 代码可读性,和可维护性 并不强
(2) 另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参的构造方法来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数
OkHttpClient okHttpClient=new OkHttpClient();
okHttpClient.setDispatcher(dispatcher);
okHttpClient.setProxy(proxy);
......
okHttpClient.setPingInterval(30, TimeUnit.SECONDS);
不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法被分割成了多次调用,所以在构造过程中 JavaBean 可能处于不一致的状态。也就是 线程安全问题。
(3)builder 设计模式
把初始化 参数的任务合成为一步,方便做线程安全操作,
同时自定义配置参数很方便,可变性高,
按照同一约定,维护代码成本小
这个不是我总结出来的,是《effctive-java》这本书里面看到的
3.工厂 模式
OkHttpClient和Request 的实例化都是通过builder模式
Call 的实例化则是通过 工厂模式
Call call= okHttpClient.newCall(request);
public interface Call extends Cloneable {
......
interface Factory {
Call newCall(Request request);
}
......
}
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
......
}
可以看到 okHttpClient 同时也是 实现了Call.Factory 接口的一个工厂类,生产标准的 Call 对象用于处理请求
优点是 :可以生产出 相同标准的产品
缺点是 :每扩展一个产品就需要 新建一个产品类,产品复杂多样的情况下,代码难以维护
4.模板 模式
不管是同步请求还是 异步请求,都是调用 Call这个对象 的接口
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
......
}
Call 只是一个接口文件,具体的实现类为RealCall
不管具体的实现方式是什么,OkHttp都会给你这几个接口,request,execute,enqueue等
调用接口时也不用管 具体的实现类到底是谁,这就是模板设计模式的好处
5.责任链 模式
来看看 同步请求的具体实现方法
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
dispatcher就不分析了,就是直接把任务加到 线程池执行队列里面,注意,是队列数据结构
Response result = getResponseWithInterceptorChain();
来看看getResponseWithInterceptorChain 这个方法做了什么
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> 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());
}
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 的所有的一堆 拦截器,不管是 默认的还是自定义添加的(要注意拦截器的顺序),都放到 Interceptor.Chain 这个数据结构中,并调用了 chain.proceed() 方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
......
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
......
return response;
}
最主要的就是这段代码,从第一层开始分析调用 顺序中的第一个拦截器,把index递增之后的RealInterceptorChain对象传给它,如果没有设置自定义的拦截器,第一个就是RetryAndFollowUpInterceptor,看看它的intercept 方法
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
......
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
......
}
其实就是 调用了 刚才传过来的RealInterceptorChain对象的proceed方法,只是index自增了,再往下分析,其实就是调用下一个 拦截器的intercept 方法,这样就是一层一层的向下调用了
时序图如下:
所以自定义拦截器的时候 要注意一点,必须要调用chain.proceed 这个方法,不然下层的拦截器调用不到了,也就没有网络请求,毕竟具体的网络请求在接下来的拦截器里面 实现
总结来说,okhttp的拦截器实现 ,就是一个有上下文 的责任链模式
转载:https://blog.csdn.net/XAVI_2010/article/details/104423147