日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android网络重试机制,okhttp源码解析(四):重试机制

發(fā)布時間:2023/12/2 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android网络重试机制,okhttp源码解析(四):重试机制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

這一篇我們分析okhttp的重試機制,一般如果網(wǎng)絡請求失敗,我們會考慮連續(xù)請求多次,增大網(wǎng)絡請求成功的概率,那么okhttp是怎么實現(xiàn)這個功能的呢?

正文

首先還是回到之前的InterceptorChain:

Response getResponseWithInterceptorChain() throws IOException {

// Build a full stack of interceptors.

List interceptors = new ArrayList<>();

interceptors.addAll(client.interceptors());

// 重試的Interceptor,在構(gòu)造方法中創(chuàng)建

interceptors.add(retryAndFollowUpInterceptor);

// 其他的interceptor

...

return chain.proceed(originalRequest);

}

其中的RetryAndFollowUpInterceptor是負責重試的Interceptor,他處于責任鏈的頂端,負責網(wǎng)絡請求的開始工作,也負責收尾的工作。

他的創(chuàng)建是在RealCall.java構(gòu)造方法中:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

this.client = client;

this.originalRequest = originalRequest;

this.forWebSocket = forWebSocket;

this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

}

創(chuàng)建的時機就是我們調(diào)用OkhttpClient的newCall方法,每一次發(fā)起網(wǎng)絡請求,我們都需要調(diào)用:

@Override public Call newCall(Request request) {

return RealCall.newRealCall(this, request, false /* for web socket */);

}

了解了他的創(chuàng)建過程,我們接著分析RetryAndFollowUpInterceptor的工作過程:

Request request = chain.request();

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Call call = realChain.call();

// 我們設置的eventListener回調(diào)

EventListener eventListener = realChain.eventListener();

// 從參數(shù)上看,可以推測StreamAllocation中保存了此次網(wǎng)絡請求的信息

// 連接池(),地址,網(wǎng)絡請求,eventListenenr

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),

createAddress(request.url()), call, eventListener, callStackTrace);

this.streamAllocation = streamAllocation;

一開始,創(chuàng)建了StreamAllocation對象,他封裝了網(wǎng)絡請求相關的信息:連接池,地址信息,網(wǎng)絡請求,事件回調(diào),負責網(wǎng)絡連接的連接、關閉,釋放等操作。callStackTrace是一個Throwable對象,他主要是記錄運行中異常信息,幫助我們識別網(wǎng)絡請求的來源。

之后就進入到網(wǎng)絡連接的循環(huán),代碼稍微有點長:

// 計數(shù)器

int followUpCount = 0;

Response priorResponse = null;

// 開始進入while循環(huán)

while (true) {

// 如果請求已經(jīng)被取消了,釋放連接池的資源

if (canceled) {

streamAllocation.release();

throw new IOException("Canceled");

}

Response response;

boolean releaseConnection = true;

try {

// 得到最終的網(wǎng)絡請求結(jié)果

response = realChain.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(), streamAllocation, false, request)) {

throw e.getLastConnectException();

}

releaseConnection = false;

// 繼續(xù)

continue;

} catch (IOException e) {

// An attempt to communicate with a server failed. The request may have been sent.

// 判斷網(wǎng)絡請求是否已經(jīng)開始

boolean requestSendStarted = !(e instanceof ConnectionShutdownException);

// 判斷是否能夠恢復,也就是是否要重試

if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;

releaseConnection = false;

continue;

} finally {

// We're throwing an unchecked exception. Release any resources.

// 釋放連接

if (releaseConnection) {

streamAllocation.streamFailed(null);

streamAllocation.release();

}

}

// Attach the prior response if it exists. Such responses never have a body.

// 如果不為空,保存到response中

if (priorResponse != null) {

response = response.newBuilder()

.priorResponse(priorResponse.newBuilder()

.body(null)

.build())

.build();

}

Request followUp;

try {

// 判斷返回結(jié)果response,是否需要繼續(xù)完善請求,例如證書驗證等等

followUp = followUpRequest(response, streamAllocation.route());

} catch (IOException e) {

streamAllocation.release();

throw e;

}

// 如果不需要繼續(xù)完善網(wǎng)絡請求,返回response

if (followUp == null) {

if (!forWebSocket) {

streamAllocation.release();

}

return response;

}

// 關閉之前的連接

closeQuietly(response.body());

// 如果已經(jīng)超過最大的網(wǎng)絡請求追加數(shù),釋放連接,拋出協(xié)議異常

if (++followUpCount > MAX_FOLLOW_UPS) {

streamAllocation.release();

throw new ProtocolException("Too many follow-up requests: " + followUpCount);

}

// 如果body內(nèi)容只能發(fā)送一次,釋放連接

if (followUp.body() instanceof UnrepeatableRequestBody) {

streamAllocation.release();

throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());

}

// 如果返回response的URL地址和追加請求的url地址不一致

if (!sameConnection(response, followUp.url())) {

// 釋放之前你的url地址連接

streamAllocation.release();

// 創(chuàng)建新的網(wǎng)絡請求封裝對象StreamAllocation

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?");

}

// 更新下一次的網(wǎng)絡請求對象

request = followUp;

// 保存上一次的請求結(jié)果

priorResponse = response;

}

followUpCount是用來記錄我們發(fā)起網(wǎng)絡請求的次數(shù)的,為什么我們發(fā)起一個網(wǎng)絡請求,可能okhttp會發(fā)起多次呢?例如https的證書驗證,我們需要經(jīng)過:發(fā)起 -> 驗證 -> 響應,三個步驟需要發(fā)起至少兩次的請求,或者我們的網(wǎng)絡請求被重定向,在我們第一次請求得到了新的地址后,再向新的地址發(fā)起網(wǎng)絡請求。

但是多次相應的次數(shù)是有限制的,我們看一下okhttp的注釋是怎么解釋的:

/**

* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,

* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.

*/

不同的瀏覽器推薦的次數(shù)是不同的,還特別強調(diào)了HTTP 1.0協(xié)議推薦5次。不過我們一般也不會設置這么多次,這會導致網(wǎng)絡請求的效率很低。

在網(wǎng)絡請求中,不同的異常,重試的次數(shù)也不同,okhttp捕獲了兩種異常:RouteException和IOException。

RouteException:所有網(wǎng)絡連接失敗的異常,包括IOException中的連接失敗異常;

IOException:除去連接異常的其他的IO異常。

這個時候我們需要判斷是否需要重試:

private boolean recover(IOException e, StreamAllocation streamAllocation,

boolean requestSendStarted, Request userRequest) {

streamAllocation.streamFailed(e);

// The application layer has forbidden retries.

// 如果設置了不需要重試,直接返回false

if (!client.retryOnConnectionFailure()) return false;

// We can't send the request body again.

// 如果網(wǎng)絡請求已經(jīng)開始,并且body內(nèi)容只可以發(fā)送一次

if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)

return false;

// This exception is fatal.

// 判斷異常類型,是否要繼續(xù)嘗試,

// 不會重試的類型:協(xié)議異常、Socketet異常并且網(wǎng)絡情況還沒開始,ssl認證異常

if (!isRecoverable(e, requestSendStarted)) return false;

// No more routes to attempt.

// 已經(jīng)沒有其他可用的路由地址了

if (!streamAllocation.hasMoreRoutes()) return false;

// For failure recovery, use the same route selector with a new connection.

// 其他情況返回true

return true;

}

其中的路由地址我們先忽略,這個之后我們還會討論。假定沒有其他路由地址的情況下:

1、連接失敗,并不會重試;

2、如果連接成功,因為特定的IO異常(例如認證失敗),也不會重試

其實這兩種情況是可以理解的,如果連接異常,例如無網(wǎng)絡狀態(tài),重試也只是毫秒級的任務,不會有特別明顯的效果,如果是網(wǎng)絡很慢,到了超時時間,應該讓用戶及時了解失敗的原因,如果一味重試,用戶就會等待多倍的超時時間,用戶體驗并不好。認證失敗的情況就更不用多說了。

如果我們非要重試多次怎么辦?

自定義Interceptor,增加計數(shù)器,重試到你滿意就可以了:

/**

* 重試攔截器

*/

public class RetryInterceptor implements Interceptor {

/**

* 最大重試次數(shù)

*/

private int maxRetry;

RetryInterceptor(int maxRetry) {

this.maxRetry = maxRetry;

}

@Override

public Response intercept(@NonNull Chain chain) throws IOException {

Request request = chain.request();

Response response = null;

int count = 0;

while (count < maxRetry) {

try {

//發(fā)起網(wǎng)絡請求

response = chain.proceed(request);

// 得到結(jié)果跳出循環(huán)

break;

} catch (Exception e) {

count ++;

response = null;

}

}

if(response == null){

throw Exception

}

return response;

}

}

這是一份偽代碼,具體的邏輯大家可以自行完善。

總結(jié)

到這里okhttp的重試機制就分析結(jié)束了,我們發(fā)現(xiàn)只有在特定情況下,okhttp才會重試,如果想要自定義重試機制,可以設置Intercptor來解決這個問題。

接下來我們了解和研究一下okhttp的Dns。

總結(jié)

以上是生活随笔為你收集整理的android网络重试机制,okhttp源码解析(四):重试机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。