編輯:關於Android編程
OkHttp現在很火呀。於是上個星期就一直在學習OkHttp框架,雖然說起來已經有點晚上手了,貌似是2013年就推出了。但是現在它版本更加穩定了呀。這不,說著說著,OkHttp3.3版本在這幾天又發布了。以下以OkHttp3.2版本為准,沒辦法,上個星期看的時候還是以3.2為最新版本的。首先,我們要先了解一些背景,OkHttp這個框架是有Square公司推出的,進入官網。如果想看API,點擊進入API。大概了解了OkHttp之後,我們應該知道OkHttp是一個網絡框架,想想以前在開發中,網絡框架一般用的是什麼?很快我們就會想到剛學習android開發的時候接觸的HttpURLConnection和Apache提供的HttpClient這兩個類,然後就是後面推出的一些第三方網絡框架,比如2013年google推出的Volley框架、android-async-http框架、2014年很火的Xutils、以及現在很多人用的Retrofit等等。這麼多,到底選哪個?一開始我也暈。後來看了一些資料,似乎懂了一個概念:OkHttp是用來替換HttpURLConnection的,據說android4.4源碼的HttpURLConnection就替換成了OkHttp。所以我們別拿OkHttp和這些網絡框架比,這些網絡框架也只是基於HttpURLConnection進行一些封裝,使我們的代碼更加簡潔方便。懂了這點,我們應該就懂了為什麼網上那麼多OkHttp和Volley或者Retrofit等等這些框架結合使用了,其實是一個道理。那麼我用的HttpUrlConnection或者HttpClient用的好好的,干嘛要用你的OkHttp?這裡就來比較下HttpURLConnection和OkHttp。至於HttpClient嘛,android6.0已經把它的API廢除了。用它還要引入org.apache.http.legacy.jar包,不值得,而且okhttp也已經提供了對應的okhttp-apache 模塊。
OkHttp比HttpURLConnection具有更好的同步異步請求、緩存機制,支持HttpDNS、重定向、Gzip壓縮,平台適應性、很好的服務器IP的轉換、直接Socket通信,支持攔截器等等。
看到這麼多機制,是不是覺得很強大,通過Socket直接通信,以及很好的緩存機制,Gzip對於Http頭部的壓縮傳輸。自然對於網絡請求這塊使應用更加省流量、請求的更快。OkHttp對於Https和HttpDNS的支持,使得應用的網絡通信安全性更高。當然說了它的好,現在也來說說它的
不好之處
雖然是不好的地方,但是OkHttp已經比較成熟了,網上解決這幾個問題的資料也很多了。所以這些都不是問題。
這裡我們就以經典的官網提供的Get請求的例子來學習下,說大概的代碼。
先在manifest加個網絡權限,養成良好習慣
然後在build.gradle文件的dependencies添加庫如下:
dependencies {
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
}
同步Get請求:
final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.build();
final Request request = new Request.Builder()
.url("https://www.publicobject.com/helloworld.txt")
.header("User-Agent","OkHttp Example")
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = okHttpClient.newCall(request).execute();
Log.d("zgx","response====="+response.body().string());
response.body().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
這個例子網上說爛了,沒啥說的,來看下結果
很漂亮的樣子。呵呵
異步Get請求:
修改上面部分代碼,Call類調用enqueue方法。代碼如下:
new Thread(new Runnable() {
@Override
public void run() {
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("zgx","response====="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("zgx","response====="+response.body().string());
response.body().close();
}
});
}
}).start();
運行結果和上面一樣。只是多了一個CallBack
其實還有Post請求,文件上傳下載,圖片加載,攔截器的使用,支持session的保持這裡就先不說了。以後有時間再學習下。下面就是簡單的來看下他的源碼,只是以個人理解來分析下,看源碼前先來了解一些基本的知識。
Http
Http是一種基於TCP/IP連接的一套網絡通信協議,它是一種一應一答的請求,它分為Get和Post請求,Get請求獲取得是靜態頁面,它可以把參數放在URL字符串後面。而Post請求就不同了,它是把參數放在Http請求的正文的。
Get請求我們會這樣請求:
private void HttpURLConnection_Get(){
try{
//通過openConnection 連接
URL url = new java.net.URL(URL);
urlConn=(HttpURLConnection)url.openConnection();
//設置輸入和輸出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
//關閉連接
urlConn.disconnect();
}catch(Exception e){
resultData = "連接超時";
}
}
然後把獲取到的urlConn連接的數據通過IO流把讀取出來:
InputStreamReader in = new InputStreamReader(urlConn.getInputStream());
BufferedReader buffer = new BufferedReader(in);
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){
resultData += inputLine + "\n";
}
System.out.println(resultData);
in.close();
Post請求則會這樣:
private void HttpURLConnection_Post(){
try{
//通過openConnection 連接
URL url = new java.net.URL(URL_Post);
urlConn=(HttpURLConnection)url.openConnection();
//設置輸入和輸出流
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
urlConn.setRequestMethod("POST");
urlConn.setUseCaches(false);
// 配置本次連接的Content-type,配置為application/x-www-form-urlencoded的
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
// 連接,從postUrl.openConnection()至此的配置必須要在connect之前完成,
// 要注意的是connection.getOutputStream會隱含的進行connect。
urlConn.connect();
//DataOutputStream流
DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());
//要上傳的參數
String content = "par=" + URLEncoder.encode("ylx_Post+中正", "UTF_8");
//將要上傳的內容寫入流中
out.writeBytes(content);
//刷新、關閉
out.flush();
out.close();
}catch(Exception e){
resultData = "連接超時";
}
}
然後同上把獲取到的urlConn連接的數據通過IO流把讀取出來,大概的代碼就是這樣。
HTTPS從這張圖我們可以看出,最左邊為經典的ISO7層模型圖,右邊我們可以看到有一個SSL層,它又叫做安全套捷字層,它分為SSL記錄協議和SSL握手協議。SSL位於傳輸層和應用層之間,其中SSL記錄 層協議位於傳輸層協議之上,而SSL握手協議又在SSL記錄協議之上。SSL記錄協議可以為高層協議進行加密,壓縮,封裝等功能,而SSL握手協議進行的是身份認證,協商加密算法、交換加密密鑰等。其中TLS和SSL類似,它建立在SSL3.0協議之上。主要的不同在於他們的加密算法不同,其他功能作用類似。想要詳情看他們的區別,請看這篇文章SSL與TLS的區別以及介紹。
基礎基本上講完了,現在就來說說OkHttp涉及到的一些知識了。支持SPDY協議和HTTP2.0協議,同步和異步請求,攔截機制,請求和響應的邏輯處理,緩存機制,重連和重定向機制,連接池,Gzip壓縮,安全性,平台適應性等等。下面我們就來通過源碼來一步步的學習。
private static final List DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
再進去Protocol類
public enum Protocol {
HTTP_1_0("http/1.0"),
HTTP_1_1("http/1.1"),
SPDY_3("spdy/3.1"),
HTTP_2("h2");
//省略部分代碼
}
進入這個類,我們發現,這是一個枚舉類,它定義了一些和遠程服務器通信的協議名稱,如上面四種。
然後回到OkHttpClient這個類,跟蹤protocols這個屬性,我們會找到這個方法:
public Builder protocols(List protocols) {
protocols = Util.immutableList(protocols);
if (!protocols.contains(Protocol.HTTP_1_1)) {
throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
}
if (protocols.contains(Protocol.HTTP_1_0)) {
throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
}
if (protocols.contains(null)) {
throw new IllegalArgumentException("protocols must not contain null");
}
this.protocols = Util.immutableList(protocols);
return this;
}
public Builder connectionSpecs(List connectionSpecs) {
this.connectionSpecs = Util.immutableList(connectionSpecs);
return this;
}
我們會發現,OkHttp是支持http/1.1版本的,但是不支持http/1.0版本的協議,支持h2協議,以及spdy/3.1協議。而且協議名稱不能為null。既然支持h2,就說明服務端支持 ALPN的,它將可以協商到協商到 HTTP/2。這個很好呀,好在哪裡呢,我們可以看下這篇文章,為什麼我們應該盡快支持 ALPN?
同步和異步請求
public interface Call {
//初始化這個請求並且返回這個請求
Request request();
//同步方法
Response execute() throws IOException;
//異步方法
void enqueue(Callback responseCallback);
//取消請求,完成的請求則不能取消
void cancel();
//省略部分代碼
interface Factory {
Call newCall(Request request);
}
}
它是一個接口類,裡面包含同步方法和異步方法,我們還可以看到定義了一個內部接口Factory ,實現這個接口,通過newCall方法返回Call對象,也就是上面我們調用的OkHttpClient.newCall(Request request),因為OkHttpClient對象已經實現這個接口。那麼我們就回到OkHttpClient對象裡面的newCall(Request request)方法裡面。
@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}
創建了一個RealCall對象,那麼同步異步方法肯定就在這裡面實現了,繼續來看看,RealCall實現了Call接口,果然是在這裡,接下來看同步和異步方法。
同步方法:
@Override
public Response execute() throws IOException {
//同步操作,如果這個請求已經請求完了,則直接拋異常返回
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//通過dispatcher類來實現同步請求
client.dispatcher().executed(this);
//攔截器,下文再說
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
先不管Dispatcher類,先來看看異步請求方法:
void enqueue(Callback responseCallback, boolean forWebSocket) {
//同步操作,如果這個請求已經請求完了,則直接拋異常返回,省略這裡代碼
//然後是發出異步請求,也是通過Dispatcher類。而且這裡增加了一個callback
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
還是等下來看Dispatcher類,先來看下AsyncCall接口,實現了NamedRunnable這個線程,這個線程會把當前”OkHttp “+請求url設置為當前線程名。接下來就是看下核心的execute抽象方法的實現
@Override protected void execute() {
boolean signalledCallback = false;
try {
//也是會來到攔截器裡面,下文說
Response response = getResponseWithInterceptorChain(forWebSocket);
//如果這個請求已經取消,則直接調用callback的失敗接口。
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//直接調用callback的成功接口。
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);
}
}
其實主要還是回到攔截器裡面,這裡先不說攔截器,其他的也就是callback的邏輯處理。好了,現在就來說Dispatcher這個類了。上面我們說了同步和異步方法裡面會去調用Dispatcher類的executed(this)和enqueue(AsyncCall call)方法。先來看下Dispatcher類的executed(this)
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
synchronized void finished(Call call) {
if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
}
Dispatcher類通過一個同步隊列runningSyncCalls會幫我保存同步的請求Call。然後在完成請求之後再finished裡面remove掉,其實runningSyncCalls的作用就是統計當前有多少個同步請求,其他作用還沒發現。
再來看下enqueue(AsyncCall call)方法:
synchronized void enqueue(AsyncCall call) {
//當前異步請求量小於請求量的最大值64,並且請求同一個host服務器地址小於5的條件下
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//把請求的AsyncCall記錄到正在執行的請求隊列runningAsyncCalls,並且通過線程池去執行這個請求。
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
/*超過最大請求量則添加到後備隊列裡面,等前面請求完成的時候,也就是調用finished(AsyncCall call)的時候。通過promoteCalls方法把readyAsyncCalls的請求添加到runningAsyncCalls執行*/
readyAsyncCalls.add(call);
}
}
把readyAsyncCalls的請求添加到runningAsyncCalls執行方法如下
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
上面的代碼應該都看的懂。
攔截器機制
class LoggingInterceptors implements Interceptor{
private final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
@Override
public Response intercept(Chain chain) throws IOException {
long t1 = System.nanoTime();
Request request = chain.request();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
然後創建OkHttp的時候這樣創建就好了
new OkHttpClient()
.newBuilder()
.addNetworkInterceptor(new LoggingInterceptors())
.build();
代碼就不解釋了,網上很多這個例子。
再簡單說下添加公共參數的例子。
class LoggingInterceptors implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
//添加請求頭,可以添加多個參數,或者在original.body()http的body裡面添加參數
Request.Builder requestBuilder = original.newBuilder()
.addHeader("device", Build.DEVICE)
.method(original.method(),original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
}
上面只是一個簡單的參考,可以參考Okhttp中如何在攔截器中的RequestBody添加參數?,我們也可以和源碼裡面一樣通過Builder的形式,向外公開一些add方法。
其實網上也挺多這方面的例子的,攔截器還是很好用的,下面我們就來學下OkHttp源碼裡面的攔截器
通過上面例子可以知道chain.proceed(request)直接然後的是http的響應請求,那麼現在就來這個方法看下吧。
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
我們可以看到有proceed這個方法。有兩個攔截器實現這個接口,一個是應用攔截器ApplicationInterceptorChain,一個是網絡攔截器NetworkInterceptorChain。那麼怎麼理解這兩種攔截器呢,這裡我們先來debug下。
先來debug下addInterceptor這個方法,也就是應用攔截器。
vcjnzbyjrM7Sw8e/ydLUv7TPwrX308Nwcm9jZWVko6ijqbe9t6i1xLXYt73Wu9PQ0ru49qOs0rK+zcrHQXBwbGljYXRpb25JbnRlcmNlcHRvckNoYWluoaO2+EFwcGxpY2F0aW9uSW50ZXJjZXB0b3JDaGFpbrXEcHJvY2VlZLe9t6jA78PmtffTw2ludGVyY2VwdLe9t6ijrLb4TmV0d29ya0ludGVyY2VwdG9yQ2hhaW61xHByb2NlZWS3vbeow7vX36Os1eLSssrHzqrKssO0yc/D5sD919O08tOh0ru0zrXE1K3S8qGjyOe5+7K70MW1xLuwo6y/ydLUyKVOZXR3b3JrSW50ZXJjZXB0b3JDaGFpbrXEcHJvY2VlZLbPteO198rUz8KjrMv8yseyu9ffxMfA77XEoaO908/CwLTO0sPHvs3AtL+0z8IuYWRkTmV0d29ya0ludGVyY2VwdG9yKKOpo6zAtLX3ytTPwqGjPC9wPg0KPHA+PGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160530/20160530092755366.png" title="\" />
這裡有兩個地方調用了proceed(),說明了ApplicationInterceptorChain的proceed方法裡面也調用intercept方法,可以去ApplicationInterceptorChain的proceed斷點調試下,它還是會走那裡。這也是上面例子打印兩次的原因
所以可以把它理解為應用攔截器,是http發送請求的時候進行一次攔截,它不會走網絡進行請求的。而網絡攔截器我們可以把它看做是服務器響應的時候進行的一次攔截,它會走網絡請求的。可以結合下圖來理解
來看下應用攔截器ApplicationInterceptorChain的process方法
@Override public Response proceed(Request request) throws IOException {
//如果攔截鏈裡面還有攔截器,則調用這裡,也就是遞歸處理
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
//如果沒有攔截器,則繼續進行http請求
return getResponse(request, forWebSocket);
}
繼續來看下網絡攔截器NetworkInterceptorChain的proceed方法。
@Override public Response proceed(Request request) throws IOException {
//統計proceed調用次數
calls++;
if (index > 0) {
Interceptor caller = client.networkInterceptors().get(index - 1);
Address address = connection().route().address();
//每次遞歸都會去判斷url是否包含host和port。
if (!request.url().host().equals(address.url().host())
|| request.url().port() != address.url().port()) {
throw new IllegalStateException("network interceptor " + caller
+ " must retain the same host and port");
}
// Confirm that this is the interceptor's first call to chain.proceed().
if (calls > 1) {
throw new IllegalStateException("network interceptor " + caller
+ " must call proceed() exactly once");
}
}
//遞歸NetworkInterceptorChain,直到攔截鏈裡面沒有攔截器
if (index < client.networkInterceptors().size()) {
NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
Interceptor interceptor = client.networkInterceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
// Confirm that the interceptor made the required call to chain.proceed().
if (chain.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
if (interceptedResponse == null) {
throw new NullPointerException("network interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
//把request寫入http頭部裡面
httpStream.writeRequestHeaders(request);
//更新的networkRequest,可能攔截已經請求更新
networkRequest = request;
//這裡是對post請求進行的一些body寫入
if (permitsRequestBody(request) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
//通過io包進行一些io流操作
Response response = readNetworkResponse();
int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
其實攔截器還有個stetho工具,集成它使用攔截器獲取日志就更方便了。集成教程網上有很多。
請求和響應的邏輯處理
Response getResponse(Request request, boolean forWebSocket) throws IOException {
//...省略前面代碼
try {
//發送請求
engine.sendRequest();
//回復的響應
engine.readResponse();
releaseConnection = false;
}
//...省略後面代碼
從上面可以知道,請求和響應的邏輯處理是在HttpEngine對象的,接下來就來看下HttpEngine對象
這裡是發送一個請求方法
public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代碼
//通過Request.Builder設置Request的配置信息,然後返回配置好的Request 對象
Request request = networkRequest(userRequest);
//...省略部分代碼
//將request 傳入緩存的處理類裡面進行一些緩存處理,然後返回networkRequest ,其實和request 一樣
networkRequest = cacheStrategy.networkRequest;
//...省略部分代碼
try {
//真正的通過socket通信發送請求出去
httpStream = connect();
httpStream.setHttpEngine(this);
//如果是post或者帶有body的請求方式,執行下面部分寫出body
if (writeRequestHeaders()) {
long contentLength = OkHeaders.contentLength(request);
if (bufferRequestBody) {
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if (contentLength != -1) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}
讀取響應的方法
public void readResponse() throws IOException {
//...省略部分代碼
if (forWebSocket) {
httpStream.writeRequestHeaders(networkRequest);
networkResponse = readNetworkResponse();
} else if (!callerWritesRequestBody) {
networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
} else {
// Emit the request body's buffer so that everything is in requestBodyOut.
if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
bufferedRequestBody.emit();
}
//...省略部分代碼
//其實真正的我們還是通過這個方式來獲取響應數據的
networkResponse = readNetworkResponse();
}
//...省略部分代碼
}
接下來就來看看readNetworkResponse這個方法
private Response readNetworkResponse() throws IOException {
httpStream.finishRequest();
//這裡通過io流去讀取響應的數據
Response networkResponse = httpStream.readResponseHeaders()
.request(networkRequest)
.handshake(streamAllocation.connection().handshake())
.header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
.header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
.build();
if (!forWebSocket) {
networkResponse = networkResponse.newBuilder()
.body(httpStream.openResponseBody(networkResponse))
.build();
}
if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
|| "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
streamAllocation.noNewStreams();
}
return networkResponse;
}
其實發送請求這塊還有很多源碼的處理,其他的就先不看了,基本上是一些io流的處理了。
緩存處理
int cacheSize = 10 * 1024 * 1024; // 10 MiB
//cacheDirectory保存緩存的目錄,cacheSize緩存空間的大小
Cache cache = new Cache(context.getCacheDir(), cacheSize);
final OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.cache(cache)
.build();
這樣我們就設置好了。下面我們就來分析下源碼,其實在上面發送請求和讀取響應方法裡面已經有緩存處理的邏輯。回到sendRequest()方法
public void sendRequest() throws RequestException, RouteException, IOException {
//...省略部分代碼
/*Internal是一個抽象類,定義了很多個抽象類,其中就有setCache(OkHttpClient.Builder builder, InternalCache internalCache)這個方法,然後.internalCache(client)其實它會去調用OkHttpClient裡的static塊裡的Internal的internalCache方法,返回一個InternalCache*/
InternalCache responseCache = Internal.instance.internalCache(client);
//Cache類獲取緩存裡面的響應數據
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
long now = System.currentTimeMillis();
//創建CacheStrategy.Factory對象,進行緩存配置
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
//傳入的網絡連接
networkRequest = cacheStrategy.networkRequest;
//cacheCandidate 傳入CacheStrategy後得到的緩存的響應數據
cacheResponse = cacheStrategy.cacheResponse;
if (responseCache != null) {
//記錄當前請求是網絡發起還是緩存發起
responseCache.trackResponse(cacheStrategy);
}
//如果傳入CacheStrategy不可用並且cacheResponse 為null,結束所有請求連接資源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果網絡連接被禁止訪問並且緩存為null的時候
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
// 如果沒有網絡的情況下,這時候緩存是不為null的,所以這裡就去獲取緩存裡面的數據
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}
//...省略部分代碼
}
上面的返回的InternalCache 接口定義在了Cache這個類裡面,我們可以看到OkHttp使用的緩存是DiskLruCache,詳細緩存處理就不說了。
public void readResponse() throws IOException {
//...省略部分代碼
if (cacheResponse != null) {
//檢查緩存是否可用,如果可用。那麼就用當前緩存的Response,關閉網絡連接,釋放連接。
if (validate(cacheResponse, networkResponse)) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation();
// headers去掉Content-Encoding之後更新緩存
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
}
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}
其實緩存這塊還不是特別理解,由於篇幅比較長了,而且這篇是初識篇,其實還有很多沒去學習,比如重連和重定向機制,連接池,Gzip壓縮,安全性,平台適應性,cookie的保持等等的知識,下次抽時間再來學習下了。
1、概述 眾所周知,Activity在不明確指定屏幕方向和configChanges時,當用戶旋轉屏幕會重新啟動。當然了,應對這種情況,Android給
RecyclerView是android-support-v7-21版本中新增的一個Widget,官方介紹RecyclerView 是 ListView 的升級版本,更加
QQ空間打賞紅包是手機QQ最近新推出的一個功能,這個打賞紅包功能可以讓你給QQ空間中發表優質內容的人打賞紅包,如果你看到好友發了一條說說可以試試這個QQ空間
Buzz桌面安裝好之後,是不會自動將已安裝的手機應用程序圖標添加到桌面上的,需要我們手動添加或拖動應用程序圖標到桌面,下面就讓我們來看看如何將已經安裝的應用