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[]{};
}
}
};
}