OkHttp框架解析
一:概述
本章内容着重点在于简单封装GET与POST请求的工具类,并介绍OkHttpClient框架相关重要组件源码。
二:常用工具类简单封装
2.1 GET请求
public static String getTest() throws IOException {
// 创建客户端
OkHttpClient client = new OkHttpClient();
// 创建请求Request
Request request = new Request.Builder().url("").build();
// 执行请求
Response response = client.newCall(request).execute();
return null;
}
2.2 Post请求
Post请求相对于Get请求多了RequestBody部分操作
public static String postTest() throws IOException {
// 创建客户端
OkHttpClient client = new OkHttpClient.Builder().build();
// 创建请求体
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"这是传输的内容");
// 创建请求
Request request = new Request.Builder().url("").post(body).build();
// 执行异步请求
Response response = client.newCall(request).execute();
return response.body().string();
}
三:请求客户端OkHttpClient
3.1重点摘要
OkHttpClient应该共享
,每个客户端客户端都维护有连接池,线程池,复用可以减少创建消耗以及线程浪费
3.2实例创建
请求客户端OkHttpClient拥有巨多属性,为满足个性化请求设定,提供无参构造默认值
以及建造者模式构建个性化赋值
- 无参构造:实例化静态内部类构建器,通过构件实例赋值客户属性
- 构造者:静态内部类构建器提供各属性赋值方法构建个性化构建器后赋值客户端并通过方法构建()返回客户端实例
public OkHttpClient() {
this(new OkHttpClient.Builder());
}
public Builder() {
this.dispatcher = new Dispatcher();
this.protocols = OkHttpClient.DEFAULT_PROTOCOLS;
this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS;
this.eventListenerFactory = EventListener.factory(EventListener.NONE);
this.proxySelector = ProxySelector.getDefault();
this.cookieJar = CookieJar.NO_COOKIES;
this.socketFactory = SocketFactory.getDefault();
this.hostnameVerifier = OkHostnameVerifier.INSTANCE;
this.certificatePinner = CertificatePinner.DEFAULT;
this.proxyAuthenticator = Authenticator.NONE;
this.authenticator = Authenticator.NONE;
this.connectionPool = new ConnectionPool();
this.dns = Dns.SYSTEM;
this.followSslRedirects = true;
this.followRedirects = true;
this.retryOnConnectionFailure = true;
this.connectTimeout = 10000;
this.readTimeout = 10000;
this.writeTimeout = 10000;
this.pingInterval = 0;
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
// 无参构造默认值方式实例化client对象
OkHttpClient client1 = new OkHttpClient();
// 建造者模式定制个性化请求方案
OkHttpClient client2 = new OkHttpClient.Builder() // 初始化内部类Builder
.readTimeout(500, TimeUnit.MILLISECONDS) // 设置读取超时时间
.connectTimeout(200, TimeUnit.MILLISECONDS) // 设置连接超时时间
.build(); // build返回OkHttpClient实例对象
四:请求对象RealCall
4.1 实例创建
请求对象RealCall访问权限为同包下,需要通过OkHttpClient方法newCall()创建
final class RealCall implements Call {
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
4.2 同步执行
executed为boolean属性值,请求对象RealCall被执行后该属性都会被修改为true,即一个请求对象只能被执行一次
public Response execute() throws IOException {
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
this.captureCallStackTrace();
this.eventListener.callStart(this);
Response var2;
try {
// dispatcher调度器保存这个请求对象
this.client.dispatcher().executed(this);
Response result = this.getResponseWithInterceptorChain();
if (result == null) {
throw new IOException("Canceled");
}
var2 = result;
} catch (IOException var7) {
this.eventListener.callFailed(this, var7);
throw var7;
} finally {
this.client.dispatcher().finished(this);
}
return var2;
}
4.3 异步执行
异步执行与同步执行相同的是一个请求对象也只能被执行一次,不同点在于调度器保存执行的是AsyncCall,该类是Runnable子类
public void enqueue(Callback responseCallback) {
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
this.captureCallStackTrace();
this.eventListener.callStart(this);
// 调度器保存执行的是AsyncCall
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
onFailure()请求失败时回调的方法、onResponse()请求成功时回调的方法
public static void postTest() throws IOException {
// 创建客户端
OkHttpClient client = new OkHttpClient.Builder().build();
// 创建请求体
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"这是传输的内容");
// 创建请求
Request request = new Request.Builder().url("http://locallhost:8080/testPost").post(body).tag("12").build();
// 执行异步请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful())
System.out.println("执行成功");
}
});
System.out.println("这是异步执行方法");
}
五: 请求Request
5.1 实例创建
Request并未提供外部访问构造器,采用与OkHttpClient一致的设计,需要使用建造者模式静态内部类Builder构建个性化请求Request实例
public Builder() {
// 可以看出默认请求方式为GET
this.method = "GET";
this.headers = new okhttp3.Headers.Builder();
}
public Request build() {
if (this.url == null) {
throw new IllegalStateException("url == null");
} else {
return new Request(this);
}
}
Request request = new Request.Builder()
.url("")
.post(RequestBody.create(MediaType.parse(""), ""))
.build();
5.2 主要属性
// 请求路径
final HttpUrl url;
// 请求方式,包括GET、POST
final String method;
// 请求头
final Headers headers;
// 请求体,请求方式为POST必须设置
final RequestBody body;
5.3 请求体RequestBody
- 抽象类不能进行实例化,通过create()构建对象
- create()包含两个参数,MediaType请求体中数据类型以及传输的数据content
- MediaType类未提供可以访问的构造器,需要通过方法parse构建对象
- 常见MediaType类型如下表所示:
传输数据类型 | 表达字符串 |
---|---|
Json | application/json |
XML | application/xml |
From | application/x-www-form-urlencoded |
HTML | text/html |
public abstract class RequestBody {
public static RequestBody create(@Nullable MediaType contentType, String content) {
Charset charset = Util.UTF_8;
if (contentType != null) {
charset = contentType.charset();
if (charset == null) {
charset = Util.UTF_8;
contentType = MediaType.parse(contentType + "; charset=utf-8");
}
}
byte[] bytes = content.getBytes(charset);
return create(contentType, bytes);
}
六:调度器Dispatcher
- 调度器Dispatcher是保存同步请求、保存执行异步请求的地方
- 保存同步请求使用双端队列runningSynCalls
- 保存异步请求使用两个双端队列,因为异步执行限制默认最多支持64个请求,单个Host默认支持最大5个请求。当runningAsynCalls队列充满时需要放置在readyAsyncCall队列等待
七:拦截器
7.1 RetryAndFollowUpInterceptor
- 网络请求失败后进行重试
- 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接
7.2 BridgeInterceptor
- 设置内容长度,内容编码
- 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
- 添加饼干
- 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤
7.3 CacheInterceptor
- 当网络请求有符合要求的缓存时直接返回缓存
- 当服务器返回内容有改变时更新当前缓存
- 如果当前缓存失效,删除
7.4 ConnectInterceptor
为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定
7.5 CallServerInterceptor
负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回
7.6 应用拦截器
/**
* @ClassName: LoggingInterceptor
* @Description 自定义应用拦截器
* @Author: zsl
* @Data: 2019/4/22 14:32
* @Version 1.0
**/
public class LoggingInterceptor implements Interceptor {
private Integer num;
public LoggingInterceptor(Integer _num){
this.num = _num;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long begin = System.nanoTime();
System.out.println("第"+this.num+"个拦截器开始时间"+begin);
// 这个方法是核心,生成一个响应来满足请求
Response proceed = chain.proceed(request);
long end = System.nanoTime();
System.out.println("第"+this.num+"个拦截器总体耗时"+(end-begin));
return proceed;
}
}
public static String interceptTest() throws IOException {
Response response = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor(1))
.addInterceptor(new LoggingInterceptor(2))
.build()
.newCall(new Request.Builder().url("http:///localhost:8080/testPost").build())
.execute();
return null;
}
第1个拦截器开始时间23661050122538
第2个拦截器开始时间23661050311978
第2个拦截器总体耗时5101673558
第1个拦截器总体耗时5101909078
7.7 网络拦截器
网络拦截器与应用拦截器使用上的区别就是将addInterceptor()换成addNetworkInterceptor()。两者区别如下:
Application interceptors:
- 不需要关心是否重定向或者失败重连
- 应用拦截器只会调用一次,即使数据来源于缓存
- 只考虑应用的初始意图,不去考虑Okhhtp注入的Header比如:if-None-Match,意思就是不管其他外在因素只考虑最终的返回结果
- 自定义的应用拦截器是第一个开始执行的拦截器,所以应用拦截器可以决定是否执行其他的拦截器,通过Chain.proceed().
- 可以执行多次调用其他拦截器,通过Chain.proceed().
Network Interceptors:、
- 网络拦截器可以操作重定向和失败重连的返回值
- 取缓存中的数据就不会去执行Chain.proceed().所以就不能执行网络拦截器
- 网络拦截器可以观察到所有通过网络传输的数据
- 请求服务连接的拦截器先于网络拦截器执行,所以在进行网络拦截器执行时,就可以看到Request中服务器请求连接信息,因为应用拦截器是获取不到对应的连接信息的。
八:HTTPS
忽略验证
public static OkHttpClient buildOKHttpClient() {
try {
TrustManager[] trustAllCerts = buildTrustManagers();
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
})
.build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
return new OkHttpClient();
}
}
private static TrustManager[] buildTrustManagers() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
}