blog

OkHttpのソースコード解析 (a)

同じ Request も Builder を通してコンストラクタのインスタンスを生成し、HTTP リクエストに必要な情報の一部をカプセル化します。 Okhttp は newCall を通して Call...

May 21, 2020 · 10 min. read
シェア

リクエストメソッド

OkHttp は同期と非同期の両方の Http リクエストをサポートしています。

同期リクエストと非同期リクエストのコードは以下の通りです:

private final static OkHttpClient client = new OkHttpClient.Builder()
 .connectTimeout(8, TimeUnit.SECONDS)
 .readTimeout(8,TimeUnit.SECONDS)
 .writeTimeout(8,TimeUnit.SECONDS)
 .build();
//同期されたリクエスト
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
 return response.body().string();
} else {
 throw new IOException("Unexpected code " + response);
}
//非同期リクエスト
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
 @Override
 public void onFailure(Call call, IOException e) {
 System.out.println(e.getMessage());
 }
 @Override
 public void onResponse(Call call, Response response) throws IOException {
 if (response.isSuccessful()) {
 return response.body().string();
 }
 }
});

OkHttpClient は Builder パターンを使用して多くのパラメータを設定します。

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 connectTimeout;
 int readTimeout;
 int writeTimeout;
 int pingInterval;
  
 //BuilderCallServerInterceptor のデフォルトパラメータには、いくつかのパラメータ設定がある。
 public Builder() {
 dispatcher = new Dispatcher();
 protocols = DEFAULT_PROTOCOLS;
 connectionSpecs = DEFAULT_CONNECTION_SPECS;
 eventListenerFactory = EventListener.factory(EventListener.NONE);
 proxySelector = ProxySelector.getDefault();
 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;
 connectTimeout = ;
 readTimeout = ;
 writeTimeout = ;
 pingInterval = 0;
 }
 public OkHttpClient build() {
 return new OkHttpClient(this);
 }
}

OkHttpClientインスタンスがビルドによって作成されると、多くのパラメータが含まれます。

リクエストの作成

ユーザーのリクエストはリクエスト(Request)によって記述されます:


Request request = new Request.Builder().url(url).build();
public final class Request {
  
 public static class Builder {
 private HttpUrl url;// URL
 private String method;//GET POST などのリクエストメソッドがある。
 private Headers.Builder headers;//リクエストの HTTP ヘッダ
 private RequestBody body;// body
 private Object tag;
 public Builder() {
 this.method = "GET";
 this.headers = new Headers.Builder();
 }
 }
 public Request build() {
 if (url == null) throw new IllegalStateException("url == null");
 return new Request(this);
 }
}

同じRequestもBuilderを通してインスタンスを作成し、HTTPリクエストに必要な情報の一部をカプセル化します。 OkhttpはnewCallを通してリクエストのCallオブジェクトを作成し、このオブジェクトはRequestサービスがHttpリクエスト処理を完了させる役割を果たします。

@Override public Call newCall(Request request) {
 return new RealCall(this, request);
}

発願

RealCallの実装を見てみましょう。

//同期されたリクエスト
@Override public Response execute() throws IOException {
 synchronized (this) {
 if (executed) throw new IllegalStateException("Already Executed");
 executed = true;
 }
 try {
 client.dispatcher().executed(this);
 Response result = getResponseWithInterceptorChain(false);//リクエスト処理を実行する
 if (result == null) throw new IOException("Canceled");
 return result;
 } finally {
 client.dispatcher().finished(this);
 }
}
//非同期リクエスト
void enqueue(Callback responseCallback, boolean forWebSocket) {
 synchronized (this) {
 if (executed) throw new IllegalStateException("Already Executed");
 executed = true;
 }
 client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

同期リクエストは、RealCallの実行を介して開始され、その実装はまた、非常に簡単です、最初の実行をtrueに設定すると、呼び出しが実行するために開始されている、あなたが再びリクエストを開始する呼び出しを使用する場合は、すでに実行された例外がスローされますし、OkHttpClientの設定ディスパッチャの実行を介して実行され、このディスパッチャは、リクエストのディスパッチポリシーは、単刀直入に言えば、リクエストの開始であるかどうか、非同期または同期ディスパッチャを介していくつかの設定を行います。同期リクエストの場合、ディスパッチャは、同期リクエストキューにリクエストを記録され、リクエストが削除されます完了メソッドを介して完了し、非同期リクエストの場合ディスパッチャは、非同期リクエストキューにリクエストタスクに追加され、スレッドプールを介して実際のリクエストを起動します。ここで重要なのは、非同期リクエストのCallはAsyncCallであるということです。

public final class 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<>();//同期キュー
 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;
 }
 /** Used by {@code Call#execute} to signal it is in-flight. */
 synchronized void executed(RealCall call) {
 runningSyncCalls.add(call);//リクエストを同期キューに追加する
 }
 /** Used by {@code Call#execute} to signal completion. */
 //同期リクエストはこのメソッドを使う
 synchronized void finished(Call call) {
 if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
 }
 synchronized void enqueue(AsyncCall call) {
 //非同期キューに非同期リクエストを追加すると、OKhttp は同時に 64 個の非同期リクエストの実行をサポートしていることがわかる。 
 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
 runningAsyncCalls.add(call);
 executorService().execute(call);
 } else {
 readyAsyncCalls.add(call);//それ以外の場合は待ち行列に追加される
 }
 }
 //非同期リクエストコール
 synchronized void finished(AsyncCall call) {
 if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
 promoteCalls();//非同期リクエストの呼び出しが完了するたびに、待ち行列にあるリクエストはこのメソッドを通して非同期リクエスト待ち行列に移され、リクエストが開始される。
 }
   
}

