Android API23(6.0)版本之後,Google正式移除Apache-HttpClient。OkHttp做爲一個現代,快速,高效的HttpClient,其功能之強大也是顯而易見的html
- 支持HTTP/2協議,經過HTTP/2 可讓客戶端中到服務器的全部請求共用同一個Socket鏈接
- 非HTTP/2 請求時, OkHttp內部會維護一個線程池,經過線程池能夠對HTTP/1.x的鏈接進行復用,減小延遲
- 支持post,get請求,基於http的文件上傳和下載
- 默認狀況下,OkHttp會自動處理常見的網絡問題,像二次鏈接、SSL的握手問題
固然OkHttp的功能遠不止這些,這裏只是說明平時常常用到的。既然OkHttp已經做爲官方庫使用,相比咱們在作項目的時候也會用,但對於其底層的實現原理仍是隻知其一;不知其二,那咱們就從這篇文章開始解釋其底層實現原理。開車前先來一波介紹:java
Ecplise引用:下載最新的Jar包android
Android studio引用:implementation 'com.squareup.okhttp3:okhttp:3.11.0' //最新版本號請關注okhttp官網
git
Maven引用:github
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version> //最新版本號請關注okhttp官網
</dependency>
複製代碼
各位老司機們,立刻開車,嘀嘀嘀!web
流程以下apache
// 啓動客戶端類,主要有兩種方法進行建立,new對象和Builder內部類實現實例化
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
// get請求
// 經過Builder模式建立一個Request對象(即請求報文)
// 這裏能夠設置請求基本參數:url地址,get請求,POST請求,請求頭,cookie參數等
Request request = new Request.Builder()
.url("http://www.baidu.com")
.header("User-Agent", "xxx.java")
.addHeader("token", "xxx")
.get()
.build();
// POST請求
// 表單形式上傳
RequestBody body = new FormBody.Builder().add("xxx","xxx").build();
// JSON參數形式,File對象上傳
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
RequestBody body = RequestBody.create(MediaType.parse("File/*"), file);
Request request = new Request.Builder()
.post(body)
.url(url)
.header("User-Agent", "xxx.java")
.addHeader("token", "xxx")
.build();
// 建立Call對象(Http請求) ,即鏈接Request和Response的橋樑
// newCall方法將request封裝成Call對象
Call call = client.newCall(request);
try{
// Response即響應報文信息,包含返回狀態碼,響應頭,響應體等
Response response = call.execute();
// 這裏深刻一點,Call實際上是一個接口,調用Call的execute()發送同步請求實際上是調用了Realcall實現類的方法,Realcall從源碼能夠看出示一個Runable
System.out.println(response.body().string());
}catch(IOException e){
e.printStackTrace();
}
複製代碼
看完代碼你可能以爲OkHttp基本流程很繁瑣,可是去掉一些擴展參數,你會發現OkHttp的使用其實很簡單,無非就是json
- 建立一個OkHttpClient並實例化,可設置相關參數鏈接時長connectTimeout等
- 建立一個Request對象並實例化,可設置網絡地址url,請求方式get,post,攜帶參數等;
- 建立一個Call對象,經過okhttpClient的newCall()方法將Request封裝成Call對象
- 建立一個Response響應,用於接收服務器返回的相關信息; 即OkHttpClient客戶端經過newCall()方法接受你的Request請求並生成Response響應
看到這裏你可能會問,爲何不繼續講些關於文件上傳,文件下載,Interceptors攔截器這些內容?其實同步和異步請求的實現能夠說是源碼中很是重要的一環,涉及到線程池
,Dispatch調度
,反向代理
等,掌握核心科技,剩下的都是開胃小菜。緩存
1)基本使用方法安全
同步請求方法
OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = client.newCall(request);
try{
Response response = call.execute();//調用同步請求
System.out.println(response.body().string());
}catch(IOException e){
e.printStackTrace();
}
複製代碼
經過上面代碼能夠看出同步請求的基本流程: 1.建立OkHttpClient和Request對象 2.將Request封裝成Call對象 3.調用Call的excute()發起同步請求
*特別注意*:
當前線程發送同步請求後,就會進入阻塞狀態
,直到數據有響應纔會中止(和異步最大的不一樣點
)
異步請求方法
OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() { //調用異步請求,CallBack用於請求結束之後來進行接口回調
@Override
public void onFailure(Call call, IOException e) { System.out.println("Failure");}
@Override
public void onResponse(Call call, Response response) throw s IOException {
System.out.println(response.body().string());
}
});
複製代碼
經過上面代碼能夠看出異步請求的基本流程: 1.建立OkHttpClient和Request對象 2.將Request封裝成Call對象 3.調用Call的enqueue發起異步請求
*特別注意*:
onFailure和onResponse都是執行在子線程
中
不難看出,其實異步和同步請求的不一樣點
在於
發起請求方法調用
是否阻塞線程
到此,咱們已經熟悉了OkHttp的同步,異步請求方法的基本使用;無論同步仍是異步的調用都須要先初始化OkHttpClient,建立Request ,調用OkHttpClient.newcall()封裝Call對象,但其內部又是如何實現的呢?
同步請求執行流程
第一步:初始化OkHttpClient(Builder 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; // cookie
socketFactory = SocketFactory.getDefault(); // socket 工廠
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; // 命令間隔
}
複製代碼
從這裏看到,其實OkHttpClient的初始化已經幫咱們配置了基本參數,咱們也能夠根據自身業務需求進行相應的參數設置(失敗重連,添加攔截器,cookie等等),通常遇到建立對象須要大量參數時,推薦使用Builider模式鏈式調用完成參數初始化,具體使用能夠去Android源碼中的AlertDialog、Notification中詳細瞭解; 這裏咱們重點注意兩個核心,Dispatcher和ConnectionPool,這兩點會在後面作詳細講解
Dispatcher
:OkHttp請求的調度分發器,由它決定異步請求在線程池中是直接處理仍是緩存等待,固然對於同步請求,只是將相應的同步請求放到請求隊列當中執行
ConnectionPool
: 統一管理客戶端和服務器之間鏈接的每個Connection,做用在於
- 當你的Connection請求的URL相同時,能夠選擇是否複用;
- 控制Connection保持打開狀態仍是複用
第二步:建立 (Builder builder)
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(); //Headers內部類
}
複製代碼
這個構造方法很簡單,在Request.Builder模式下默認指定請求方式爲GET請求,建立了Headers內部類來保存頭部信息,咱們再來看build方法
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
複製代碼
Request的構造方法就是爲其初始化指定需求的請求方式,請求URL,請求頭部信息,這樣就完成同步請求的前兩步
第三步:調用OkHttpClient.newcall()封裝Call對象
/**
* Prepares the {@code request} to be executed at some point in the future.
* 準備在未來某個時候執行{@code請求}
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
上面咱們也提到過,Call是一個接口,因此它的實際操做是在RealCall類中實現的
final class RealCall implements Call {
final OkHttpClient client;
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; //重定向攔截器
/**
* There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward.
* This will be set after we create the call instance then create the event listener instance.
*/
private EventListener eventListener;
/** The application's original request unadulterated by redirects or auth headers. */ final Request originalRequest; final boolean forWebSocket; // Guarded by this. private boolean executed; //實際構造方法 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); } 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; } } 複製代碼
從這裏就能夠看到,RealCall實際上是持有以前初始化好的OkHttpClient和Request對象,同時賦值了RetryAndFollowUpInterceptor重定向攔截器,關於攔截器的內容,咱們會後面具體講解OKhttp內部的5大攔截器;
第四步,調用call.exucte方法實現同步請求
@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); // 調度器將call請求 加入到了同步執行隊列中
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);
}
}
複製代碼
首先, 加入了synchronized 同步鎖,判斷executed標識位是否爲true,確保每一個call只能被執行一次不能重複執行,而後開啓了eventListener監聽事件,接收相應的事件回調,經過dispatcher將Call請求添加到同步隊列中
public Dispatcher dispatcher() {
return dispatcher;
}
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */ // 同步請求隊列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); 複製代碼
每當調用executed同步方法時,dispather就會幫咱們把同步請求添加到同步請求隊列中去,由此能夠看出Dispather調度器的做用就是維持Call請求發送狀態
和維護線程池
並把Call請求添加到相應的執行隊列
當中,由它決定當前Call請求是緩存等待仍是直接執行,流程以下
getResponseWithInterceptorChain()是一個攔截器鏈,依次調用攔截器對返回的response進行相應的操做,咱們在講解到責任鏈模式時會詳細介紹,如圖
另外要特別注意下client.dispatcher().finished(this);
/** Used by {@code Call#execute} to signal completion. */
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) {
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();
}
}
/***********************************************************************************************************************/
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
複製代碼
當同步請求完成後會調用finished()方法將隊列中的請求清除掉,runningCallsCount()計算返回正在執行同步請求和正在執行異步請求的數量總和,最後判斷若是runningCallsCount 爲0的時候,表示整個Dispatcher分發器中沒有可運行的請求,同時在知足idleCallback不爲空的狀況下,就調用Run方法開啓閒置接口;這裏能夠看出,在同步請求的方法中,dispatcher的做用只是調用 executed將Call請求添加到同步隊列中,執行完畢後調用 finished清除隊列中的請求,可見dispatcher更多的是爲異步服務
異步請求執行流程
關於OkHttpClient和Request初始化流程上文已經講解,不清楚的能夠返回去看看,因此直奔主題
第四步,調用call.enqueue方法實現異步請求
//RealCall實現類
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true; // executed用於表示Call請求是否執行過
}
captureCallStackTrace();// 捕捉異常堆棧信息
eventListener.callStart(this);// 開啓監聽事件
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
有沒有發現和同步的excute方法很相似,都是先使用synchronized 防止請求重複執行,而後開啓監聽事件,最後在執行相應的方法,但奇怪的是同步在執行完excute方法後是直接經過getResponseWithInterceptorChain()返回數據,異步又是如何返回數據的呢?AsyncCall又是幹什麼的?
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 返回數據
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
複製代碼
這裏的 AsyncCall 是 RealCall 的一個內部類,它繼承於NamedRunnable抽象類,NamedRunnable抽象類又實現了 Runnable,因此能夠被提交到ExecutorService上執行,在execute方法裏,咱們看到了熟悉的流程,上文也說到getResponseWithInterceptorChain是一個攔截器鏈,會依次執行相應的攔截器後返回數據,因此當返回數據後,經過retryAndFollowUpInterceptor重定向攔截器判斷請求是否正常執行,而且經過Callback接口返回相應數據信息,最後調用finished方法清除隊列 這裏有個疑問,Dispatcher是經過什麼把異步就緒隊列的請求調度分發
到異步執行隊列中的?
//Disaptcher
/** Ready async calls in the order they'll be run. */ // 異步就緒隊列 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 異步執行隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
private void promoteCalls() {
// maxRequests最大請求數量64
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.
}
}
複製代碼
源碼是否是很清楚明瞭,原來異步隊列就是在這裏進行調度的,在for循環中,Disaptcher首先對異步就緒隊列進行遍歷,若是知足runningCallsForHost(當前調用請求主機數)小於maxRequestsPerHost( 最大請求主機數5個)而且異步併發數量沒有超過最大請求數量64的前提下,就把異步就緒隊列中最後一個元素移除加入到異步執行隊列中
咱們接着看enqueue方法具體作了哪些操做//Disaptcher
synchronized void enqueue(AsyncCall call) {
// 異步併發請求數量不能超過最大請求數量64
// 當前網絡請求的host是否小於5個請求的host
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 加入執行隊列 並交給線程池執行
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
// 加入就緒隊列等待
readyAsyncCalls.add(call);
}
}
***************************************************************************************************************
public synchronized ExecutorService executorService() {
// 核心線程 最大線程 非核心線程閒置60秒回收 任務隊列
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
複製代碼
Disaptcher的enqueue方法只是作了一個異步請求的邏輯判斷,即判斷當前異步併發執行隊列的數量是否超過最大承載運行數量64和相同host主機最多容許5條線程同時執行請求,知足以上條件,則將傳進來的AsyncCall添加到異步執行隊列,同時啓動線程池執行,反之則添加到異步就緒隊列中等待,executorService調用的就是AsyncCall的execute方法
同步和異步請求的源碼就講到這裏,對過程還有不理解的能夠在下方評論中提出問題 文章連接: