Okhttpソース分析--基本使用フロー分析
27110 ワード
Okhttpソース分析--基本使用フロー分析
一、使用
同期要求
非同期リクエスト
同期または非同期リクエストにかかわらずokhttpを使用するには、大きく3つのステップに分けられます.1.okhttpclientを作成する.2.リクエストのrequestを作成する.3.clientを介してcallを取得し、リクエストを送信する.
注意:okhttpclientとrequestの作成には、コンストラクタモードが使用できます.コンストラクタでは、okhttpclientコンストラクション時にカスタムブロッカーを追加したり、requestコンストラクション中に接続タイムアウト時間を設定したりするなど、実際のニーズに応じてパラメータを設定できます.
二、ソース分析
まずOkhttpClientというクラスを見て、手順の最初のステップはOkhttpClientオブジェクトを構築することです.
OkhttpClientの定義を先に貼ります
OkhttpClientはリクエストの送信と応答の読み取りに使用され、公式には単一の例のOkHttpClientを作成し、すべてのhttpリクエストを多重化することを提案しています.OkHttpClientごとに独自のconnection pool and thread poolがあるからです.接続pool and thread poolを多重化するとメモリが節約され、逆にリクエストごとにOkHttpClientを作成するとidle poolsのリソースが浪費されます.
OkHttpClientを作成するには、1、コンストラクション関数2、Builder()でclientを作成して設定をカスタマイズする方法があります.
また、newBuilder()は、共有するclientと同じconnection pool,thread pools,and configurationを持つclientを定義するものも提供されています.この方法で特定の設定のclientを取得できます.
OkHttpClientは無パラメトリック関数を提供し、コードからOkHttpClientの有パラメトリック関数を内部で呼び出し、有パラメトリック関数でOkHttpClientの主要属性を初期化して付与したことがわかる.
次にOkHttpClientの主な属性を貼ります
ソースコードを参照すると、OkHttpClientはコンストラクタ設計モードを採用しており、パラメータ設定を簡素化し、使用コストを削減していることがわかります.例えば私たちが前に簡単に使った例
OkHttpClientクラスのもう1つの理解すべき関数はnewCallです.OkHttpClientはCall.Factoryインタフェースを実装するため、newCallメソッドを上書きし、メソッドの内部にRealCallインスタンスを返します.
OkHttpClientが構築されたら、次はrequestを作成し、requestは私たちが送信するリクエストです.ビルダーモードで構築されています.以下にRequestの主なプロパティとそのコンストラクション関数を貼ります.
リクエストをRequestで取得し、次にcallインスタンスを取得してリクエストを送信します.OkHttpClientクラスを紹介する際に我々はすでにcallオブジェクトがOkHttpClientのnewCallメソッドによって得られた実際の戻りはRealCallオブジェクトであり,すなわち実際に送信されたリクエストはRealCallであると述べているが,RealCallというクラスを見てみる
RealCallが実装したCallインタフェースは、newCall関数の内部でRealCallのコンストラクション関数によって1つのcallをインスタンス化し、callを返します.最後にリクエストを送信します.同期と非同期の2つの方法があります.まず、同期リクエストの方法を見てみましょう.同期リクエストはexecuteで送信されます.
executeはまず(注釈1で)synchronizedを使用してexecuted値をチェックし、各リクエストが1回しか実行できないことを確認します.その後、dispatcherのexecuted(注釈2)が呼び出される.Dispatcherというクラスを見てみましょう
okhttpはコンカレントリクエストをサポートしているが、最大コンカレントリクエスト数の制限があることがわかる.さらにokhttpは、異なるリクエスト方式に対して異なるリクエストキューを提供する.dispatcherというクラスの主な役割はrequestのリクエスト方式と現在のclientの実行状況に基づいて新しく作成されたcallリクエストを異なるキューに配布して実行することである.
dispatcherクラスの役割を理解したexectued関数を見てみましょう
簡単には、受信したcallオブジェクトを同期要求キューに追加するだけです(runningSyncCalls).では、リクエストは具体的にどのように送信されますか?RealCallのexectued関数を見てみましょう.注記3でgetResponseWithInterceptorChain()を呼び出してresponeを取得し、そのresponeを返します.
getResponseWithInterceptorChainは、まずカスタムおよびokhttp定義のブロッキングをinterceptorsのlistに追加し、RealInterceptorChainブロッキングチェーンを構築し、chain.proceedチェーンを呼び出して各ブロッキングを呼び出し、最終的にresponeを取得します.
Interceptorはokhttpのコアメカニズムの一つと言えるので、一緒に見てみましょう.
これはokhttp定義のインタフェースクラスであり、okhttpは5つの実装クラスを提供している.彼らはgetResponseWithInterceptorChain()にinterceptorsに追加された5つのInterceptorであり、彼らの役割はそれぞれ異なり、これは後で単独で分析される.また、独自のブロッキングをカスタマイズすることもできます.ブロッキングの概念を理解した後、RealInterceptorChainとそのproceed関数を見てみましょう.
RealInterceptorChainは、アプリケーション全体のブロッキングおよびネットワークブロッキングを持つブロッキングチェーンとして機能します.このクラスのproceedメソッドは、実際には、クラスのリロードを呼び出すproceedメソッド(注釈1)である.注記2では、ブロッキングチェーンの次のブロッキングを呼び出します.ここでnewはRealInterceptorChainであり、ここで入力されたindexに1(getResponseWithInterceptorChainが入力したindexは0)が加えられていることに注意し、これはブロッキングチェーン内の次のブロッキングのindexを表し、その後indexに基づいて現在のブロッキングを取得し、そのinterceptメソッドを呼び出す.interceptはインタフェースInterceptorの1つの方法であり,具体的な実装クラスによって実現される.ここではRetryAndFollowUpInterceptorを例にinterceptメソッドで何をしたかを見る.
RetryAndFollowUpInterceptorのinterceptメソッドでは、受信したnext(すなわち、ブロッキングチェーン内の現在のブロッキングの次のブロッキング)のproceedメソッドが呼び出され、chain内のすべてのブロッキングがチェーン的に順次呼び出され、各ブロッキングが自分のタスクを実行して最終的にresponeに戻ることができます.このresponeはRealCallのgetResponseWithInterceptorChainによってexecuteメソッドに戻り,最終的に我々が得たresponeになる.これで同期要求はresponeを獲得し,最後の操作はRealCallのexecuteメソッドでfinishedメソッドを呼び出すことである.
この方法はdispatcherが提供する
promoteAndExecute()という方法を見てみましょう
この方法は主に2つのforループであり、まず最初のforループ(注釈1)が準備準備準備キューを巡回し、空ではなく一定の条件を満たす場合はexecutableCallsに追加されるが、同期要求時に準備準備準備キュー(readyAsyncCalls)が空であり、executableCallsも空であるため、両方のforループは入らない(実際にはこの方法は非同期要求のために準備された)関数は最終的にfalseを返す.
dispatcherのfinish()メソッドに戻ると、promoteAndExecute()の戻り値とidleCallbackが空であるかどうかを判断し、isRuningがfalseであり、idleCallbackが空でない場合はidleCallbackを実行します.そうしないと、何もしません.
これで同期要求プロセスの分析が完了します.
非同期リクエスト
非同期リクエストと同期リクエストの違いはRealCallを呼び出すenqueueメソッドです
メソッドの内部でdispatcherのenqueueを呼び出し、AsyncCallパラメータを入力します.まず、AsyncCallを見てから非同期要求プロセスを分析します.
AsyncCallはRealCallの内部クラスですNamedRunnableから継承されています
NamedRunnableは、実はRunnableを実装するスレッドクラスです.runメソッドで提供される抽象関数execute()が呼び出され、execute()の実装はAsyncCallです.
これは同期要求のプロセスと同じであることがわかります.まずgetResponseWithInterceptorChain()チェーン呼び出しブロックチェーンはresopneを取得し、その後callbackコールバックの結果、最後にfinishedを呼び出す.AsyncCallを分析した後、非同期要求の実現はスレッドを通じてCallを実行することであり、要求の実行過程は同期要求と同じであり、最後にcallbcakを通じて戻ることであると大まかに推測できる.
では、具体的な非同期リクエストプロセスを見てみましょう.前にdispatcherのenqueueメソッドについて話しましたが、このメソッドは何をしたのか見てみましょう.
簡単に方法の内部でまずcallを非同期準備準備準備キューに追加してpromoteAndExecuteを呼び出しました.promoteAndExecute私たちは以前同期要求でその内部が主に2つのforループであることを分析しました.同期要求の時、この2つのforループは条件を満たしていません.では、非同期要求を見てみましょう.
非同期準備準備キュー(readyAsyncCalls)にcallを追加したためを選択すると、最初のforは入力できます.最初のforループの内部では、まず、現在の準備完了キューのcallが最大リクエスト数、すなわち最大同時リクエスト数に達したかどうかを判断し、次に、単一hostが最大リクエスト数に達したかどうかを判断します.その後、現在のcallをexecutableCallsとrunningAsyncCallsの2つのキューに追加します.その後、2番目のforループに入りますループ、このforループでcallオブジェクトを順次取り出してexecuteO関数を呼び出します.execute関数に入力されたexecuterServiceはスレッドプールであることに注意してください.
executorServiceは非コアスレッドのみで、非コアスレッド数が無限大のスレッドプールであることがわかります.わかりました.executorServiceは戻ってexecuteOnを見て、executeOnでexecutorService.execute(this)を呼び出します.,executeOnはAsyncCall内部の関数であり,AsyncCallはスレッドクラスであるため,この操作はスレッドのrunメソッドを実行する.ここでは具体的にはNamedRunnableのrunメソッドであり,このrunメソッドではexecute()メソッドが呼び出され,execute()は同期要求プロセスと同様にチェーン呼び出しブロッカーが最終的にresponeを取得することを解析した.
これで非同期リクエストプロセスも分析済みです.
最後に、Okhttpのリクエストフローについてまとめます.まず、同期でも非同期でもOkhttpClientを初期化してからRequest、Callになります.異なる点は、同期リクエストがrunningSyncCallsにリクエストを追加してからexecuteを直接呼び出し、チェーンコールブロッカーがresponeを取得して返します.非同期リクエストはreadyAsyncCallsにリクエストを追加してから実行します行のときにrunningAsyncCallsに追加し、要求をサブスレッドに配置して実行します.すなわち、チェーン呼び出しブロッカーがresponeを取得するのはサブスレッドで完了します.また、同期や非同期リクエストが完了するとCallが削除されます.以上はOkhttpの最も簡単なプロセス分析ですが、Okhttpには学ぶべきことがたくさんあります.その後、私はOkhttpをより深く理解するために、関連コンテンツを更新し続けます.
一、使用
同期要求
OkHttpClient okHttpClient=new OkHttpClient();
Request request=new Request.Builder()
.get()
.url("www.baidu.com")
.build();
Call call =okHttpClient.newCall(request).execute();
非同期リクエスト
OkHttpClient okHttpClient=new OkHttpClient();
Request request=new Request.Builder()
.get()
.url("www.baidu.com")
.build();
Call call=okHttpClient.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure: ");
}
@Override public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG, "onResponse: ");
}
});
同期または非同期リクエストにかかわらずokhttpを使用するには、大きく3つのステップに分けられます.1.okhttpclientを作成する.2.リクエストのrequestを作成する.3.clientを介してcallを取得し、リクエストを送信する.
注意:okhttpclientとrequestの作成には、コンストラクタモードが使用できます.コンストラクタでは、okhttpclientコンストラクション時にカスタムブロッカーを追加したり、requestコンストラクション中に接続タイムアウト時間を設定したりするなど、実際のニーズに応じてパラメータを設定できます.
二、ソース分析
まずOkhttpClientというクラスを見て、手順の最初のステップはOkhttpClientオブジェクトを構築することです.
OkhttpClientの定義を先に貼ります
*Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
* responses.
* OkHttpClients should be shared
* OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
* all of your HTTP calls. This is because each client holds its own connection pool and thread
* pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
* client for each request wastes resources on idle pools.
OkhttpClientはリクエストの送信と応答の読み取りに使用され、公式には単一の例のOkHttpClientを作成し、すべてのhttpリクエストを多重化することを提案しています.OkHttpClientごとに独自のconnection pool and thread poolがあるからです.接続pool and thread poolを多重化するとメモリが節約され、逆にリクエストごとにOkHttpClientを作成するとidle poolsのリソースが浪費されます.
OkHttpClientを作成するには、1、コンストラクション関数2、Builder()でclientを作成して設定をカスタマイズする方法があります.
また、newBuilder()は、共有するclientと同じconnection pool,thread pools,and configurationを持つclientを定義するものも提供されています.この方法で特定の設定のclientを取得できます.
OkHttpClientは無パラメトリック関数を提供し、コードからOkHttpClientの有パラメトリック関数を内部で呼び出し、有パラメトリック関数でOkHttpClientの主要属性を初期化して付与したことがわかる.
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;
...// N
}
次にOkHttpClientの主な属性を貼ります
public class OkHttpClient{
final Dispatcher dispatcher;//
final @Nullable Proxy proxy;//
final List protocols;//
final List connectionSpecs;//
final List interceptors;// (okhttp )
final List networkInterceptors;//
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;//
final CookieJar cookieJar;//cookie
final @Nullable
Cache cache;//cache
final @Nullable
InternalCache internalCache;//
final SocketFactory socketFactory;//socket
final @Nullable
SSLSocketFactory sslSocketFactory;// socket https
final @Nullable
CertificateChainCleaner certificateChainCleaner;// , HTTPS
final HostnameVerifier hostnameVerifier;//
final CertificatePinner certificatePinner;//
final Authenticator proxyAuthenticator;//
final Authenticator authenticator;//
final ConnectionPool connectionPool;//
final Dns dns; //
final boolean followSslRedirects;//
final boolean followRedirects;//
final boolean retryOnConnectionFailure;//
final int connectTimeout;//
final int readTimeout;//
final int writeTimeout;//
}
ソースコードを参照すると、OkHttpClientはコンストラクタ設計モードを採用しており、パラメータ設定を簡素化し、使用コストを削減していることがわかります.例えば私たちが前に簡単に使った例
OkHttpClientクラスのもう1つの理解すべき関数はnewCallです.OkHttpClientはCall.Factoryインタフェースを実装するため、newCallメソッドを上書きし、メソッドの内部にRealCallインスタンスを返します.
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
OkHttpClientが構築されたら、次はrequestを作成し、requestは私たちが送信するリクエストです.ビルダーモードで構築されています.以下にRequestの主なプロパティとそのコンストラクション関数を貼ります.
public final class Request {
final HttpUrl url;// url
final String method;//
final Headers headers;//
final @Nullable RequestBody body;// body
final Map, Object> tags;// tags tags tag
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);
}
...
}
リクエストをRequestで取得し、次にcallインスタンスを取得してリクエストを送信します.OkHttpClientクラスを紹介する際に我々はすでにcallオブジェクトがOkHttpClientのnewCallメソッドによって得られた実際の戻りはRealCallオブジェクトであり,すなわち実際に送信されたリクエストはRealCallであると述べているが,RealCallというクラスを見てみる
final class RealCall implements Call {
final OkHttpClient client; //realcall client
private Transmitter transmitter;//
/** The application's original request unadulterated by redirects or auth headers. */
final Request originalRequest;//
final boolean forWebSocket;//
// Guarded by this.
private boolean executed;//
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { //
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {//okhttpclient call
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
RealCallが実装したCallインタフェースは、newCall関数の内部でRealCallのコンストラクション関数によって1つのcallをインスタンス化し、callを返します.最後にリクエストを送信します.同期と非同期の2つの方法があります.まず、同期リクエストの方法を見てみましょう.同期リクエストはexecuteで送信されます.
@Override public Response execute() throws IOException {
synchronized (this) {//1
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
client.dispatcher().executed(this);//2
return getResponseWithInterceptorChain();//3
} finally {
client.dispatcher().finished(this);//4
}
}
executeはまず(注釈1で)synchronizedを使用してexecuted値をチェックし、各リクエストが1回しか実行できないことを確認します.その後、dispatcherのexecuted(注釈2)が呼び出される.Dispatcherというクラスを見てみましょう
public final class Dispatcher {
private int maxRequests = 64;//
private int maxRequestsPerHost = 5;// host
private @Nullable Runnable idleCallback;//
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; //
/** Ready async calls in the order they'll be run. */
private final Deque readyAsyncCalls = new ArrayDeque<>();//
/** Running asynchronous( ) calls. Includes canceled calls that haven't finished yet. */
private final Deque runningAsyncCalls = new ArrayDeque<>();//
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque runningSyncCalls = new ArrayDeque<>();//
okhttpはコンカレントリクエストをサポートしているが、最大コンカレントリクエスト数の制限があることがわかる.さらにokhttpは、異なるリクエスト方式に対して異なるリクエストキューを提供する.dispatcherというクラスの主な役割はrequestのリクエスト方式と現在のclientの実行状況に基づいて新しく作成されたcallリクエストを異なるキューに配布して実行することである.
dispatcherクラスの役割を理解したexectued関数を見てみましょう
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
簡単には、受信したcallオブジェクトを同期要求キューに追加するだけです(runningSyncCalls).では、リクエストは具体的にどのように送信されますか?RealCallのexectued関数を見てみましょう.注記3でgetResponseWithInterceptorChain()を呼び出してresponeを取得し、そのresponeを返します.
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());// client interceptor interceptors
interceptors.add(new RetryAndFollowUpInterceptor(client));//
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));// CallServer
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());//
RealInterceptorChain , interceptors
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);// proceed respone
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;// respone
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
getResponseWithInterceptorChainは、まずカスタムおよびokhttp定義のブロッキングをinterceptorsのlistに追加し、RealInterceptorChainブロッキングチェーンを構築し、chain.proceedチェーンを呼び出して各ブロッキングを呼び出し、最終的にresponeを取得します.
Interceptorはokhttpのコアメカニズムの一つと言えるので、一緒に見てみましょう.
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
これはokhttp定義のインタフェースクラスであり、okhttpは5つの実装クラスを提供している.彼らはgetResponseWithInterceptorChain()にinterceptorsに追加された5つのInterceptorであり、彼らの役割はそれぞれ異なり、これは後で単独で分析される.また、独自のブロッキングをカスタマイズすることもできます.ブロッキングの概念を理解した後、RealInterceptorChainとそのproceed関数を見てみましょう.
public final class RealInterceptorChain implements Interceptor.Chain {
private final List interceptors;// list
private final Transmitter transmitter;
private final @Nullable Exchange exchange;
private final int index;
private final Request request;//
private final Call call;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
public RealInterceptorChain(List interceptors, Transmitter transmitter,
@Nullable Exchange exchange, int index, Request request, Call call,
int connectTimeout, int readTimeout, int writeTimeout) {//
this.interceptors = interceptors;
this.transmitter = transmitter;
this.exchange = exchange;
this.index = index;
this.request = request;
this.call = call;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
@Override public Response proceed(Request request) throws IOException {//proceed proceed
return proceed(request, transmitter, exchange);
}
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {//1
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.exchange != null && !this.exchange.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.exchange != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 2
//Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, 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 (exchange != 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");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
RealInterceptorChainは、アプリケーション全体のブロッキングおよびネットワークブロッキングを持つブロッキングチェーンとして機能します.このクラスのproceedメソッドは、実際には、クラスのリロードを呼び出すproceedメソッド(注釈1)である.注記2では、ブロッキングチェーンの次のブロッキングを呼び出します.ここでnewはRealInterceptorChainであり、ここで入力されたindexに1(getResponseWithInterceptorChainが入力したindexは0)が加えられていることに注意し、これはブロッキングチェーン内の次のブロッキングのindexを表し、その後indexに基づいて現在のブロッキングを取得し、そのinterceptメソッドを呼び出す.interceptはインタフェースInterceptorの1つの方法であり,具体的な実装クラスによって実現される.ここではRetryAndFollowUpInterceptorを例にinterceptメソッドで何をしたかを見る.
public final class RetryAndFollowUpInterceptor implements Interceptor {
private final OkHttpClient client;// client
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();// chain request request next request
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
response = realChain.proceed(request, transmitter, null);// next proceed
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
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, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
Request followUp = followUpRequest(response, route);//
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;// respone
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
}
RetryAndFollowUpInterceptorのinterceptメソッドでは、受信したnext(すなわち、ブロッキングチェーン内の現在のブロッキングの次のブロッキング)のproceedメソッドが呼び出され、chain内のすべてのブロッキングがチェーン的に順次呼び出され、各ブロッキングが自分のタスクを実行して最終的にresponeに戻ることができます.このresponeはRealCallのgetResponseWithInterceptorChainによってexecuteメソッドに戻り,最終的に我々が得たresponeになる.これで同期要求はresponeを獲得し,最後の操作はRealCallのexecuteメソッドでfinishedメソッドを呼び出すことである.
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
client.dispatcher().executed(this);
return getResponseWithInterceptorChain();
} finally {
client.dispatcher().finished(this);// respone finish
}
}
この方法はdispatcherが提供する
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
private void finished(Deque calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");// call
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();//1
if (!isRunning && idleCallback != null) {
idleCallback.run();// isRuning false idleCallback idleCallback。
}
}
promoteAndExecute()という方法を見てみましょう
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {//1
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {//2
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
この方法は主に2つのforループであり、まず最初のforループ(注釈1)が準備準備準備キューを巡回し、空ではなく一定の条件を満たす場合はexecutableCallsに追加されるが、同期要求時に準備準備準備キュー(readyAsyncCalls)が空であり、executableCallsも空であるため、両方のforループは入らない(実際にはこの方法は非同期要求のために準備された)関数は最終的にfalseを返す.
dispatcherのfinish()メソッドに戻ると、promoteAndExecute()の戻り値とidleCallbackが空であるかどうかを判断し、isRuningがfalseであり、idleCallbackが空でない場合はidleCallbackを実行します.そうしないと、何もしません.
これで同期要求プロセスの分析が完了します.
非同期リクエスト
非同期リクエストと同期リクエストの違いはRealCallを呼び出すenqueueメソッドです
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));// dispatcher enqueue
}
メソッドの内部でdispatcherのenqueueを呼び出し、AsyncCallパラメータを入力します.まず、AsyncCallを見てから非同期要求プロセスを分析します.
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
}
AsyncCallはRealCallの内部クラスですNamedRunnableから継承されています
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()が呼び出され、execute()の実装はAsyncCallです.
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
これは同期要求のプロセスと同じであることがわかります.まずgetResponseWithInterceptorChain()チェーン呼び出しブロックチェーンはresopneを取得し、その後callbackコールバックの結果、最後にfinishedを呼び出す.AsyncCallを分析した後、非同期要求の実現はスレッドを通じてCallを実行することであり、要求の実行過程は同期要求と同じであり、最後にcallbcakを通じて戻ることであると大まかに推測できる.
では、具体的な非同期リクエストプロセスを見てみましょう.前にdispatcherのenqueueメソッドについて話しましたが、このメソッドは何をしたのか見てみましょう.
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();//
}
簡単に方法の内部でまずcallを非同期準備準備準備キューに追加してpromoteAndExecuteを呼び出しました.promoteAndExecute私たちは以前同期要求でその内部が主に2つのforループであることを分析しました.同期要求の時、この2つのforループは条件を満たしていません.では、非同期要求を見てみましょう.
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {// call
AsyncCall asyncCall = i.next();
// host
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
// call
i.remove();
asyncCall.callsPerHost().incrementAndGet();// call host
executableCalls.add(asyncCall);// executableCalls
runningAsyncCalls.add(asyncCall);//
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {//executableCalls ,
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());//call
}
return isRunning;
}
非同期準備準備キュー(readyAsyncCalls)にcallを追加したためを選択すると、最初のforは入力できます.最初のforループの内部では、まず、現在の準備完了キューのcallが最大リクエスト数、すなわち最大同時リクエスト数に達したかどうかを判断し、次に、単一hostが最大リクエスト数に達したかどうかを判断します.その後、現在のcallをexecutableCallsとrunningAsyncCallsの2つのキューに追加します.その後、2番目のforループに入りますループ、このforループでcallオブジェクトを順次取り出してexecuteO関数を呼び出します.execute関数に入力されたexecuterServiceはスレッドプールであることに注意してください.
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
executorServiceは非コアスレッドのみで、非コアスレッド数が無限大のスレッドプールであることがわかります.わかりました.executorServiceは戻ってexecuteOnを見て、executeOnでexecutorService.execute(this)を呼び出します.,executeOnはAsyncCall内部の関数であり,AsyncCallはスレッドクラスであるため,この操作はスレッドのrunメソッドを実行する.ここでは具体的にはNamedRunnableのrunメソッドであり,このrunメソッドではexecute()メソッドが呼び出され,execute()は同期要求プロセスと同様にチェーン呼び出しブロッカーが最終的にresponeを取得することを解析した.
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);// call
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
これで非同期リクエストプロセスも分析済みです.
最後に、Okhttpのリクエストフローについてまとめます.まず、同期でも非同期でもOkhttpClientを初期化してからRequest、Callになります.異なる点は、同期リクエストがrunningSyncCallsにリクエストを追加してからexecuteを直接呼び出し、チェーンコールブロッカーがresponeを取得して返します.非同期リクエストはreadyAsyncCallsにリクエストを追加してから実行します行のときにrunningAsyncCallsに追加し、要求をサブスレッドに配置して実行します.すなわち、チェーン呼び出しブロッカーがresponeを取得するのはサブスレッドで完了します.また、同期や非同期リクエストが完了するとCallが削除されます.以上はOkhttpの最も簡単なプロセス分析ですが、Okhttpには学ぶべきことがたくさんあります.その後、私はOkhttpをより深く理解するために、関連コンテンツを更新し続けます.