學會這篇OkHttp,花了我一個通宵,也是值了,android原生開發

mb61c1d80e4e114 2022-01-07 08:46:21 阅读数:856

okhttp 花了 通宵 也是 值了

系統自帶攔截器

1. 重試與重定向攔截器 RetryAndFollowUpInterceptor

先說結論吧:

顧名思義,retry 重試,FollowUp 重定向 。這個攔截器處在所有攔截器的第一個,它是用來判定要不要對當前請求進行重試和重定向的,
那麼我們應該關心的是: 什麼時候重試, 什麼時候重定向。並且,它會判斷用戶有沒有取消請求,因為RealCall中有一個cancel方法,可以支持用戶 取消請求(不過這裏有兩種情况,在請求發出之 前取消,和 在之 後取消。如果是在請求之 前取消,那就直接不執行之後的過程,如果是在請求發出去之 後取消,那麼客戶端就會丟弃這一次的 response

重試
RetryAndFollowUpInterceptor的核心方法 interceptor() :

@Override public Response intercept(Chain chain) throws IOException {
…省略
while (true) {
…省略
try {
response = ((RealInterceptorChain) chain).proceed(request,streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false; continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
}
…省略
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
…省略

}
}

上面的代碼中,我只保留了關鍵部分。其中有兩個continue,一個return.
當請求到達了這個攔截器,它會進入一個 while(true)循環,

當發生了 RouteException 异常(這是由於請求尚未發出去,路由异常,連接未成功),就會去判斷 recover方法的返回值,根據返回值决定要不要 continue.
當發生 IOException(請求已經發出去,但是和服務器通信失敗了)之後,同樣去判斷 recover方法的返回值,根據返回值决定要不要 continue.
如果這兩個 continue都沒有執行,就有可能走到最後的 returnresponse結束本次請求. 那麼 是不是要 重試,其判斷邏輯就在 recover()方法內部:

private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);

//todo 1、在配置OkhttpClient是設置了不允許重試(默認允許),則一旦發生請求失敗就不再重試
//The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;

//todo 2、由於requestSendStarted只在http2的io异常中為false,http1則是 true,
//在http1的情况下,需要判定 body有沒有實現UnrepeatableRequestBody接口,而body默認是沒有實現,所以後續instanceOf不成立,不會走return false.
//We can’t send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)
return false;

//todo 3、判斷是不是屬於重試的异常
//This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;

//todo 4、有沒有可以用來連接的路由路線
//No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;

// For failure recovery, use the same route selector with a new connection.
return true;
}

簡單解讀一下這個方法:

  • 如果okhttpClient已經set了不允許重試,那麼這裏就返回false,不再重試。
  • 如果requestSendStarted 只在http2.0的IO异常中是true,不過HTTP2.0還沒普及,先不管他,這裏默認通過。
  • 判斷是否是重試的异常,也就是說,是不是之前重試之後發生了异常。這裏解讀一下,之前重試發生過异常,拋出了Exception,這個 isRecoverable方法會根據這個异常去判定,是否還有必要去重試。
  • 協議异常,如果發生了協議异常,那麼沒必要重試了,你的請求或者服務器本身可能就存在問題,再重試也是白瞎。
  • 超時异常,只是超時而已,直接判定重試(這裏requestSendStartedhttp2才會為true,所以這裏默認就是false)
  • SSL异常,HTTPS證書出現問題,沒必要重試。
  • SSL握手未授權异常,也不必重試

private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 出現協議异常,不能重試
if (e instanceof ProtocolException) {
return false;
}

// requestSendStarted認為它一直為false(不管http2),异常屬於socket超時异常,直接判定可以重試
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted;
}

// SSL握手异常中,證書出現問題,不能重試
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// SSL握手未授權异常 不能重試
if (e instanceof SSLPeerUnverifiedException) {
return false; }
return true;
}

有沒有可以用來連接的路由路線,也就是說,如果當DNS解析域名的時候,返回了多個IP,那麼這裏可能一個一個去嘗試重試,直到沒有更多ip可用。

重定向
依然是 RetryAndFollowUpInterceptor的核心方法 interceptor() 方法,這次我截取後半段:

public Response intercept(Chain chain) throws IOException {
while (true) {
…省略前面的重試判定
//todo 處理3和4xx的一些狀態碼,如301 302重定向
Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}

closeQuietly(response.body());

//todo 限制最大 followup 次數為20次
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException(“Cannot retry streamed HTTP body”, response.code());
}
//todo 判斷是不是可以複用同一份連接
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response

  • " didn’t close its backing stream. Bad interceptor?");
    }
    }
    }

上面源碼中, followUpRequest() 方法中規定了哪些響應碼可以重定向:

private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();

final String method = userResponse.request().method();
switch (responseCode) {
// 407 客戶端使用了HTTP代理服務器,在請求頭中添加 “Proxy-Authorization”,讓代理服務器授權
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException(“Received HTTP_PROXY_AUTH (407) code while not using proxy”);
}
return client.proxyAuthenticator().authenticate(route, userResponse);
// 401 需要身份驗證 有些服務器接口需要驗證使用者身份 在請求頭中添加 “Authorization”
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
// 308 永久重定向
// 307 臨時重定向
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// 如果請求方式不是GET或者HEAD,框架不會自動重定向請求
if (!method.equals(“GET”) && !method.equals(“HEAD”)) {
return null;
}
// 300 301 302 303
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// 如果用戶不允許重定向,那就返回null
if (!client.followRedirects()) return null;
// 從響應頭取出location
String location = userResponse.header(“Location”);
if (location == null) return null;
// 根據location 配置新的請求 url
HttpUrl url = userResponse.request().url().resolve(location);
// 如果為null,說明協議有問題,取不出來HttpUrl,那就返回null,不進行重定向
if (url == null) return null;

Android開發除了flutter還有什麼是必須掌握的嗎?

相信大多數從事Android開發的朋友們越來越發現,找工作越來越難了,面試的要求越來越高了

除了基礎紮實的java知識,數據結構算法,設計模式還要求會底層源碼,NDK技術,性能調優,還有會些小程序和跨平臺,比如說flutter,以思維腦圖的方式展示在下圖;

點擊文檔前往獲取面試資料與視頻教程; 【阿裏P7級別Android架構師技術腦圖+全套視頻】

學會這篇OkHttp,花了我一個通宵,也是值了,android原生開發_Android

版权声明:本文为[mb61c1d80e4e114]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201070846205856.html