1はじめに
OkHttpは、Androidネットワークリクエスト用のサードパーティのオープンソース軽量フレームワークです。フレームワークはモバイルペイメントスクエアによって提供されます。その利点には、HTTP / 2のサポートが含まれ、同じホストアドレスに接続されたすべてのリクエストがソケット接続を共有できるようになります。HTTP/ 2が利用できない場合は、接続プールの設計を通じてリクエストを減らすこともできます。遅延、GZip圧縮を自動的に処理して応答データのサイズを保存、応答要求データをキャッシュして繰り返し要求を回避するなど
実際、前回の記事「AndroidネットワークプログラミングのHttpURLConnection原則の分析(8)」で okhttpフレームワークを公開しました。HttpURLConnectionのhttpおよびhttpsプロトコル処理の最下層はOkHttpを介して完了しているため、そのソースコードダウンロードアドレスにアクセスできることが言及されています:https ://android.googlesource.com/platform/external/okhttp からダウンロードしてください。実際、OkHttpには独自の公式Webサイトhttps://square.github.io/okhttpもあります。OkHttpの3番目のバージョンであるOkHttp3はマイルストーンバージョンです。最新バージョンは4.Xにアップグレードされていますが、パッケージ名がokhttp3であっても、内部的にはOkHttp3.Xとの厳密な互換性を維持しています。 。OkHttp 4.0.0 RC 3バージョン以降、その実装言語がJavaからKotlinに変更されたことにも言及する価値があります。
2クイックスタート
使用を開始する前に、プロジェクトグラドルでOkHttpの依存関係を構成してください。執筆時点での公式ウェブサイトの最新バージョンは4.2.1で、構成コードは次のとおりです。
implementation("com.squareup.okhttp3:okhttp:4.2.1")
AndroidManifest.xmlにアクセスネットワーク権限を追加します
<uses-permissionandroid:name="android.permission.INTERNET" />
2.1同期取得リクエスト
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void syncGet() throws Exception {
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = mOkHttpClient.newCall(request);
Response response = call.execute();
if (response.isSuccessful()) {
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
}
2.2非同期Getリクエスト
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void asyncGet() {
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.code());
System.out.println(response.message());
System.out.println(response.body().string());
}
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
2.3送信後の文字列
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void postString() throws Exception {
String postBody = "Hello world!";
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), postBody))
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
2.4投稿投稿ストリーム
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void PostStreaming() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MediaType.parse("text/x-markdown; charset=utf-8");
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Hello \n");
sink.writeUtf8("world");
}
};
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
2.5投稿の投稿
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void PostFile() throws Exception {
File file = new File("README.md");
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), file))
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
2.6投稿投稿フォーム
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void PostForm() throws Exception {
RequestBody formBody = new FormBody.Builder()
.add("name", "zyx")
.build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(formBody)
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
2.7 Post Complexリクエストボディ
MultipartBodyは、HTMLファイルアップロードフォームと互換性のある複雑なリクエストボディを構築できます。マルチパートリクエストボディの各パートは、それ自体がリクエストボディであり、独自のヘッダーを定義できます。これらのリクエストヘッダーは、Content-Dispositionなどのリクエスト本文の一部を記述するために使用できます。Content-LengthとContent-Typeが利用可能な場合、それらは自動的に追加されます。
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void postMultipart() throws Exception {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png", RequestBody.create(MediaType.parse("image/png"), new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
2.8キャッシュされた応答とタイムアウト
OkHttpClientの作成時にcacheメソッドを介してCacheクラスオブジェクトを渡す限り、リクエストをキャッシュできます。Cacheオブジェクトの作成は、読み取りと書き込みが可能なキャッシュディレクトリ、および(通常はプライベートディレクトリである必要があります)キャッシュサイズ制限値を渡すことです。connectTimeout、writeTimeout、およびreadTimeoutメソッドは、接続された接続、書き込み、および読み取りのタイムアウト期間を設定できます。
public void responseCaching(File cacheDirectory) throws Exception {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response1 = client.newCall(request).execute();
if (response1.isSuccessful()) {
System.out.println(response1.body().string());
System.out.println("Response 1 cache response: " + response1.cacheResponse());
System.out.println("Response 1 network response: " + response1.networkResponse());
}
Response response2 = client.newCall(request).execute();
if (response2.isSuccessful()) {
System.out.println(response2.body().string());
System.out.println("Response 2 cache response: " + response2.cacheResponse());
System.out.println("Response 2 network response: " + response2.networkResponse());
}
}
2.9リクエストのキャンセル
Callcancel()メソッドを使用して、進行中のCallをすぐに停止します。スレッドが現在要求を書き込んでいる、または応答を読み取っている場合、スレッドはIOException例外も受け取ります。例外情報は次のとおりです。java.io.IOException:キャンセルされました
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void syncGet() throws Exception {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2")
.get()
.build();
final Call call = mOkHttpClient.newCall(request);
// 将其取消
new Thread(new Runnable() {
@Override
public void run() {
call.cancel();
}
}).start();
try {
Response response = call.execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
} catch (Exception e) {
e.printStackTrace();
}
}
2.10認証処理
OkHttpは、認証されていないリクエストを自動的に再試行します。レスポンスが401 Not Authorizedの場合、Authenticatorは証明書を提供するよう求められます。Authenticatorの実装では、証明書を含む新しいリクエストを作成する必要があります。証明書がない場合は、nullを返して試行をスキップします。
Response.challenges()を使用して、認証チャレンジのスキームとレルムを取得します。基本チャレンジを完了するときに、Credentials.basic(ユーザー名、パスワード)を使用してリクエストヘッダーをデコードします。
public void syncGet() throws Exception {
OkHttpClient client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
if (response.request().header("Authorization") != null) {
return null; // Give up, we've already attempted to authenticate.
}
System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.get()
.build();
Call call = client.newCall(request);
Response response = call.execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
3インターセプター
OkHttp2.2以降、インターセプターが追加され、その設計思想はフレームワーク全体の本質であり、ネットワークの監視、リクエストの書き換え、レスポンスの書き換え、リクエストの失敗によるリトライを実現できます。ネットワーク要求の発行から応答まで、いくつかのインターセプターがあります。
インターセプターは基本的にインターセプターインターフェースに基づいており、開発者がカスタマイズできるインターセプターには、ApplicationInterceptor(アプリケーションインターセプター、addInterceptorメソッドを使用して追加)とNetworkInterceptor(ネットワークインターセプター、addNetworkInterceptorメソッドを使用して)の2種類があります。追加)。完全なインターセプター構造は、おおよそ次の図に示すとおりです。
3.1インターセプターの選択
以下では、例として傍受の使用を理解します。最初にログインターセプトLoggingInterceptorを定義し、次にOkHttpClientオブジェクトを作成するときにaddInterceptorメソッドとaddNetworkInterceptorメソッドを使用してログインターセプターを別の場所に追加します。
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
// 请求
Request request = chain.request();
long t1 = System.nanoTime();
System.out.println(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
// 响应
Response response = chain.proceed(request);
long t2 = System.nanoTime();
System.out.println(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
public void syncGet() throws Exception {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
// .addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.get()
.build();
Call call = client.newCall(request);
Response response = call.execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
addInterceptorを使用した結果を確認します。
Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
次に、addNetworkInterceptor出力の結果を使用します。
Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
URL:http://www.publicobject.com/helloworld.txtは最終的にhttps://publicobject.com/helloworld.txtにリダイレクトするためです。addInterceptorを使用して追加されたApplicationInterceptorによって出力された情報は、初期リクエストと最終応答のみですが、addNetworkInterceptorを使用して追加されたNetworkInterceptorによって出力された情報には、初期リクエスト、初期レスポンス、リダイレクトされたリクエスト、リダイレクトされたレスポンスが含まれます。
したがって、ApplicationInterceptorとNetworkInterceptorのアプリケーション選択の概要を示します。中間プロセスを気にしない場合は、ApplicationInterceptorを使用して最終結果をインターセプトするだけで済みます。リクエストプロセスで中間応答をインターセプトする必要がある場合は、NetworkInterceptorを使用する必要があります。
3.2書き換え要求と書き換え応答
インターセプターは、上記のようにログの印刷を提供していないようです。インターセプターは、リクエストの前にリクエストヘッダーを追加、削除、または置き換えることもできます。リクエストボディがある場合でも、リクエストボディを変更することができます。また、応答の後に応答ヘッダーを書き換え、その応答本文を変更できます(この操作により、サーバーから配信される応答コンテンツの意図が変わる可能性があるため、応答の書き換えは通常お勧めできません)。たとえば、次のコードが実装されています。次のインターセプターは、 "Content-Encoding"がリクエスト本文に存在しない場合、圧縮されたリクエスト本文がそれに追加されることを実装しています。
final class GzipRequestInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
}
4使用のための提案
OkHttpClientインスタンスを作成するときは、シングルトンにすることをお勧めします。OkHttpClient内には対応する接続プールとスレッドプールがあるため、複数のリクエストが発生した場合、これらのリソースを再利用すると、レイテンシを削減し、リソースを節約できます。また、各CallオブジェクトはRealCallを1回しか実行できないことに注意してください。そうしないと、プログラムが異常になります。