そして、同期リクエストの場合、RealCallのexecuteメソッドでのリクエスト処理の実際の実行は、メソッド名でも知られているgetResponseWithInterceptorChainを通して必ず行われます。非同期リクエストはAysnCallのコールバックで実行されます。

public abstract class NamedRunnable implements Runnable {
 protected final String name;
   
 @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();
}
final class AsyncCall extends NamedRunnable {
 private final Callback responseCallback;
 private final boolean 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);
 }
 }
 }
}

非同期リクエストの場合、ASyncCall コールバック・メソッドも getResponseWithInterceptorChain (forWebSocket) を通して HTTP リクエスト処理を完了し、responseCallback を通してリクエスト結果をアプリケーションの上位層に返し、最後にディスパッチャの finished メソッドで非同期キューから呼び出しを削除します。

リクエストプロセス

前の同期または非同期要求が最終的にgetResponseWithInterceptorChainメソッドを介して要求プロセスを完了するかどうかを知って、次の特定の実装の要求プロセスを分析します。

final class RealCall implements Call {
 ...
 Response getResponseWithInterceptorChain() throws IOException {
 // Build a full stack of interceptors.
 List<Interceptor> interceptors = new ArrayList<>();
 interceptors.addAll(client.interceptors());
 interceptors.add(retryAndFollowUpInterceptor);
 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);
 return chain.proceed(originalRequest);
 }
 ...
}

getResponseWithInterceptorChain からは、実際のリクエスト処理がどのように実行されるかはわかりません。CallServerInterceptor インターセプタを追加し、このインターセプタリストを通して RealInterceptorChain を構築し、processd を呼び出してリクエスト処理を実行します。では、インターセプターの役割とは何でしょうか?実際には、インターセプタは、ミドルウェアとみなすことができる、それはいくつかの追加作業を行うために、リクエストとレスポンスをインターセプトする責任がある、そのようなルートを見つけるためにリクエストにログを追加するなど、接続の開発は、ポリシーを再試行するために失敗した、応答のためのキャッシュポリシーなどを実装します。一般的に、インターセプタを追加する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();
 }
}

インターセプターの実行を理解するためには、RealInterceptroChainの実装を見る必要があります。

public final class RealInterceptorChain implements Interceptor.Chain {
 private final List<Interceptor> interceptors;//インターセプターチェーンは
 private final int index;//現在チェーン内で動作しているインターセプターのインデックス
 @Override public Response proceed(Request request) throws IOException {
 return proceed(request, streamAllocation, httpCodec, connection);
 }
 //インターセプターチェインの処理
 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);
 Interceptor interceptor = interceptors.get(index);//現在のインターセプターを取得する
 Response response = interceptor.intercept(next);//現在のインターセプターのメソッドが実行され、次のインターセプターが渡される。
  
 return response;
 }
}

実際には、インターセプタチェーンの要求プロセスは、アプリケーションのデータ処理のためのネットワークスタックに似ている、ここではその動作原理を分析するために、インターセプタのチェーンがあると仮定A -> B -> C -> Dは、OKHttpのこのチェーンは、主にインターセプタチェーンを横断するために使用される進行メソッドのインターセプタチェーンを実装するRealInterceptorChainです。インターセプタチェーンインデックスの開始は0、つまり、インターセプタからインターセプタを実行するために、そのインターセプトメソッドを呼び出すには、インターセプタチェーンのインターセプトメソッドのパラメータは、Aでインターセプトメソッドは、チェーンを介して渡され、その後、次のインターセプタとして使用することができますチェーンを呼び出すように注意する必要があります。1 はチェーンの次のインターセプターを指定します。インターセプターDに至るまで、DはチェーンのPROCEEDメソッドを再度実行する必要はありません。なぜなら、それは最後のインターセプターであり、それ以降のインターセプターは存在しないからです。OkHttp のインターセプタチェインの最後のインターセプタは CallServerInterceptor です。

Read next

株式 - MACD指標

MACD指標は、すべてのテクニカル指標の中で最も古典的なテクニカル指標であり、この指標を正しく使用し、K線、個々の株式の動向、取引量、市場動向、良いニュースと悪いニュース、会社のファンダメンタルズなどと組み合わせることで、基本的な

May 21, 2020 · 10 min read