首先爲何要寫這篇博客,主要是由於如今網絡用的都是okhttp3,因此在面試的時候,都會問一下okhttp的原理,而網上的原理,也看了下,要麼太簡短,核心的一筆帶過,要麼長篇大倫,看着蒙圈。因此想看看能不能用最簡短明白的方式來解釋下okhttp3的原理。 固然,若是還不是很熟悉這個框架的小朋友,能夠點這裏一文了解OKHttp3全(使用篇)
java
來,這就是簡單的一個get請求,我們按照順序從1,2,3,4這幾點開始分析。首先先分析第1點。web
首先要想使用網絡請求的話,得先初始化它,先看看有哪些屬性。面試
文件位置:OkHttpClient.java設計模式
final Dispatcher dispatcher;//調度器
final Proxy proxy;//代理
final List<Protocol> protocols;//協議
final List<ConnectionSpec> connectionSpecs;//傳輸層版本和鏈接協議
final List<Interceptor> interceptors;//攔截器
final List<Interceptor> networkInterceptors;//網絡攔截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;//代理選擇器
final CookieJar cookieJar;//cookie
final Cache cache;//cache 緩存
final InternalCache internalCache;//內部緩存
final SocketFactory socketFactory;//socket 工廠
final SSLSocketFactory sslSocketFactory;//安全套層socket工廠 用於https
final CertificateChainCleaner certificateChainCleaner;//驗證確認響應書,適用HTTPS 請求鏈接的主機名
final HostnameVerifier hostnameVerifier;//主機名字確認
final CertificatePinner certificatePinner;//證書鏈
final Authenticator proxyAuthenticator;//代理身份驗證
final Authenticator authenticator;//本地省份驗證
final ConnectionPool connectionPool;//連接池 複用鏈接
final Dns dns; //域名
final boolean followSslRedirects;//安全套接層重定向
final boolean followRedirects;//本地重定向
final boolean retryOnConnectionFailure;//重試鏈接失敗
final int connectTimeout;//鏈接超時
final int readTimeout;//讀取超時
final int writeTimeout;//寫入超時
複製代碼
很好,屬性看完了,就這些,基本也夠了,可是尼,怎麼賦值是個問題,總不能都寫在構造函數裏面吧?有什麼好的設計模式尼?就是那種將使用和複雜的構建相分離的那種?恭喜你答對了,就是構建者模式。緩存
果真,okhttp3用的也是它。看下源碼吧安全
public static final class Builder {
...
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
...
}
複製代碼
這就簡單了,都設置了默認值,那麼咱們在外邊調用的時候就很簡單了,只須要這麼寫就能夠了。服務器
OkHttpClient mClient = new OkHttpClient.Builder() // 構建者模式,建立實例
.build();
複製代碼
OK,第1步完事了cookie
第 1 步就是用構建者模式建立okhttp3的實例,裏面封裝了一些使用中必要的屬性,如超時時間,攔截器等網絡
這是第2步,說白了,第1步是建立okhttp3的實例,那麼第2步,就是建立請求信息。 廢話少說,先看下它有哪些屬性。併發
文件位置:Request
final HttpUrl url; // 接口地址
final String method; // post仍是get
final Headers headers; // Http消息的頭字段
final RequestBody body; // 它是抽象類, 有些請求須要咱們傳入body實例,若是是GET請求,body對象傳的是null,若是是POST請求,就須要咱們去設定了。
final Object tag;
複製代碼
好了,若是你想簡單省事的去用,自動設置默認值,那就繼續咱們的構建者模式吧
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body;
Object tag;
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
複製代碼
其實也沒省啥事,無非就是你直接用的時候,給你默認了個Get而已。 不過既然封裝了,咱們就能夠這麼調用了
Request mRequest = new Request.Builder() // 構建者模式,建立請求信息
.url("https://www.baidu.com")
.build();
複製代碼
OK,第2步完事了
第 2 步就是用構建者模式建立請求信息的實例,裏面封裝了一些使用中必要的屬性,如請求方式,請求頭信息,接口地址等
好了,繼續看咱們的第3步 首先尼,根據okHttpClient和Request對象,咱們就能構建出實際進行請求的call對象。看源碼 咱們得知,call是個接口,實際進行請求的是RealCall,來咱們建立它
文件位置:OkHttpClient.java
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
複製代碼
看看它作了什麼
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client; // 第1步建立的okHttpClient實例
this.originalRequest = originalRequest; // 第2步建立的request實例
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // 重定向攔截器,後面會說
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
複製代碼
好了,第3步就完事了
第 3 步就是建立realcall對象,真正的請求是交給了 RealCall 類,它實現了Call方法,它是真正的核心代碼。
開始分析最後1步 這塊是關鍵,咱們一步一步的來。首先看咱們的調用
call.enqueue(new Callback(){ ... });
複製代碼
而後看,realcall.enqueue()方法
@Override
public void enqueue(Callback responseCallback) {
1.synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
2.client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
先看下new AsyncCall(responseCallback)
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
}
複製代碼
對咱們的回調進行封裝,繼承Runnable接口 好,再繼續看client.dispatcher().enqueue()
文件位置:Dispatcher.java
synchronized void enqueue(AsyncCall call) {
1.if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
2.runningAsyncCalls.add(call);
3.executorService().execute(call);
} else {
4.readyAsyncCalls.add(call);
}
}
複製代碼
在call.enqueue(new Callback(){ ... }) 執行以後,首先作的是
1. 調用RealCall.call.enqueue()方法,判斷當前call是否已經被執行過了,被執行過了,就拋出異常,若是沒有執行過,就先將callback封裝成AsyncCall,而後調用dispatcher.enqueue()方法(dispatcher調度器,在okHttpClient裏面建立的)
2. 在dispatcher.enqueue()方法中,判斷當前正在執行的請求數及當前網絡請求的主機數是否超過了最大值。要是超過了最大值,就將請求放到等待隊列中,要是沒超過,就放當正在執行的隊列中,而後調用線程池執行它。
吃過了午餐,繼續分析源碼,承接上文,咱們分析到了,若是符合條件,就用線程池去執行它,也就是這句
executorService().execute(call);
複製代碼
看一下咱們的線程池
public synchronized ExecutorService executorService() {
if (executorService == null) {
1.executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
複製代碼
核心線程 最大線程 非核心線程閒置60秒回收,固然會有人提出疑問,最大值設置爲Integer.MAX_VALUE,會不會對性能形成影響呀?答案是不會的,由於尼,在前面的dispatcher.enqueue()方法中,已經對請求數作了限制,超過設置的最大請求數,會被放到等待隊列裏面。
好,繼續再看下線程池executorService.execute(call)方法 它會執行裏面call方法的run()方法,也就是AsyncCall的run方法,這個run方法其實是在它的父類NamedRunnable裏面。在NamedRunnable.run()方法裏面,其實是調用了execute(),該方法由子類實現,也就是調用了AsyncCall.execute()
簡單來講,就是executorService.execute(call) -> NamedRunnable.run() -> AsyncCall.execute() 看到這個寫法,心裏中就想吐槽一句話,真雞兒秀! 來繼續看下,AsyncCall.execute()
看源碼 文件位置:realcall.java
@Override protected void execute() {
boolean signalledCallback = false;
try {
1.Response response = getResponseWithInterceptorChain();
2.if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
3.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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
4.client.dispatcher().finished(this);
}
}
複製代碼
看源碼
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) {
1.if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
2.if (promoteCalls) promoteCalls();
3.runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
複製代碼
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
複製代碼
很簡單,這裏無非就是遍歷等待隊列中的請求,而後加入到執行請求隊列中,直到併發數和當前網絡請求的主機數達到上限。
至此,okhttp的異步已經分析完畢了
面試題 1.什麼是dispatcher? dispatcher做用是爲維護請求的狀態,並維護一個線程池。用於執行請求。
小夥子,是否是很簡單呀?是否是已經完了?你想多了,來分析最核心的一部分
先看源碼
Response getResponseWithInterceptorChain() throws IOException {
// 添加攔截器,責任鏈模式
List<Interceptor> interceptors = new ArrayList<>();
// 在配置okhttpClient 時設置的intercept 由用戶本身設置
interceptors.addAll(client.interceptors());
// 負責處理失敗後的重試與重定向
interceptors.add(retryAndFollowUpInterceptor);
/** 負責把用戶構造的請求轉換爲發送到服務器的請求 、把服務器返回的響應轉換爲用戶友好的響應 處理 配置請求頭等信息. 從應用程序代碼到網絡代碼的橋樑。首先,它根據用戶請求構建網絡請求。而後它繼續呼叫網絡。最後,它根據網絡響應構建用戶響應。 */
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 處理 緩存配置 根據條件(存在響應緩存並被設置爲不變的或者響應在有效期內)返回緩存響應
// 設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改)
// 可配置用戶本身設置的緩存攔截器
interceptors.add(new CacheInterceptor(client.internalCache()));
// 鏈接服務器 負責和服務器創建鏈接 這裏纔是真正的請求網絡
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
// 執行流操做(寫出請求體、得到響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據
// 進行http請求報文的封裝與請求報文的解析
interceptors.add(new CallServerInterceptor(forWebSocket));
// 責任鏈,將上述的攔截器添加到責任鏈裏面
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
複製代碼
這裏用的是責任鏈模式,不瞭解的朋友能夠先了解下 這段代碼其實很簡單,就是將一些攔截器,都裝到一個集合裏面,而後將攔截器的集合當作構造參數,建立了個對象(RealInterceptorChain),最後調用它的proceed方法。而後一直往下調用。
原本想再寫寫攔截器這邊的邏輯,可是尼,老夫掐指一算,就知道大家這塊容易看懵逼,那麼我們本身寫一個吧
1.先定義接口
public interface Interceptor {
interface Chain{
String request();
String proceed(String request);
}
}
複製代碼
2.來定義一個RetryAndFollowInterceptor(重定向攔截器)
public class RetryAndFollowInterceptor implements Interceptor{
@Override
public String interceptor(Chain chain) {
System.out.println("RetryAndFollowInterceptor_start");
String response = chain.proceed(chain.request());
System.out.println("RetryAndFollowInterceptor_start");
return response;
}
}
複製代碼
2.再定義一個BridgeInterceptor(橋接攔截器)
public class BridgeInterceptor implements Interceptor{
@Override
public String interceptor(Chain chain) {
System.out.println("BridgeInterceptor_start");
String response = chain.proceed(chain.request());
System.out.println("BridgeInterceptor_end");
return response;
}
}
複製代碼
3.最後一個攔截器CallServerInterceptor
public class CallServerInterceptor implements Interceptor {
@Override
public String interceptor(Chain chain) {
System.out.println("CallServerInterceptor_start");
System.out.println("----------將數據傳到服務器端:數據爲"+ chain.request());
System.out.println("CallServerInterceptor_end");
return "登錄成功";
}
}
複製代碼
4.來,定義個責任鏈RealInterceptorChain對象
public class RealInterceptorChain implements Interceptor.Chain{
private List<Interceptor> interceptors;
private int index;
private String request;
public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
this.interceptors = interceptors;
this.index = index;
this.request = request;
}
@Override
public String request() {
return request;
}
@Override
public String proceed(String request) {
if(index >= interceptors.size()) {
return null;
}
// 這裏就是責任鏈模式,它會把它的index+1 而後再建立一個RealInterceptorChain對象
RealInterceptorChain next = new RealInterceptorChain(interceptors, index + 1, request);
Interceptor Interceptor = interceptors.get(index);
return Interceptor.interceptor(next);
}
}
複製代碼
5.調用一下
public static void main(String[] args) {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(new RetryAndFollowInterceptor());
interceptors.add(new BridgeInterceptor());
interceptors.add(new CallServerInterceptor());
RealInterceptorChain chain = new RealInterceptorChain(interceptors, 0, "");
String result = chain.proceed("xiaoming, 123");
System.out.println("----------服務器返回的結果是:"+result);
}
複製代碼
6.結果
看明白了嗎?若是不明白,這個圖應該明白了吧。
是否是感受okhttp很簡單?攔截器還沒介紹尼,來最後介紹下攔截器。
一、用戶自定義的攔截器 用在與服務器創建連接以前進行攔截
interceptors.addAll(client.interceptors());
複製代碼
二、RetryAndFollowUpInterceptor重試或失敗重定向攔截器
interceptors.add(retryAndFollowUpInterceptor);
複製代碼
三、BridgeInterceptor 校接和適配攔截器,主要補充用戶建立請求當中的一些請求頭Content-Type
interceptors.add(new BridgeInterceptor(client.cookieJar()));
複製代碼
四、CacheInterceptor主要處理緩存
interceptors.add(new CacheInterceptor(client.internalCache()));
複製代碼
五、ConnectInterceptor 與服務器建立連接,建立能夠用的RealConnection(對java.io和java.nio進行了封裝)
interceptors.add(new ConnectInterceptor(client));
複製代碼
六、用戶自定義的攔截器 用在與服務器創建連接以後進行攔截。只有非socket進行設置
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
複製代碼
七、CallServerInterceptor 向服務器發送請求和接收數據。將請求寫入IO流,再從IO流中讀取響應數據
interceptors.add(new CallServerInterceptor(forWebSocket));
複製代碼
這裏只是面試的時候簡寫,如需詳細,請底下留言。