OkHttp 開源庫使用與原始碼解析
Android 開發只需新增依賴,如下:
implementation("com.squareup.okhttp3:okhttp:3.13.1")
官方示例1:獲取一個 url 上的內容並輸出
//Http 客戶端 OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { //構造請求 Request request = new Request.Builder() .url(url) .build(); //執行請求,獲取資料 try (Response response = client.newCall(request).execute()) { return response.body().string(); } } 複製程式碼
官方示例2:給伺服器post
資料
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } } 複製程式碼
簡單分析一下這段程式碼:這個post
請求操作看起來很簡單。但是我們需要學習其中幾個很重要的介面:
OKHttpClient:它代表著 http 客戶端
Request:它封裝了請求物件,可以構造一個 http 請求物件
Response:封裝了響應結果
Call:client.newCall()呼叫後生成一個請求執行物件Call,它封裝了請求執行過程。
下面我們結合這個例子來分析原始碼:
newCall().execute()
跟蹤原始碼後發現這個方法是在 Call 中的介面,程式碼如下:
/** * A call is a request that has been prepared for execution. A call can be canceled. As this object * represents a single request/response pair (stream), it cannot be executed twice. */ public interface Call extends Cloneable { //省略部分程式碼 //同步執行請求 Response execute() throws IOException; //請求加入佇列 void enqueue(Callback responseCallback); //省略部分程式碼 } 複製程式碼
Call 的實現類是 RealCall,繼續追蹤原始碼的 RealCall.java 檔案,可以看到 execute 方法:
@Override public Response execute() throws IOException { //同步鎖檢查該請求是否已經執行,如果沒有則標記executed = ture,否則丟擲異常 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); timeout.enter(); //呼叫了回撥方法 callStart eventListener.callStart(this); try { //okhttp 客戶端呼叫 dispatcher 將執行請求物件 client.dispatcher().executed(this); //呼叫了 getResponseWithInterceptorChain 方法獲取到響應資料 Response,後期還會繼續分析 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); //請求失敗的回撥 callFailed eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); //使用 dispather 物件呼叫 finished 方法,完成請求 } } 複製程式碼
接下來我們詳細分析一下 dispatcher.execute 和 getResponseWithInterceptorChain 這兩個方法:
跟蹤原始碼 Dispatcher
public final class Dispatcher { //省略部分程式碼 /** Executes calls. Created lazily. */ private @Nullable ExecutorService executorService; /** 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<>(); /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); public Dispatcher(ExecutorService executorService) { this.executorService = executorService; } void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (!call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host()); if (existingCall != null) call.reuseCallsPerHostFrom(existingCall); } } promoteAndExecute(); } private <T> void finished(Deque<T> calls, T call) { Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); idleCallback = this.idleCallback; } boolean isRunning = promoteAndExecute(); if (!isRunning && idleCallback != null) { idleCallback.run(); } } } 複製程式碼
發現 Dispatcher 是一個排程器,它的作用是對請求進行分發。它的內部有三個佇列,分別是同步請求進行佇列、非同步請求等待佇列、非同步請求執行佇列。
核心方法: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); } 複製程式碼
此方法構建數個攔截器之後,又構造了一個攔截器責任鏈。跟蹤原始碼 RealInterceptorChain:
RealInterceptorChain
該類主要負責將所有的攔截器串連起來,使所有的攔截器以遞迴的方式進行實現,從而確保只有所有的攔截器都執行完之後才會返回 Response。以下對該類中的主要方法 proceed 進行講解:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); //用於計算攔截器呼叫該方法的次數 calls++; // If we already have a stream, confirm that the incoming request will use it. if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must retain the same host and port"); } // If we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must call proceed() exactly once"); } //生成下一個攔截器調動該方法的 RealInterceptorChain 物件,其中 index+1 用於獲取下一個攔截器 RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); //獲取當前攔截器 Interceptor interceptor = interceptors.get(index); //呼叫該攔截器並獲取返回結果 Response response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } return response; } 複製程式碼
總結
以上就是我們對 OkHttp 核心原始碼的分析。當我們發起一個請求的時候會初始化一個 Call 的例項,然後根據同步和非同步的不同,分別呼叫它的 execute() 和 enqueue() 方法。大致過程都是通過攔截器組成的責任鏈,依次經過重試、橋接、快取、連線和訪問伺服器等過程,來獲取到一個響應並交給使用者。