有關OkHttp
的介紹這裏就不作過多贅述,這個框架算是Android
開發中的網絡框架大佬。在不少知名的框架裏也能找到它的身影,例如:Glide
、Retrofit
等等。
既然這麼牛X,咱們固然要來分析一下這個框架。本篇文章使用的是3.12.0
版本,固然官網最新版本是4.2.2
,4.x和3.x的區別主要在於4.x使用kotlin
語言對框架進行重寫,3.x使用的是Java
語言。最後還要提醒一句,在使用網絡框架時不要忘記網絡權限!網絡權限!網絡權限!android
有關網絡相關的知識在這裏就不作介紹了,這部分知識很重要,但願各位小夥伴可以掌握。這裏爲各位小夥伴準備了SnailClimb大佬的JavaGuide開源項目,裏面對相關基礎知識作了介紹,有興趣的小夥伴能夠看一下。git
private final String url_navi = "https://www.wanandroid.com/navi/json"; private final String url_friend = "https://www.wanandroid.com/friend/json"; private TextView tv_response; Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull final Message msg) { tv_response.setText(msg.obj.toString()); } }; @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_enqueue: enqueue(); break; case R.id.btn_execute: execute(); break; } } private void enqueue() { OkHttpClient okHttpClient = new OkHttpClient(); Request request = new Request.Builder() .get() .url(url_friend) .build(); final Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { Message message = Message.obtain(); message.what = 1; message.obj = response.body().string(); mHandler.sendMessage(message); } }); } private void execute() { final OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .get() .url(url_navi) .build(); new Thread(new Runnable() { @Override public void run() { try { Response response = okHttpClient.newCall(request).execute(); Message message = Message.obtain(); message.what = 1; message.obj = response.body().string(); mHandler.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } }).start(); } 複製代碼
寫這個例子是用到了鴻洋大神的Wandroid網站開放API,很是感謝。
咱們都知道,在Android
主線程中不能作耗時操做。因此咱們將網絡請求放到了子線程中。當請求拿到數據以後,經過Handler
將數據傳到主線程中對UI進行修改。
這裏使用了OkHttp
中的兩種請求方法,一種是enqueue
方法,另外一種是execute
方法。前者屬於異步方法,後者屬於同步方法,這兩種方法都會在下文中講到。最後咱們看一下運行效果:github
咱們在進行網絡請求時,首先會建立一個OkHttpClient
對象,咱們來看一下。web
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { ... public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.proxy = builder.proxy; this.protocols = builder.protocols; this.connectionSpecs = builder.connectionSpecs; this.interceptors = Util.immutableList(builder.interceptors); this.networkInterceptors = Util.immutableList(builder.networkInterceptors); this.eventListenerFactory = builder.eventListenerFactory; this.proxySelector = builder.proxySelector; this.cookieJar = builder.cookieJar; this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; boolean isTLS = false; for (ConnectionSpec spec : connectionSpecs) { isTLS = isTLS || spec.isTls(); } if (builder.sslSocketFactory != null || !isTLS) { this.sslSocketFactory = builder.sslSocketFactory; this.certificateChainCleaner = builder.certificateChainCleaner; } else { X509TrustManager trustManager = Util.platformTrustManager(); this.sslSocketFactory = newSslSocketFactory(trustManager); this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); } if (sslSocketFactory != null) { Platform.get().configureSslSocketFactory(sslSocketFactory); } this.hostnameVerifier = builder.hostnameVerifier; this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(certificateChainCleaner); this.proxyAuthenticator = builder.proxyAuthenticator; this.authenticator = builder.authenticator; this.connectionPool = builder.connectionPool; this.dns = builder.dns; this.followSslRedirects = builder.followSslRedirects; this.followRedirects = builder.followRedirects; this.retryOnConnectionFailure = builder.retryOnConnectionFailure; this.callTimeout = builder.callTimeout; this.connectTimeout = builder.connectTimeout; this.readTimeout = builder.readTimeout; this.writeTimeout = builder.writeTimeout; this.pingInterval = builder.pingInterval; if (interceptors.contains(null)) { throw new IllegalStateException("Null interceptor: " + interceptors); } if (networkInterceptors.contains(null)) { throw new IllegalStateException("Null network interceptor: " + networkInterceptors); } } ... } 複製代碼
咱們經過調用OkHttpClient()
的無參構造函數時,在其內部調用了OkHttpClient(Builder builder)
構造函數,傳入一個Builder
對象,經過Builder
對象對OkHttpClient()
內部的屬性字段進行賦值。咱們再來看一下Builder
類。json
public static final class Builder { Dispatcher dispatcher; @Nullable Proxy proxy; List<Protocol> protocols; List<ConnectionSpec> connectionSpecs; final List<Interceptor> interceptors = new ArrayList<>(); final List<Interceptor> networkInterceptors = new ArrayList<>(); EventListener.Factory eventListenerFactory; ProxySelector proxySelector; CookieJar cookieJar; @Nullable Cache cache; @Nullable InternalCache internalCache; SocketFactory socketFactory; @Nullable SSLSocketFactory sslSocketFactory; @Nullable CertificateChainCleaner certificateChainCleaner; HostnameVerifier hostnameVerifier; CertificatePinner certificatePinner; Authenticator proxyAuthenticator; Authenticator authenticator; ConnectionPool connectionPool; Dns dns; boolean followSslRedirects; boolean followRedirects; boolean retryOnConnectionFailure; int callTimeout; int connectTimeout; int readTimeout; int writeTimeout; int pingInterval; public Builder() { dispatcher = new Dispatcher(); //請求分發器 protocols = DEFAULT_PROTOCOLS; //默認協議 connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); if (proxySelector == null) { proxySelector = new NullProxySelector(); } 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; callTimeout = 0; connectTimeout = 10_000; //鏈接超時時間 readTimeout = 10_000; //讀取超時時間 writeTimeout = 10_000; //寫入超時時間 pingInterval = 0; } ... } 複製代碼
這個Builder
是OkhttpClient
的內部類,在它的構造方法中是對其屬性字段進行賦值。
設計模式
在上一步Buidler
賦值的過程當中,建立了一個Dispatcher
類,這個類是幹什麼的?緩存
/** * Policy on when async requests are executed. * * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your * own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number * of calls concurrently. */ public final class Dispatcher { private int maxRequests = 64; //最大請求個數 private int maxRequestsPerHost = 5; //每一個Host的最大請求個數 private @Nullable Runnable idleCallback; private @Nullable ExecutorService executorService; //線程池對象 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //將要執行的異步請求隊列 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //正在執行的異步請求隊列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); //正在執行的同步請求隊列 public Dispatcher(ExecutorService executorService) { this.executorService = executorService; } public Dispatcher() { } // 建立一個線程池,核心線程爲0,最大爲Integer的最大值,空閒線程60s沒任務線程自動銷燬 public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } ... synchronized void executed(RealCall call) { runningSyncCalls.add(call); } void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); } promoteAndExecute(); } ... } 複製代碼
從這個類的註釋咱們能夠看到,這個類和異步請求有關。
同時,咱們能夠看到,這個類中設定了最大的請求個數、每一個Host
最大的請求個數、線程池等等。同時還維護了三個隊列,分別表示:準備執行的異步請求、正在執行的異步請求和正在執行的同步請求方法。
在執行executed
方法時,實際是將一個同步請求對象加入到正在執行的同步請求隊列中去;在執行enqueue
方法時,將一個異步請求對象加入到準備執行的異步請求隊列中去。bash
建立完OkhttpClient
對象以後,咱們又使用new Request.Builder()
方法建立Request
對象,同時運用構建者模式對其進行賦值。markdown
public final class Request { final HttpUrl url; final String method; final Headers headers; final @Nullable RequestBody body; final Map<Class<?>, Object> tags; private volatile @Nullable CacheControl cacheControl; // Lazily initialized. Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tags = Util.immutableMap(builder.tags); } public static class Builder { @Nullable HttpUrl url; String method; Headers.Builder headers; @Nullable RequestBody body; public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } Builder(Request request) { this.url = request.url; this.method = request.method; this.body = request.body; this.tags = request.tags.isEmpty() ? Collections.<Class<?>, Object>emptyMap() : new LinkedHashMap<>(request.tags); this.headers = request.headers.newBuilder(); } public Builder url(HttpUrl url) { if (url == null) throw new NullPointerException("url == null"); this.url = url; return this; } ... } ... } 複製代碼
咱們看到,這一步主要是對Request
進行賦值,其中包括:請求地址、請求方法和請求體等等。cookie
這一步咱們經過第一步建立的OkHttpClicent
對象調用其newCall
方法,傳入第二步建立的Request
對象,返回一個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 { //獲取初始化Call對象的原始Request對象 Request request(); //執行同步請求 Response execute() throws IOException; //執行異步請求 void enqueue(Callback responseCallback); //取消請求 void cancel(); //請求是否已經執行 boolean isExecuted(); //請求是否被取消 boolean isCanceled(); ... } public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { ... @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } ... } final class RealCall implements Call { ... private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); this.timeout = new AsyncTimeout() { @Override protected void timedOut() { cancel(); } }; this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } ... } 複製代碼
Call
實際上是一個接口,裏面定義了一系列對請求進行操做的方法。RealCall
對象實現了Call
接口,重寫了接口裏面的方法。因此,這一步實際上返回的是一個RealCall
對象,從這裏咱們也能夠看出,真正發起請求的是RealCall
對象。
在執行網絡請求時,咱們使用了execute
方法,咱們看一下。
@Override public Response execute() throws IOException { synchronized (this) { // 1添加同步鎖 if (executed) throw new IllegalStateException("Already Executed"); // 2判斷RealCall是否正在請求 executed = true; } captureCallStackTrace(); timeout.enter(); eventListener.callStart(this); try { client.dispatcher().executed(this); //3 將請求對象加入到Dispatcher的正在執行同步請求隊列 Response result = getResponseWithInterceptorChain(); //4 獲取攔截器鏈 if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); //5 將請求對象從Dispatcher對象的隊列中移除。 } } 複製代碼
這個方法重要的操做都加入了註釋,其中註釋4處是最重要的,能夠當作OkHttp
框架最精華的部分,這個方法攔截器鏈,咱們看一下這個方法。
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); //開發者自定義攔截器 interceptors.add(retryAndFollowUpInterceptor); //失敗重連攔截器,在初始化RealCall對象時同步初始化 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); } 複製代碼
咱們就知道經過執行攔截器鏈能夠獲取請求結果,咱們看一下這個鏈接器鏈是運行流程。
/** * A concrete interceptor chain that carries the entire interceptor chain: all application * interceptors, the OkHttp core, all network interceptors, and finally the network caller. */ public final class RealInterceptorChain implements Interceptor.Chain { private final List<Interceptor> interceptors; private final StreamAllocation streamAllocation; private final HttpCodec httpCodec; private final RealConnection connection; private final int index; private final Request request; private final Call call; private final EventListener eventListener; private final int connectTimeout; private final int readTimeout; private final int writeTimeout; private int calls; public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call, EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) { this.interceptors = interceptors; this.connection = connection; this.streamAllocation = streamAllocation; this.httpCodec = httpCodec; this.index = index; this.request = request; this.call = call; this.eventListener = eventListener; this.connectTimeout = connectTimeout; this.readTimeout = readTimeout; this.writeTimeout = writeTimeout; } ... 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"); } // Call the next interceptor in the chain. // 請求下一個下一個攔截器 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); // 獲取當前位置的攔截器 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 is not null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } //返回請求結果 return response; } } 複製代碼
看到這裏咱們就已經知道了OkHttp
經過攔截鏈的做用過程:從第一個攔截器開始,一層一層將請求傳遞下去;在得到響應以後,由最再一層一層傳遞上來,最終返回出去。這種傳遞方式是否是有點似曾相識?沒錯,在咱們學習事件分發機制的時候就遇到過這種傳遞方式。其實這種編碼方式叫責任鏈設計模式,你們先了解一下,後面還會講。
上面咱們已經看過同步請求方法execute
,接下來讓咱們看一下異步請求方法enqueue
。
@Override public void enqueue(Callback responseCallback) { synchronized (this) { // 一、加同步鎖 if (executed) throw new IllegalStateException("Already Executed"); //二、檢查當前請求是否正在執行 executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); //三、執行Dispatcher.enqueue方法。 } public interface Callback { //請求失敗回調 void onFailure(Call call, IOException e); //獲取響應回調 void onResponse(Call call, Response response) throws IOException; } 複製代碼
一、調用enqueue方法時傳入一個
Callback
,這個Callback
內部有兩個回調方法。
二、加入同步鎖,檢查當前請求是否正在執行。
三、建立一個AsyncCall
(異步請求對象),在調用Dispatcher對象的enqueue
方法時將其傳入。
void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); //將異步請求對象加入準備運行的異步請求集合中 } promoteAndExecute(); } private boolean promoteAndExecute() { assert (!Thread.holdsLock(this)); // 一、新建一個可執行的異步請求集合 List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { // 二、循環遍歷將要運行的異步請求集合 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); // 三、判斷正在執行的異步請求個數是否大於最大的請求個數 if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. // 四、判斷當前執行的異步請求Host個數是否大於Host的限制 if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity. // 五、將當前異步請求移除 i.remove(); // 六、將異步請求加入到可執行的異步請求集合中 executableCalls.add(asyncCall); // 七、在正在運行的異步請求集合中添加異步請求對象 runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } // 八、循環遍歷可執行的異步請求集合 for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); // 九、建立線程池並執行請求 asyncCall.executeOn(executorService()); } return isRunning; } 複製代碼
在Dispatcher.enqueue
方法中只作了兩件事:
一、將異步請求對象加入到準備執行的異步請求集合中。
二、執行promoteAndExecute
方法。
在執行promoteAndExecute方法方法時又作了一下幾件事:
一、新建一個可執行的異步請求集合。
二、循環遍歷準備運行的異步請求集合。
三、判斷正在執行的異步請求個數是否大於最大的請求個數,若是不符合,直接跳出循環。
四、判斷當前執行的異步請求Host
個數是否大於Host
的限制,若是不符合,結束當前循環,進入下一次循環。
五、以上判斷都經過後,將當前異步請求移除。
六、將異步請求加入到可執行的異步請求集合中。
七、將異步請求對象加入到正在運行的異步請求集合中。
八、循環遍歷可執行的異步請求集合。
九、建立線程池並執行請求。
final class RealCall implements Call { ... final class AsyncCall extends NamedRunnable { ... void executeOn(ExecutorService executorService) { assert (!Thread.holdsLock(client.dispatcher())); boolean success = false; try { executorService.execute(this); success = true; } catch (RejectedExecutionException e) { InterruptedIOException ioException = new InterruptedIOException("executor rejected"); ioException.initCause(e); eventListener.callFailed(RealCall.this, ioException); responseCallback.onFailure(RealCall.this, ioException); } finally { if (!success) { client.dispatcher().finished(this); // This call is no longer running! } } } @Override protected void execute() { boolean signalledCallback = false; timeout.enter(); 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) { e = timeoutExit(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); } } ... } ... } public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); } 複製代碼
咱們先講一下NamedRunnable
類:
一、這個類繼承了
Runnable
接口,通知重寫了run
方法。
二、定義了一個抽象方法execute
,在run
方法中調用了execute
方法。
而後咱們看一下AsyncCall
類:
一、這個對象繼承自
NamedRunnable
類,並重寫了execute
方法。這裏要注意的是這個exectue
方法不是本篇文章在使用OkHttp
作網絡請求舉例中調用的execute
方法。
二、當執行AsyncCall.executeOn
方法時傳入了一個線程池,由這個線程池執行任務,並將當前的AsyncCall
對象傳入。
三、回想一下咱們在學習線程池的時候,線程池的execute
方法傳入一個Runnable
對象以後,會調用Runnable
對象的run
方法。在這裏,AsyncCall
的父類是NamedRunnable
,它實現了Runnable
接口,而且重寫了run
方法,在run
方法中又調用了它的抽象方法excute
方法,這個抽象方法在AsyncCall
對象中實現了。因此,最後網絡請求的調用又來到了AsyncCall.execute
方法。
四、在這個方法中咱們看到了一個熟悉的身影getResponseWithInterceptorChain
,這個方法在上面已經分析過,這裏就再也不作贅述。
分析了這裏多,咱們來作一下小結吧。
一、同步請求和異步請求的區別在於,同步請求沒有使用線程池,而異步請求會放入到線程池中去執行。
二、同步請求和異步請求最終都會調用getResponseWithInterceptorChain
方法進行網絡請求。
三、getResponseWithInterceptorChain
方法會依次執行攔截器,將請求一層層向下傳遞,獲得網絡響應後再一層層向上傳遞(以下圖所示),這種編碼屬於責任鏈設計模式。
至此,okhttp
的執行流程已經講完了,因爲篇幅的緣由,有關okhttp
中的攔截器和涉及到的設計模式相關知識點會在後續文章中進行補充。本人資歷尚淺,能力有限,若是文章哪裏寫的不對,歡迎你們拍磚,本人將不勝感激。