安卓总结 之 OkHttp使用及源码分析(三)
本章主要介紹Okhttp的使用和和源碼分析
一. 準備工作
在gradle中添加依賴
implementation 'com.squareup.okio:okio:1.7.0'implementation 'com.squareup.okhttp3:okhttp:3.2.0'添加網絡權限
<uses-permission android:name="android.permission.INTERNET"/>二. 常見用法
只是添加了一個FormBody
定義文件類型再調用RequestBody方法就ok
調用response.body().byteStream();得到輸入流然后就是文件操作了
在okHttp中,所有所有對于連接的初始化操作都在OkHttpClient.Builder進行,這里設置了超市時間和緩存。
當用戶離開應用程序或者跳轉到其他頁面的時候,我們可以取消任務節省網絡資源。
簡單的方法就是在Request.Builder.tag中分配一個標簽,然后我們就能用OkHtppClient.cancel(Object tag)來取消任務
三. OkHttp更好的封裝
public class OkHttpEngine {private static volatile OkHttpEngine mInstance;private OkHttpClient mOkHttpClient;private Handler mHandler;// 雙重檢驗鎖創建OkHttpEngine單例public static OkHttpEngine getInstance(Context context) {if (mInstance == null) {synchronized (OkHttpEngine.class) {if (mInstance == null) {mInstance = new OkHttpEngine(context);}}}return mInstance;}// 在構造函數中初始化Client和handlerprivate OkHttpEngine(Context context) {File sdcache = context.getExternalCacheDir();int cacheSize = 10 * 1024 * 1024;OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));mOkHttpClient=builder.build();mHandler = new Handler();}/*** 異步get請求* @param url* @param callback*/public void getAsynHttp(String url, ResultCallback callback) {final Request request = new Request.Builder().url(url).build();Call call = mOkHttpClient.newCall(request);dealResult(call, callback);}private void dealResult(Call call, final ResultCallback callback) {call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {sendFailedCallback(call.request(), e, callback);}@Overridepublic void onResponse(Call call, Response response) throws IOException {sendSuccessCallback(response.body().string(), callback);}private void sendSuccessCallback(final String str, final ResultCallback callback) {mHandler.post(new Runnable() {@Overridepublic void run() {if (callback != null) {try {callback.onResponse(str);} catch (IOException e) {e.printStackTrace();}}}});}private void sendFailedCallback(final Request request, final Exception e, final ResultCallback callback) {mHandler.post(new Runnable() {@Overridepublic void run() {if (callback != null)callback.onError(request, e);}});}});}public abstract class ResultCallback{public abstract void onError(Request request, Exception e);public abstract void onResponse(String str) throws IOException;} }請求網絡的時候是用Handler將請求結果回調給UI線程,所以我們想要請求網絡的時候只需要調用OkHttpEngine的getAsynHttp方法并寫一個ResultCallback回調就可以了。
四. OkHttp的源碼分析
我們從使用開始,一步步剖析OkClient的源碼實現
//下面是一段Kotlin代碼 override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val request = Request.Builder().url("https://www.baidu.com/").method("get", null).build()OkHttpClient().newCall(request).enqueue(object : Callback {override fun onFailure(call: Call?, e: IOException?) {Log.d("onClient", "isBad")}override fun onResponse(call: Call?, response: Response?) {Log.e("onClient", "isOk")}})}我們把重點關注在 OkHttpClient().newCall(request).enqueue() 這個方法中
newCall方法:
// OkHttpClint中的newCall方法@Override public Call newCall(Request request) {return new RealCall(this, request);}實例化一個RealCall類,RealCall實現了Call接口,看一下Call是干什么的
,這里保留了源碼的英文注釋
然后我們繼續看OkHttpClient().newCall(request).enqueue()
的enqueue方法
其中這個參數responseCallback就是源碼介紹開頭中OkHttpClient().newCall(request).enqueue()傳入的callback
我們這里可以看到,真正處理這個Callback的并不是RealCall,而是dispatcher(),這個就是調度器,看看他的類聲明 public final class Dispatcher 是一個不變類,也就是說不能被重寫。
我們看一下調度器Dispatcher中的enqueue方法:
// Dispatcher類的enqueue方法synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}}這個方法用了synchronized聲明。看到這么多全局的變量,感覺有點煩,沒事,其實很簡單,我們看一下Dispatcher的聲明:
// Dispatcher類的成員變量private int maxRequests = 64;private int maxRequestsPerHost = 5;/** Executes calls. Created lazily. */private 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<>();是不是感覺很清晰,很簡單
我們回頭看這段代碼
// Dispatcher類的enqueue方法synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}}很簡單,如果可以這個符合可以運行的條件,就把它加到runningAsyncCalls隊列中,然后調用execute執行,否則加到readyAsyncCalls準備隊列中
ok,我們把關注點放在這個方法中
executorService().execute(call);
我們知道CachedThreadPool實際上就是一個無限容量,但是隊列中只有一個線程的線程池。這種線程池比較適合任務比較多,但是任務比較小的情況。
execute(Runnable run)調度方法最后會調用調用這個runnable的run方法,因為這個call是AsyncCall類,我們看看這個類的run方法:
final class AsyncCall extends NamedRunnable {private final Callback responseCallback;private final boolean forWebSocket;private AsyncCall(Callback responseCallback, boolean forWebSocket) {super("OkHttp %s", originalRequest.url().toString());this.responseCallback = responseCallback;this.forWebSocket = forWebSocket;}@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {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!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}}沒有run方法呀,怎么肥事?看看它繼承的類NamedRunnable搞了什么。
public abstract class NamedRunnable implements Runnable {protected final String name;public NamedRunnable(String format, Object... args) {this.name = String.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(); }原來是它在run方法執行了execute方法,行吧,其實一樣,線程池最后執行的是AsyncCall的execute方法。ojbk,來看一下吧!
// AsyncCall的execute方法@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {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!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}別急,我們一個個往下面看,先看這個方法:getResponseWithInterceptorChain
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);return chain.proceed(originalRequest);}ApplicationInterceptorChain 從名字可以猜測到,他是一個攔截鏈,看一下吧!
// ApplicationInterceptorChain類的proceed方法@Override public Response proceed(Request request) throws IOException {// If there's another interceptor in the chain, call that.if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);// 得到了當前攔截器Interceptor interceptor = client.interceptors().get(index);// 攔截操作,所有攔截器(除了最后一個)都會阻塞到這里Response interceptedResponse = interceptor.intercept(chain);if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor+ " returned null");}return interceptedResponse;}return getResponse(request, forWebSocket);}}看到這段,有點發蒙
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);為什么還要創建一個chain呢,原來index代表當前攔截器所在的位置,比如一共有五個攔截器,攔截器“G”在第二個,那你要做的是將“G”攔截器攔截在第一個攔截器之后。get到了嗎?
Response interceptedResponse = interceptor.intercept(chain);
這個方法為什么會攔住所有的攔截器,大家可能不明白,我就隨便創建一個攔截器(攔截發出的請求和響應的日志)出來,大家就知道了。
大家能夠明白嗎,這里我畫一張圖:
這個有點類似Spirng中的攔截器,其實是一個道理。
ok,我們討論完了攔截器。回到AsyncCall的execute方法。
// AsyncCall的execute方法@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {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!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}} responseCallback.onFailure(RealCall.this, responseCallback.onResponse(RealCall.this, response);這里正是我們重寫的Callback的兩個方法。
好了,做了這么久的前戲,也該開始網絡請求了吧?看這個方法
client.dispatcher().finished(this); 是調度器的finish方法。
這里簡單地把call從運行隊列中移走了。看一下PromoteCalls方法:
// Dispatcher類中的promoteCalls方法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.}}很簡單,running的一個call被消化了,這個隊列也就有了空位置,這時候ready隊列有call乘機上位。等等,說好的網絡請求呢,在哪?是不是哪里疏忽了?
我們分析到AsyncCall的execute方法的時候,還記得它做了什么嗎,它用一大堆攔截鏈攔截他,然后在最后 client.dispatcher().finished(this);對調度器Dispactcher的兩個隊列進行收尾對吧?
于是我們大概分析到網絡請求操作在它們中間!果然在攔截器中間ApplicationInterceptorChain類的proceed方法找到啦!
// ApplicationInterceptorChain類的proceed方法@Override public Response proceed(Request request) throws IOException {// If there's another interceptor in the chain, call that.if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);// 得到了當前攔截器Interceptor interceptor = client.interceptors().get(index);// 攔截操作,所有攔截器(除了最后一個)都會阻塞到這里Response interceptedResponse = interceptor.intercept(chain);if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor+ " returned null");}return interceptedResponse;}return getResponse(request, forWebSocket);}}網絡請求在這里:getResponse(request, forWebSocket);
這個方法又臭又長,這里我把重要的貼出來。
Response getResponse(Request request, boolean forWebSocket) throws IOException {engine.sendRequest();engine.readResponse(); }喵喵喵!就這么短
一個個看啰!
HttpEngine的sendRequest方法也很復雜,主要解決的是緩存問題,我們就不展開了,不然要要要講到明年!
cacheCandidate是上次與服務器交互時緩存的Response,這里的緩存均基于Map。key是請求中url的md5,value是在文件中查到的緩存,頁面置換算法基于LRU。
通過cacheStrategy我們可以得到networkRequest和cacheResponse,代表網絡請求和緩存是否存在。如果networkRequest和cacheResponse都為null的時候,返回504錯誤,當networkRequest為null時,也就是不進行網絡請求時,就可以直接返回緩存,其他情況就請求網絡。
我們粗略看一下readResponse()方法:
public void readResponse() throws IOException {Response networkResponse;if (forWebSocket) {httpStream.writeRequestHeaders(networkRequest);// 讀取網絡響應networkResponse = readNetworkResponse();} else if (!callerWritesRequestBody) {// 檢查緩存是否可用,如果可用則使用當前緩存的Response,關閉網絡連接,釋放連接networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);} else {// Emit the request body's buffer so that everything is in requestBodyOut.if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {bufferedRequestBody.emit();}// Emit the request headers if we haven't yet. We might have just learned the Content-Length.if (sentRequestMillis == -1) {if (OkHeaders.contentLength(networkRequest) == -1&& requestBodyOut instanceof RetryableSink) {long contentLength = ((RetryableSink) requestBodyOut).contentLength();networkRequest = networkRequest.newBuilder().header("Content-Length", Long.toString(contentLength)).build();}httpStream.writeRequestHeaders(networkRequest);}// Write the request body to the socket.if (requestBodyOut != null) {if (bufferedRequestBody != null) {// This also closes the wrapped requestBodyOut.bufferedRequestBody.close();} else {requestBodyOut.close();}if (requestBodyOut instanceof RetryableSink) {httpStream.writeRequestBody((RetryableSink) requestBodyOut);}}networkResponse = readNetworkResponse();}receiveHeaders(networkResponse.headers());// If we have a cache response too, then we're doing a conditional get.if (cacheResponse != null) {if (validate(cacheResponse, networkResponse)) {userResponse = cacheResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).headers(combine(cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();networkResponse.body().close();releaseStreamAllocation();// Update the cache after combining headers but before stripping the// Content-Encoding header (as performed by initContentStream()).InternalCache responseCache = Internal.instance.internalCache(client);responseCache.trackConditionalCacheHit();responseCache.update(cacheResponse, stripBody(userResponse));userResponse = unzip(userResponse);return;} else {closeQuietly(cacheResponse.body());}}userResponse = networkResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();if (hasBody(userResponse)) {maybeCache();userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));}}這個方法做的是解析HTTP響應報頭。如果有緩存而且可用,則用緩存的數據并更新緩存,否則則用網絡請求返回的數據。
總結完了,大家應該會覺得很亂,這里粗略畫個圖幫助大家理解。
總結
以上是生活随笔為你收集整理的安卓总结 之 OkHttp使用及源码分析(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android总结 之 View系统学习
- 下一篇: kotlin学习之伴生对象(十五)