不忘初心 砥礪前行, Tomorrow Is Another Day !
相關文章
本文概要:
- 基本使用
- 基本原理
一. 基本使用
okhttp的請求和響應大多數採用建造者模式設計.
1. GET同步請求
public void syncGetRequest() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
final OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.get()
.url(url)
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = okHttpClient.newCall(request).execute();
Log.d(TAG, "syncGetRequest: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).star
複製程式碼
2. GET非同步請求
public void asyncGetRequest() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
final OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get()
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "Callback-Thread: " + Thread.currentThread());
String result = response.body().string();
Log.d(TAG, "asyncGetRequest: " + result);
//通過handler或者runOnUiThread方式切換執行緒.
runOnUiThread(new Runnable() {
@Override
public void run() {
idtv.setText("jasonhww");
}
});
/*
*如果獲取的是檔案/圖片
*/
//方式一:通過獲取流
//InputStream inputStream = response.body().byteStream();
//方式二:通過獲取位元組陣列
//byte[] bytes = response.body().bytes();
}
});
}
複製程式碼
注意事項:
- Callback的回撥方法是在子執行緒執行的,如需更新UI,可通過runjOnUiThread或者handler切換到主執行緒.
3. POST提交表單
可以使用FormBody.Builder構建一個表單請求體
public void asyncPostForm(){
String url = "http://api.k780.com/";
final OkHttpClient okHttpClient = new OkHttpClient();
//構建表單請求體
RequestBody requestBody = new FormBody.Builder()
.add("app","weather.future")
.add("weaid","1")
.add("appkey","10003")
.add("sign","b59bc3ef6191eb9f747dd4e83c99f2a4")
.add("format","json")
.build();
//建立POST請求
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncPostForm: "+response.body().string());
}
});
}
複製程式碼
4. POST提交JSON字串
指定請求體的媒體型別為"application/json"
public void asyncJson() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
String json = "{code:1,result:null}";
OkHttpClient okHttpClient = new OkHttpClient();
//指定媒體型別
MediaType mediaType = MediaType.parse("application/json,charset=utf-8");
RequestBody requestBody = RequestBody.create(mediaType, json);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncJson: " + response.body().string());
}
});
}
複製程式碼
5. 快取
快取使用相對比較簡單,只需指定一下快取目錄及大小即可.
//虛擬碼
Cache cache = new Cache(cacheDirectory,cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
複製程式碼
6. 攔截器
通過攔截器我們可以很方便的去修改請求和響應的相關資訊,如修改請求頭,請求體等.
- 應用攔截器
攔截應用層與okhttp之間的請求和響應 - 網路攔截器
攔截okhttp與網路層之間的請求和響應,此時網路連線已經建立
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
//獲取請求
Request request = chain.request();
//列印url,連線狀態,請求頭
Log.d(TAG, "LoggingInterceptor: url = " + request.url() +
"\nconnectionStatus = " + chain.connection() +
"\nrequestHeaderInfo = " + request.headers());
//執行請求
Response response = chain.proceed(request);
//列印url,響應頭
Log.d(TAG, "LoggingInterceptor: url = " + response.request() +
"\nresponseHeaderInfo = " + response.headers());
return response;
}
}
public class NetInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
//演示設定響應頭
CacheControl.Builder builder = new CacheControl.Builder()
.maxAge(10,TimeUnit.MINUTES);
return response.newBuilder()
.header("Cache-Control",builder.build().toString)
.build();
}
}
複製程式碼
public void asyncCacheInterceptor() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())//應用攔截
.addNetworkInterceptor(new NetInterceptor())//網路攔截器
.build();
//通過攔截器修改請求頭
Request request = new Request.Builder()
.get()
.header("user-Agent","Interceptor example")
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncCacheInterceptor: " + response.body().string());
}
});
}
複製程式碼
二. 基本原理
採用OkHttp原始碼版本為3.8.1
1. OkHttpClient的建立
OkHttpClient作為okhttp的入口,一般將OkHttpClient採用單例模式.
初始化兩種方式
- 採用直接"new"的方式
- 採用建造者模式
對應原始碼
//內部也是通過建造者進行初始化
OkHttpClient okhttpClient = new OkHttpClient();
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.build();
複製程式碼
通過初始化okhttpclient,完成對許多成員變數的初始化工作.
對應原始碼
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;
//Cookie瓶
this.cookieJar = builder.cookieJar;
//磁碟快取
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
//HTTPS相關成員變數初始化
......
}
複製程式碼
2. Call的建立
當okhttpclient初始化後,再通過okHttpClient.newCall(request),建立一個Call物件,最終實際返回了一個RealCall物件.
對應原始碼
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
複製程式碼
2.1 同步請求時
呼叫RealCall的execute方法
對應原始碼
/**
* 1. 首先看RealCall的execute方法.
*/
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
//實際呼叫了分發器的executed方法,傳入RealCall物件.
client.dispatcher().executed(this);
//最終通過攔截器鏈獲取響應.
//最終通過攔截器鏈獲取響應.
//最終通過攔截器鏈獲取響應.
//重要的話說三遍.
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
/**
* 2. 接著看dispatcher的executed方法
*/
synchronized void executed(RealCall call) {
//標識已經執行了請求
runningSyncCalls.add(call);
}
複製程式碼
2.1 非同步請求時
同樣呼叫RealCall的enqueue方法
對應原始碼
/**
* 1.首先看RealCall的enqueue方法.
*/
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//實際呼叫了分發器的enqueue方法,傳入AsyncCall物件
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
/**
* 2.接著看dispatcher物件的enqueue方法.
*/
synchronized void enqueue(AsyncCall call) {
//檢測請求是否超過最大請求數和一個host對應最大的請求數
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//標識已經執行了請求(這裡和同步方法一樣操作,可以對比上一點方法)
runningAsyncCalls.add(call);
//使用執行緒池執行請求
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
/**
* 3.最後我們看AsyncCall的實現.
*/
//AsyncCall是RealCall的內部類,繼承自NamedRunnable
//NamedRunnable的實現比較簡單就是修改執行緒名.提供一個execute方法在run用呼叫,這裡就不再曬出具體原始碼了.
//AsyncCall的實現主是獲取響應,進行回撥.
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
//....省略部分程式碼
@Override protected void execute() {
boolean signalledCallback = false;
try {
//最終通過攔截器鏈獲取響應.
//最終通過攔截器鏈獲取響應.
//最終通過攔截器鏈獲取響應.
//重要的話說三遍.
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
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!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
//回撥失敗
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
複製程式碼
最後將Realcall對同步與非同步請求處理流程做個小結:
同步時.
- 呼叫RealCall的execute,實際呼叫了分發器的executed方法,傳入RealCall物件.
- 分發器的executed方法,僅僅標識已經執行了請求.
- 最終通過攔截器鏈獲取響應.
非同步時.
- 呼叫RealCall的enqueue,實際呼叫了分發器的enqueue方法,傳入AsyncCall物件.
- 分發器的executed方法,先檢查請求,滿足則標識已經執行了請求,最終使用執行緒池執行請求.不滿足則進人等待佇列
- AsyncCall的實現
- AsyncCall是一個NamedRunnable子類
- 通過攔截器鏈獲取響應,回撥成功與失敗.
3.攔截器鏈的構建與啟動
整個okhttp的核心設計之處
- 遵循單一原則,每個攔截器只做一項處理.
- 攔截器之間通過攔截器鏈(RealInterceptorChain)進行連線,構建出處理請求和響應的流水鏈.
請求通過層層攔截器處理,最後傳送出去,反之響應.
對應原始碼
//通過攔截器鏈獲取響應
Response getResponseWithInterceptorChain() throws IOException {
/**
* 初始化攔截器集合
*/
List<Interceptor> interceptors = new ArrayList<>();
//新增7種型別攔截器
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);
}
/**
* 攔截器鏈的啟動
*/
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//......省略部分程式碼
// 建立一個新的攔截器鏈,攔截器位置指向將index+1.即下一個.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//通過下標index獲取當前要執行的攔截器
Interceptor interceptor = interceptors.get(index);
//執行攔截器intercept方法
//如果當前攔截器執行完後,則呼叫傳入的next(新鏈物件)的proceed方法,執行下一個攔截器.依次類推.
Response response = interceptor.intercept(next);
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
複製程式碼
這7種攔截器執行順序為:
- 應用攔截器
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- 網路攔截器
- CallServerInterceptor
其中帶下劃線的攔截器,就是我們之前自定義的攔截器.接下來就依次講解7種攔截器的作用.
3.1 應用攔截器
不再講述
3.2 retryAndFollowUpInterceptor
處理錯誤重試和重定向
對應原始碼
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = 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;
} finally {
//出現異常釋放資源,continue在finally語句塊執行後才執行
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
//檢測是否滿足定向的要求程式碼
......
//重新賦值為重定向的請求
request = followUp;
priorResponse = response;
}
}
複製程式碼
3.3 BridgeInterceptor
橋接應用層和網路層
A. 將請求進行深加工,使其成為真正請求
- 對一些請求頭的設定
B. 並且也對網路響應做響應處理.
- 解壓縮,去掉不必要的響應頭
對應原始碼
@Override public Response intercept(Chain chain) throws IOException {
//獲取請求
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//對一些請求頭的設定.
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
//如果沒有配置,預設配置gzip.獲取響應時需要解壓縮.
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//執行下一個攔截器獲取響應
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
//用於解壓縮
GzipSource responseBody = new GzipSource(networkResponse.body().source());
//返回應用層去掉不需要的響應頭
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
複製程式碼
3.4 CacheInterceptor
承擔快取的查詢和儲存職責
A. 不需要網路請求,快取可用,直接返回
B. 快取不可用,執行下一個攔截器獲取響應;
如果使用者配置了需要快取,將響應寫入快取,並返回.
對應原始碼
@Override
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//檢查快取策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//networkRequest不為空,需要傳送請求
Request networkRequest = strategy.networkRequest;
//cacheResponse不為空,則快取可用
Response cacheResponse = strategy.cacheResponse;
//......省略部分程式碼
// If we don't need the network, we're done.
//不需要網路請求,快取可用,直接返回
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//快取不可用,執行下一個攔截器獲取響應
networkResponse = chain.proceed(networkRequest);
} finally {
//......省略部分程式碼
}
//......省略部分程式碼
//包裝網路響應
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//使用者配置了快取
if (cache != null) {
//判斷條件
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
//網路響應寫入快取,並返回
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
複製程式碼
3.5 ConnectInterceptor
給請求提供一個連線
- TCP的連線,HTTPS的連線全部在此完成.
對應原始碼
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//獲取HTTP編碼解碼器
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
//獲取一個連結
RealConnection connection = streamAllocation.connection();
//執行下一個攔截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製程式碼
3.6 網路攔截器
不再講述
3.7 CallServerInterceptor
將請求傳送出去
對應原始碼
@Override
public Response intercept(Chain chain) throws IOException {
//.......省略部分程式碼
//傳送網路請求,httpCodec則是在上一個ConnectInterceptor攔截器獲取到的
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
//構建網路響應物件
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//寫入響應體
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
複製程式碼
由於本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.