一举户、前言
目前做Android開發(fā)在網(wǎng)絡(luò)框架上榄鉴,大公司一般都維護著有自己的網(wǎng)絡(luò)框架财著,而一般的應(yīng)用都在使用Volley或者OkHttp诵冒,或者封裝了OkHttp的Retrofit,那么為何還要來分析Android自帶的網(wǎng)絡(luò)框架呢境蜕。當(dāng)然蝙场,Android大部分是來自于Java本身的庫,只不過針對移動端做了更改和簡化粱年。自帶網(wǎng)絡(luò)框架雖然被大家所詬病售滤,但其實是復(fù)雜的,龐大的,很多情況下都是非常貼近 HTTP 協(xié)議以及TCP/IP協(xié)議的完箩。所謂越原始往往越接近真相赐俗。整個分析下來相信會有不錯的收獲。原生框架是一份不錯的學(xué)習(xí)資料弊知。
這里的Android原生網(wǎng)絡(luò)框架的分析是基于 4.3 的阻逮,想必了解的人都知道了。Android從4.4開始就引入OkHttp但還不是作為唯一底層框架秩彤,而在5.0之后完全以O(shè)kHttp為底層網(wǎng)絡(luò)框架了叔扼。
原生的網(wǎng)絡(luò)框架有好幾個包,關(guān)系上看起來有亂:
java.net:主要就是簡單的HTTP協(xié)議實現(xiàn)漫雷,包括URL瓜富,HttpUrlConnection等,也是最基礎(chǔ)的部分
java.security:安全相關(guān)降盹,Http協(xié)議的實現(xiàn)食呻,證書校驗等
javax.crypto:加解密相關(guān)
javax.net:主要有 ssl 相關(guān)
javax.security: 也是安全相關(guān)的。與 **java.security **有什么關(guān)系呢澎现?又有什么不同呢?各自負(fù)責(zé)的職責(zé)是什么每辟?
ibcore.net:算是網(wǎng)絡(luò)庫的中間層封裝
org.apache.harmony.xnet.provider.jsse: apache的包剑辫?有什么用?
這些是否有內(nèi)在聯(lián)系渠欺?帶著這些疑問妹蔽,開始Android原生網(wǎng)絡(luò)庫的分析之旅吧。
還是以網(wǎng)絡(luò)連接挠将、通信與斷開為線索進行分析胳岂。
二、Http連接
1.示例
先來看一段官方通信示例代碼舔稀。
// 1.打開 URLConnection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
// 2.設(shè)置參數(shù)
urlConnection.setDoOutput(true);
urlConnection.setChunkedStreamingMode(0);
// 3.獲取輸出流
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
// 4.寫入?yún)?shù)
writeStream(out);
// 5.獲取讀入流
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
// 6.讀取數(shù)據(jù)
readStream(in);
} finally {
// 7.斷開連接
urlConnection.disconnect();
}
代碼很簡單乳丰,但如注釋時所述,共分了7個小步驟内贮。但代碼雖然短产园,可這里卻有不少的疑問:
(1)可以注意到有disconnect()調(diào)用,但卻沒有connect()調(diào)用夜郁。那么openConnection()發(fā)起了真正的網(wǎng)絡(luò)連接了嗎什燕?到底什么時候執(zhí)行的TCP三次握手呢?
(2)disconnect()調(diào)用后竞端,底層的TCP執(zhí)行了4次揮手協(xié)議了嗎屎即?
帶著這些疑問,先從 openConnection()看起吧。
2.openConnection()打開連接
URL的構(gòu)建函數(shù)重載的比較多技俐,這里只是簡單傳入一個url乘陪,最終會調(diào)用到如下這個版本的構(gòu)建函數(shù)。
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
153 if (spec == null) {
154 throw new MalformedURLException();
155 }
156 if (handler != null) {
157 streamHandler = handler;
158 }
159 spec = spec.trim();
160 // 1.先解析出協(xié)議頭虽另,確定協(xié)議
161 protocol = UrlUtils.getSchemePrefix(spec);
162 int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;
163
164 // If the context URL has a different protocol, discard it because we can't use it.
165 if (protocol != null && context != null && !protocol.equals(context.protocol)) {
166 context = null;
167 }
168
169 // Inherit from the context URL if it exists.
170 if (context != null) {
171 set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),
172 context.getUserInfo(), context.getPath(), context.getQuery(),
173 context.getRef());
174 if (streamHandler == null) {
175 streamHandler = context.streamHandler;
176 }
177 } else if (protocol == null) {
178 throw new MalformedURLException("Protocol not found: " + spec);
179 }
180
181 if (streamHandler == null) {
// 2.關(guān)鍵調(diào)用暂刘。根據(jù)協(xié)議設(shè)置用來干活的 Handler
182 setupStreamHandler();
183 if (streamHandler == null) {
184 throw new MalformedURLException("Unknown protocol: " + protocol);
185 }
186 }
187
188 // Parse the URL. If the handler throws any exception, throw MalformedURLException instead.
189 try { // 解析url的全部部分,handler解析完成后捂刺,最后會通過URL#set()方法將port,filepath,userinfo等都傳回來的
190 streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
191 } catch (Exception e) {
192 throw new MalformedURLException(e.toString());
193 }
194 }
如代碼中的關(guān)鍵注釋谣拣,構(gòu)建函數(shù)的主要作用是確定協(xié)議,端口族展,文件路徑森缠,以用于處理實際事務(wù)的handler。這里就假設(shè)就是HTTP協(xié)議仪缸,那么其確定下來的Handler就是HttpHandler.
URLStreamHandler是一個抽象類贵涵,HttpHandler是其子類。parseURL這類公共操作在父類URLStreamHandler中完成恰画,openConnection()因為會發(fā)生不同的行為宾茂,所以其在子類HttpHandler中完成。
public URLConnection openConnection() throws IOException {
471 return streamHandler.openConnection(this);
472 }
URL#openConnection()非常簡單就是進一步調(diào)用相應(yīng)Handler的openConnection()拴还,這里調(diào)用的是HttpHandler#openConnection()
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
33 if (url == null || proxy == null) {
34 throw new IllegalArgumentException("url == null || proxy == null");
35 }
36 return new HttpURLConnectionImpl(url, getDefaultPort(), proxy);
37 }
這里最終構(gòu)建了一個具體的實現(xiàn)類HttpURLConnectionImpl跨晴,它繼承自HttpURLConnection,而HttpURLConnection又繼承自URLConnection片林。構(gòu)建函數(shù)并沒有做什么特殊的事情端盆,而是一級一級將url向父類傳遞直到URLConnection,并最終記錄在URLConnection费封。同時這里還有一個需要提一下的是焕妙,這里還確定了默認(rèn)端口為 80。
3.參數(shù)設(shè)置
setDoOutput(),setChunkedStreamingMode(),setDoInput()等這些只是設(shè)置一些參數(shù)弓摘,也并沒有真正發(fā)起網(wǎng)絡(luò)請求焚鹊。稍微提一下setChunkedStreamingMode(),用來設(shè)置body的分塊大小韧献,系統(tǒng)建議默認(rèn)值寺旺,即為 1024。
除了這些基礎(chǔ)的參數(shù)可以進行設(shè)置势决,還一個關(guān)鍵的HTTP的request header參數(shù)的添加阻塑。
@Override public final void addRequestProperty(String field, String value) {
511 if (connected) {
512 throw new IllegalStateException("Cannot add request property after connection is made");
513 }
514 if (field == null) {
515 throw new NullPointerException("field == null");
516 }
517 rawRequestHeaders.add(field, value);
518 }
添加的field-value(通常描述是key-value)會被記錄到rawRequestHeaders中,它所屬的類是RawHeaders果复。另外一個要注意的是陈莽,它必須在連接之前就設(shè)置好,否則會引發(fā)異常。那么什么時候進行連接的呢走搁,進一步看getOutputStream吧独柑。
4.getOutputStream
過程有點長峡捡,畫的也有點細(xì)贱田,分了20步,需要耐著點性子看内边,不要畏難也不要怕麻煩曲稼,其實挺簡單的索绪。
下面還是來分步驟更詳細(xì)的了解其是如何建立起連接的。
@Override public final OutputStream getOutputStream() throws IOException {
197 connect();
198
199 OutputStream result = httpEngine.getRequestBody();
200 if (result == null) {
201 throw new ProtocolException("method does not support a request body: " + method);
202 } else if (httpEngine.hasResponse()) {
203 throw new ProtocolException("cannot write request body after response has been read");
204 }
205
206 return result;
207 }
根據(jù)前面的分析贫悄,getOutputStream調(diào)用的是HttpURLConnectionImpl的方法瑞驱。在這里調(diào)用了內(nèi)部方法 connect()
78 @Override public final void connect() throws IOException {
79 initHttpEngine();
80 try {
81 httpEngine.sendRequest();
82 } catch (IOException e) {
83 httpEngineFailure = e;
84 throw e;
85 }
86 }
這里僅有兩個關(guān)鍵調(diào)用,initHttpEngine()以及httpEngine.sendRequest()先來看initHttpEngine()方法窄坦。
private void initHttpEngine() throws IOException {
235 if (httpEngineFailure != null) {
236 throw httpEngineFailure;
237 } else if (httpEngine != null) {
238 return;
239 }
240 // 記錄 connected 狀態(tài)為 true唤反,記住不能再設(shè)置/添加Header參數(shù)了
241 connected = true;
242 try {
243 if (doOutput) { // 設(shè)置了 doOutput 且為 GET 方法的會被自動校正為 POST方法
244 if (method == HttpEngine.GET) {
245 // they are requesting a stream to write to. This implies a POST method
246 method = HttpEngine.POST;
247 } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
248 // If the request method is neither POST nor PUT, then you're not writing
249 throw new ProtocolException(method + " does not support writing");
250 }
251 }
// 該方法的關(guān)鍵,調(diào)用 newHttpEngine()
252 httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
253 } catch (IOException e) {
254 httpEngineFailure = e;
255 throw e;
256 }
257 }
這里的關(guān)鍵是記錄了connected狀態(tài)以及進一步調(diào)用 newHttpEngine()方法初始化HttpEngine實例鸭津。
259 /**
260 * Create a new HTTP engine. This hook method is non-final so it can be
261 * overridden by HttpsURLConnectionImpl.
262 */
263 protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
264 HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
265 return new HttpEngine(this, method, requestHeaders, connection, requestBody);
266 }
newHttpEngine是一個可重載的方法彤侍,不同協(xié)議會有自己的 Engine。這里僅僅是構(gòu)建了一個HttpEngine的具體對象實例逆趋,另外需要注意的關(guān)鍵點是 connection 是傳入的 null拥刻。繼續(xù)看 HttpEngine的構(gòu)建函數(shù)。
187 public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
188 HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
189 this.policy = policy;
190 this.method = method;
191 this.connection = connection;
192 this.requestBodyOut = requestBodyOut;
193
194 try {
195 uri = policy.getURL().toURILenient();
196 } catch (URISyntaxException e) {
197 throw new IOException(e);
198 }
199
200 this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
201 }
構(gòu)建函數(shù)只是簡單的記錄相應(yīng)的參數(shù)父泳。但這里有幾個重要的類是需要關(guān)注一下的,下面說說幾個重要的類吴汪。
HttpEngine
它在這里是一個管理類惠窄,HTTP等協(xié)議相關(guān)的如發(fā)起連接,獲取結(jié)果漾橙,斷開連接等都通過該類來完成杆融。此外,還有一個最重要的任務(wù)也是在這里完成霜运,即生成請求行以及包裝請求頭脾歇,都在這里完成。
RequestHeaders淘捡、RawHeaders
RawHeaders記錄的是比較原始的數(shù)據(jù)藕各,如協(xié)議版本號,請求行以及請求Header參數(shù)的fieldname-value焦除〖た觯可以看成就是個暫時記錄數(shù)據(jù)的地方。
RequestHeaders是對RawHeaders的進一步解析,也是HTTP協(xié)議相關(guān)的乌逐。來看看其構(gòu)造函數(shù)竭讳。
public RequestHeaders(URI uri, RawHeaders headers) {
65 this.uri = uri;
66 this.headers = headers;
67
68 HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
69 @Override public void handle(String directive, String parameter) {
70 if (directive.equalsIgnoreCase("no-cache")) {
71 noCache = true;
72 } else if (directive.equalsIgnoreCase("max-age")) {
73 maxAgeSeconds = HeaderParser.parseSeconds(parameter);
74 } else if (directive.equalsIgnoreCase("max-stale")) {
75 maxStaleSeconds = HeaderParser.parseSeconds(parameter);
76 } else if (directive.equalsIgnoreCase("min-fresh")) {
77 minFreshSeconds = HeaderParser.parseSeconds(parameter);
78 } else if (directive.equalsIgnoreCase("only-if-cached")) {
79 onlyIfCached = true;
80 }
81 }
82 };
83
84 for (int i = 0; i < headers.length(); i++) {
85 String fieldName = headers.getFieldName(i);
86 String value = headers.getValue(i);
87 if ("Cache-Control".equalsIgnoreCase(fieldName)) {
88 HeaderParser.parseCacheControl(value, handler);
89 } else if ("Pragma".equalsIgnoreCase(fieldName)) {
90 if (value.equalsIgnoreCase("no-cache")) {
91 noCache = true;
92 }
93 } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
94 ifNoneMatch = value;
95 } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
96 ifModifiedSince = value;
97 } else if ("Authorization".equalsIgnoreCase(fieldName)) {
98 hasAuthorization = true;
99 } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
100 try {
101 contentLength = Integer.parseInt(value);
102 } catch (NumberFormatException ignored) {
103 }
104 } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
105 transferEncoding = value;
106 } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
107 userAgent = value;
108 } else if ("Host".equalsIgnoreCase(fieldName)) {
109 host = value;
110 } else if ("Connection".equalsIgnoreCase(fieldName)) {
111 connection = value;
112 } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
113 acceptEncoding = value;
114 } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
115 contentType = value;
116 } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
117 proxyAuthorization = value;
118 }
119 }
120 }
非常的長,但不要畏難或者被嚇到浙踢。其實就是解析RawHeader中的Header字段绢慢,說白了就是處理數(shù)據(jù),只不過處理的協(xié)議頭的參數(shù)罷了洛波。
到這里胰舆,終于構(gòu)建好了 HttpEngine 類了,Header的參數(shù)也解析好了奋岁。下面就可以發(fā)請求了思瘟。好,來看 HttpEngine#sendRequest()闻伶。
207 /**
208 * Figures out what the response source will be, and opens a socket to that
209 * source if necessary. Prepares the request headers and gets ready to start
210 * writing the request body if it exists.
211 */
212 public final void sendRequest() throws IOException {
213 if (responseSource != null) {
214 return;
215 }
216 //1. 準(zhǔn)備請求頭的參數(shù)
217 prepareRawRequestHeaders();
// 2.初始化響應(yīng)資源
218 initResponseSource();
219 if (responseCache instanceof ExtendedResponseCache) {
220 ((ExtendedResponseCache) responseCache).trackResponse(responseSource);
221 }
222
223 /*
224 * The raw response source may require the network, but the request
225 * headers may forbid network use. In that case, dispose of the network
226 * response and use a GATEWAY_TIMEOUT response instead, as specified
227 * by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
228 */
229 if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
230 if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
231 IoUtils.closeQuietly(cachedResponseBody);
232 }
233 this.responseSource = ResponseSource.CACHE;
234 this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
235 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders());
236 setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
237 }
238
239 if (responseSource.requiresConnection()) {
// 3.發(fā)起 socket 請求
240 sendSocketRequest();
241 } else if (connection != null) {
242 HttpConnectionPool.INSTANCE.recycle(connection);
243 connection = null;
244 }
245 }
這里有三個的關(guān)鍵調(diào)用滨攻,在注釋中有描述。下面一個一個來看吧蓝翰。先看 prepareRawRequestHeaders()方法光绕。
prepareRawRequestHeaders()
693 /**
694 * Populates requestHeaders with defaults and cookies.
695 *
696 * <p>This client doesn't specify a default {@code Accept} header because it
697 * doesn't know what content types the application is interested in.
698 */
699 private void prepareRawRequestHeaders() throws IOException {
700 requestHeaders.getHeaders().setStatusLine(getRequestLine());
701
702 if (requestHeaders.getUserAgent() == null) {
703 requestHeaders.setUserAgent(getDefaultUserAgent());
704 }
705
706 if (requestHeaders.getHost() == null) {
707 requestHeaders.setHost(getOriginAddress(policy.getURL()));
708 }
709
710 if (httpMinorVersion > 0 && requestHeaders.getConnection() == null) {
711 requestHeaders.setConnection("Keep-Alive");
712 }
713
714 if (requestHeaders.getAcceptEncoding() == null) {
715 transparentGzip = true;
716 requestHeaders.setAcceptEncoding("gzip");
717 }
718
719 if (hasRequestBody() && requestHeaders.getContentType() == null) {
720 requestHeaders.setContentType("application/x-www-form-urlencoded");
721 }
722
723 long ifModifiedSince = policy.getIfModifiedSince();
724 if (ifModifiedSince != 0) {
725 requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
726 }
727
728 CookieHandler cookieHandler = CookieHandler.getDefault();
729 if (cookieHandler != null) {
730 requestHeaders.addCookies(
731 cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap()));
732 }
733 }
這里設(shè)置了請求行:如 GET url HTTP/1.1,設(shè)置UA畜份,設(shè)置Host诞帐,設(shè)置 Connection 為 keep-alive,設(shè)置 accept-encoding 為gzip爆雹。設(shè)置 content-type 為 "application/x-www-form-urlencoded",設(shè)置 if-Modified-Since停蕉,添加發(fā)送給服務(wù)器的 cookie。
準(zhǔn)備好了請求參數(shù)钙态,下面來看一看initResponseSource()方法慧起。
initResponseSource
private void initResponseSource() throws IOException {
// 默認(rèn)為是從網(wǎng)絡(luò)獲取
252 responseSource = ResponseSource.NETWORK;
253 if (!policy.getUseCaches() || responseCache == null) {
254 return;
255 }
256 // 查看是否有緩存
257 CacheResponse candidate = responseCache.get(uri, method,
258 requestHeaders.getHeaders().toMultimap());
259 if (candidate == null) {
260 return;
261 }
262 // 有緩存的情況下取出響Header與body
263 Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
264 cachedResponseBody = candidate.getBody();
// 對于不接受的緩存類型直接關(guān)掉被返回
265 if (!acceptCacheResponseType(candidate)
266 || responseHeadersMap == null
267 || cachedResponseBody == null) {
268 IoUtils.closeQuietly(cachedResponseBody);
269 return;
270 }
271 // 緩存可接受,從responseHeadersMap解析出RawHeaders册倒,這個類在上面的分析中已經(jīng)講過了◎炯罚現(xiàn)在看來它也同時用于記錄 Response 的 Header
272 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
// 構(gòu)建 ResponseHeaders
273 cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
274 long now = System.currentTimeMillis();
// 最后的 response 的實際來源
275 this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
276 if (responseSource == ResponseSource.CACHE) {//如果是cache,把剛才的 request 以及 response 包裝成Response驻子,這也是要返回給上層用戶的結(jié)果灿意。
277 this.cacheResponse = candidate;
278 setResponse(cachedResponseHeaders, cachedResponseBody);
279 } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {//條件緩存
280 this.cacheResponse = candidate;
281 } else if (responseSource == ResponseSource.NETWORK) {//網(wǎng)絡(luò)則直接關(guān)閉緩存
282 IoUtils.closeQuietly(cachedResponseBody);
283 } else {
284 throw new AssertionError();
285 }
286 }
287
initResponseSource的主要作用決定請求的結(jié)果是由哪里來的。共有三種情況:網(wǎng)絡(luò)崇呵,緩存缤剧,條件緩存。網(wǎng)絡(luò)沒什么好解釋的域慷,緩存被禁用或者沒有自然就是要發(fā)起網(wǎng)絡(luò)請了鞭执。
緩存司顿,相應(yīng)的類是ResponseCache,這是一個接口兄纺,其具體的子類為HttpResponseCache大溜。關(guān)于這個類的細(xì)節(jié)實現(xiàn)沒有必要再進一步分析,因為其和Volley還算比較深入的分析中的緩存差不太多估脆,緩存算法也是 DiskLruCache钦奋,但這里的key有點差別,用的是 url 的md5編碼疙赠,既安全又節(jié)省資源大小付材。
條件緩存,與Http協(xié)議的緩存密切相關(guān)圃阳。在chooseResponseSource()中最后會根據(jù)request的請求頭參數(shù)來確定是否為條件緩存厌衔。
280 public boolean hasConditions() {
281 return ifModifiedSince != null || ifNoneMatch != null;
282 }
而它們所對應(yīng)的字段就是"If-None-Match"和"If-Modified-Since"。
假設(shè)緩存里沒有吧捍岳,那就要發(fā)起真正的網(wǎng)絡(luò)請了富寿。來看看 sendSocketRequest()方法。
sendSocketRequest
private void sendSocketRequest() throws IOException {
289 if (connection == null) {
290 connect();
291 }
292
293 if (socketOut != null || requestOut != null || socketIn != null) {
294 throw new IllegalStateException();
295 }
296
297 socketOut = connection.getOutputStream();
298 requestOut = socketOut;
299 socketIn = connection.getInputStream();
300
301 if (hasRequestBody()) {
302 initRequestBodyOut();
303 }
304 }
這里以 connection 來決定是否要發(fā)起內(nèi)部的 connect()锣夹。connection是 HttpConnection的實例页徐。進一步看看connect()的代碼。
309 protected void connect() throws IOException {
310 if (connection == null) {
311 connection = openSocketConnection();
312 }
313 }
比較簡單银萍,進一步調(diào)用了 openSocketConnection()來構(gòu)建一個 connection变勇。
315 protected final HttpConnection openSocketConnection() throws IOException {
316 HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(),
317 policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
318 Proxy proxy = result.getAddress().getProxy();
319 if (proxy != null) {
320 policy.setProxy(proxy);
321 }
322 result.setSoTimeout(policy.getReadTimeout());
323 return result;
324 }
進一步調(diào)用HttpConnection的靜態(tài)方法connect()來創(chuàng)建 HttpConnection。
public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
90 Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException {
91 /*
92 * Try an explicitly-specified proxy.
93 */
94 if (proxy != null) {
95 Address address = (proxy.type() == Proxy.Type.DIRECT)
96 ? new Address(uri, sslSocketFactory)
97 : new Address(uri, sslSocketFactory, proxy, requiresTunnel);
98 return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
99 }
100
101 /*
102 * Try connecting to each of the proxies provided by the ProxySelector
103 * until a connection succeeds.
104 */
105 ProxySelector selector = ProxySelector.getDefault();
106 List<Proxy> proxyList = selector.select(uri);
107 if (proxyList != null) {
108 for (Proxy selectedProxy : proxyList) {
109 if (selectedProxy.type() == Proxy.Type.DIRECT) {
110 // the same as NO_PROXY
111 // TODO: if the selector recommends a direct connection, attempt that?
112 continue;
113 }
114 try {
115 Address address = new Address(uri, sslSocketFactory,
116 selectedProxy, requiresTunnel);
117 return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
118 } catch (IOException e) {
119 // failed to connect, tell it to the selector
120 selector.connectFailed(uri, selectedProxy.address(), e);
121 }
122 }
123 }
124
125 /*
126 * Try a direct connection. If this fails, this method will throw.
127 */
128 return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout);
129 }
這一堆的代碼贴唇,前面都是設(shè)置代理的搀绣,不管它。真正管用的是最后一句HttpConnectionPool.INSTANCE#get()戳气。即從HttpConnectionPool里返回一個HttpConnection链患。補充一點,INSTANCE代表著HttpConnectionPool對象本身物咳,其實現(xiàn)為一個單例類。進一步看看是如何 Get 的吧蹄皱。
public HttpConnection get(HttpConnection.Address address, int connectTimeout)
64 throws IOException {
65 // First try to reuse an existing HTTP connection.
66 synchronized (connectionPool) {
67 List<HttpConnection> connections = connectionPool.get(address);
68 while (connections != null) {
69 HttpConnection connection = connections.remove(connections.size() - 1);
70 if (connections.isEmpty()) {
71 connectionPool.remove(address);
72 connections = null;
73 }
74 if (connection.isEligibleForRecycling()) {
75 // Since Socket is recycled, re-tag before using
76 Socket socket = connection.getSocket();
77 SocketTagger.get().tag(socket);
78 return connection;
79 }
80 }
81 }
82
83 /*
84 * We couldn't find a reusable connection, so we need to create a new
85 * connection. We're careful not to do so while holding a lock!
86 */
87 return address.connect(connectTimeout);
88 }
Get()是一個常見的緩存池的套路览闰。先判斷當(dāng)前連接池里是否包含有同一Address的且沒有被回收的連接,如果能找到就重新re-tag一下socket巷折,再返回當(dāng)前找到的HttpCoonection压鉴。如果沒有找到就通過 address#connect()方法來創(chuàng)建一個。先不進行下一步來關(guān)注兩個比較重要的點锻拘。
isEligibleForRecycling()方法的實現(xiàn)
248 /**
249 * Returns true if this connection is eligible to be reused for another
250 * request/response pair.
251 */
252 protected boolean isEligibleForRecycling() {
253 return !socket.isClosed()
254 && !socket.isInputShutdown()
255 && !socket.isOutputShutdown();
256 }
從代碼中知道油吭,未被回收的連接的判定條件是:socket未關(guān)閉击蹲,輸入以及輸出流未被關(guān)閉。
連接池的Key——Address
Address是HttpConnection的內(nèi)部類婉宰。我們知道任何一個xx池之類的歌豺,都要有一個唯一的key用來判斷是否為同一資源。這里使用Address對象心包,看起來有點奇怪类咧。既然這么用了,那么它一定是重寫了 equal()方法和hashcode()方法了蟹腾。來看一看痕惋。
@Override public boolean equals(Object other) {
318 if (other instanceof Address) {
319 Address that = (Address) other;
320 return Objects.equal(this.proxy, that.proxy)
321 && this.uriHost.equals(that.uriHost)
322 && this.uriPort == that.uriPort
323 && Objects.equal(this.sslSocketFactory, that.sslSocketFactory)
324 && this.requiresTunnel == that.requiresTunnel;
325 }
326 return false;
327 }
328
329 @Override public int hashCode() {
330 int result = 17;
331 result = 31 * result + uriHost.hashCode();
332 result = 31 * result + uriPort;
333 result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
334 result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
335 result = 31 * result + (requiresTunnel ? 1 : 0);
336 return result;
337 }
果然是被重寫了。從上面兩個方法也可以看出娃殖,判斷為同一Key值戳,需要 host,port,sslfactory,proxy,tunel都是相等的才能算是同一key。
下面繼續(xù)看HttpConnection是如何創(chuàng)建的以及創(chuàng)建完成后做了什么炉爆。前面有分析到堕虹,如果連接池里沒有就會創(chuàng)建一個新的。而創(chuàng)建一個新的HttpConnection是通過HttpConnection的子類Address#connect()方法進行的叶洞。
public HttpConnection connect(int connectTimeout) throws IOException {
340 return new HttpConnection(this, connectTimeout);
341 }
挺簡單的鲫凶,直接new一個實例,那來看看HttpConnection的構(gòu)建函數(shù)衩辟。
private HttpConnection(Address config, int connectTimeout) throws IOException {
62 this.address = config;
63
64 /*
65 * Try each of the host's addresses for best behavior in mixed IPv4/IPv6
66 * environments. See http://b/2876927
67 * TODO: add a hidden method so that Socket.tryAllAddresses can does this for us
68 */
69 Socket socketCandidate = null;
// 1.尋址螟炫,即通過 hostname查找 IP 地址,可能會查到多個艺晴。
70 InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
71 for (int i = 0; i < addresses.length; i++) {
//4. 創(chuàng)建用于執(zhí)行TCP連接的套接字 Socket
72 socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
73 ? new Socket(config.proxy)
74 : new Socket();
75 try {
// 3. 發(fā)起套接字Socket的TCP請求連接
76 socketCandidate.connect(
77 new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
78 break;
79 } catch (IOException e) {
// 4.所有地址都試過了昼钻,彈盡糧絕了,報異常封寞。
80 if (i == addresses.length - 1) {
81 throw e;
82 }
83 }
84 }
85
86 this.socket = socketCandidate;
87 }
雖然只是一個構(gòu)造函數(shù)然评,但復(fù)雜度確不低,按照注釋的步驟來詳細(xì)的看一下狈究。
尋址
調(diào)用的是InetAddress#getAllByName()碗淌,這是一個靜態(tài)方法。
public static InetAddress[] getAllByName(String host) throws UnknownHostException {
214 return getAllByNameImpl(host).clone();
215 }
216
217 /**
218 * Returns the InetAddresses for {@code host}. The returned array is shared
219 * and must be cloned before it is returned to application code.
220 */
221 private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
// 為空則判定為回路地址抖锥,即 127.0.0.1
222 if (host == null || host.isEmpty()) {
223 return loopbackAddresses();
224 }
225
226 // Is it a numeric address?本身就是IP地址嗎亿眠?是的話就不用繼續(xù)找了。
227 InetAddress result = parseNumericAddressNoThrow(host);
228 if (result != null) {
229 result = disallowDeprecatedFormats(host, result);
230 if (result == null) {
231 throw new UnknownHostException("Deprecated IPv4 address format: " + host);
232 }
233 return new InetAddress[] { result };
234 }
235 // 前面的都不是磅废,那就要進一步查找了纳像。
236 return lookupHostByName(host).clone();
237 }
這里需要關(guān)注的是對內(nèi)部方法lookupHostByName()的調(diào)用。
378 /**
379 * Resolves a hostname to its IP addresses using a cache.
380 *
381 * @param host the hostname to resolve.
382 * @return the IP addresses of the host.
383 */
384 private static InetAddress[] lookupHostByName(String host) throws UnknownHostException {
385 BlockGuard.getThreadPolicy().onNetwork();
386 // Do we have a result cached?
// 1.先從緩存里面找
387 Object cachedResult = addressCache.get(host);
388 if (cachedResult != null) {
389 if (cachedResult instanceof InetAddress[]) {
390 // A cached positive result.
391 return (InetAddress[]) cachedResult;
392 } else {
393 // A cached negative result.
394 throw new UnknownHostException((String) cachedResult);
395 }
396 }
397 try {
398 StructAddrinfo hints = new StructAddrinfo();
399 hints.ai_flags = AI_ADDRCONFIG;
400 hints.ai_family = AF_UNSPEC;
401 // If we don't specify a socket type, every address will appear twice, once
402 // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
403 // anyway, just pick one.
// 2.緩存里面沒有拯勉,調(diào)用native方法進一步查找竟趾。Libcore.os.getaddrinfo底層會發(fā)起DNS域名解析請求
404 hints.ai_socktype = SOCK_STREAM;
405 InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
406 // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
407 for (InetAddress address : addresses) {
408 address.hostName = host;
409 }
410 addressCache.put(host, addresses);
411 return addresses;
412 } catch (GaiException gaiException) {
413 // If the failure appears to have been a lack of INTERNET permission, throw a clear
414 // SecurityException to aid in debugging this common mistake.
415 // http://code.google.com/p/android/issues/detail?id=15722
416 if (gaiException.getCause() instanceof ErrnoException) {
417 if (((ErrnoException) gaiException.getCause()).errno == EACCES) {
418 throw new SecurityException("Permission denied (missing INTERNET permission?)", gaiException);
419 }
420 }
421 // Otherwise, throw an UnknownHostException.
422 String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
423 addressCache.putUnknownHost(host, detailMessage);
424 throw gaiException.rethrowAsUnknownHostException(detailMessage);
425 }
426 }
尋址就是DNS域名解析憔购,會先從緩存中查看是存在,如果沒有則會由底層發(fā)起請求到DNS服務(wù)器來解析域名映射到實際用于通信的IP地址岔帽。
創(chuàng)建socket并發(fā)起TCP連接
Socket本身還只是一個包裝類玫鸟,它的connect()會繼續(xù)調(diào)具體的實現(xiàn)類。由Socket的構(gòu)造函數(shù)可知山卦,它默認(rèn)是PlainSocketImpl鞋邑,也就是最后調(diào)用了它的connect()方法進行TCP連接。
56 this.impl = factory != null ? factory.createSocketImpl() : new PlainSocketImpl();
57 this.proxy = null;
58 }
到這里終于看到連接發(fā)出去了账蓉。原來構(gòu)建一個HttpConnection就會發(fā)起TCP連接了枚碗。創(chuàng)建好了HttpConnection,方法就會原路返回铸本,并最終返回輸出流給到示例處調(diào)用 getOutputStream()的地方肮雨。
5.getInputStream()
getInputStream()與getOutputStream()的整個執(zhí)行流程是大同小異的,有很多的共同部分箱玷,比如也可能發(fā)起網(wǎng)絡(luò)連接怨规,建立TCP的三次握手。
有了前面getOutputStream的基礎(chǔ)锡足,getInputStream就簡單多了波丰。
@Override public final InputStream getInputStream() throws IOException {
173 if (!doInput) {
174 throw new ProtocolException("This protocol does not support input");
175 }
176 // 獲取 HttpEngine
177 HttpEngine response = getResponse();
178
179 /*
180 * if the requested file does not exist, throw an exception formerly the
181 * Error page from the server was returned if the requested file was
182 * text/html this has changed to return FileNotFoundException for all
183 * file types
184 */
185 if (getResponseCode() >= HTTP_BAD_REQUEST) {
186 throw new FileNotFoundException(url.toString());
187 }
188 // 從 HttpEngin 那里獲取 InputStream
189 InputStream result = response.getResponseBody();
190 if (result == null) {
191 throw new IOException("No response body exists; responseCode=" + getResponseCode());
192 }
193 return result;
194 }
如注釋第一步是獲取HttpEngine,因為在getOutputStream階段已經(jīng)創(chuàng)建好了舶得,所以不會再重新創(chuàng)建了掰烟,用現(xiàn)成的就好。第二步就是從HttpEngine那里拿到 response body沐批。當(dāng)然這里需要進一步分析的是 getResponse纫骑。
/**
269 * Aggressively tries to get the final HTTP response, potentially making
270 * many HTTP requests in the process in order to cope with redirects and
271 * authentication.
272 */
273 private HttpEngine getResponse() throws IOException {
274 initHttpEngine();
275
276 if (httpEngine.hasResponse()) {
277 return httpEngine;
278 }
279
280 while (true) {
281 try {
282 httpEngine.sendRequest();
283 httpEngine.readResponse();
284 } catch (IOException e) {
285 /*
286 * If the connection was recycled, its staleness may have caused
287 * the failure. Silently retry with a different connection.
288 */
289 OutputStream requestBody = httpEngine.getRequestBody();
290 if (httpEngine.hasRecycledConnection()
291 && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
292 httpEngine.release(false);
293 httpEngine = newHttpEngine(method, rawRequestHeaders, null,
294 (RetryableOutputStream) requestBody);
295 continue;
296 }
297 httpEngineFailure = e;
298 throw e;
299 }
300 // 處理響應(yīng)頭,確定重試的策略九孩。
301 Retry retry = processResponseHeaders();
302 if (retry == Retry.NONE) {
// 正常情況下不需要重試先馆,因此這里也標(biāo)記HttpConnection由HttpConnectionPool來自動釋放。
303 httpEngine.automaticallyReleaseConnectionToPool();
304 return httpEngine;
305 }
306
307 /*
308 * The first request was insufficient. Prepare for another...
309 */
310 String retryMethod = method;
311 OutputStream requestBody = httpEngine.getRequestBody();
312
313 /*
314 * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
315 * redirect should keep the same method, Chrome, Firefox and the
316 * RI all issue GETs when following any redirect.
317 */
318 int responseCode = getResponseCode();
319 if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
320 || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
321 retryMethod = HttpEngine.GET;
322 requestBody = null;
323 }
324
325 if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
326 throw new HttpRetryException("Cannot retry streamed HTTP body",
327 httpEngine.getResponseCode());
328 }
329
330 if (retry == Retry.DIFFERENT_CONNECTION) {
331 httpEngine.automaticallyReleaseConnectionToPool();
332 } else {
333 httpEngine.markConnectionAsRecycled();
334 }
335 // 需要重試情況下躺彬,主動斷開連接
336 httpEngine.release(true);
337 // 生成一個新的 HttpEngine
338 httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,
339 httpEngine.getConnection(), (RetryableOutputStream) requestBody);
340 }
341 }
如前所述initHttpEngine()以及httpEngine.sendRequest在getOutputStream那里已經(jīng)做了詳細(xì)分析了煤墙,這里也不用再贅述了。
這里有HttpEngine中的兩個方法需要關(guān)注宪拥,它與如何緩存響應(yīng)數(shù)據(jù)以及如何重用連接有關(guān)仿野。先來看readResponse()方法。
/**
794 * Flushes the remaining request header and body, parses the HTTP response
795 * headers and starts reading the HTTP response body if it exists.
796 */
797 public final void readResponse() throws IOException {
798 if (hasResponse()) {
799 return;
800 }
801
802 if (responseSource == null) {
803 throw new IllegalStateException("readResponse() without sendRequest()");
804 }
805
806 if (!responseSource.requiresConnection()) {
807 return;
808 }
809
810 if (sentRequestMillis == -1) {
811 int contentLength = requestBodyOut instanceof RetryableOutputStream
812 ? ((RetryableOutputStream) requestBodyOut).contentLength()
813 : -1;
814 writeRequestHeaders(contentLength);
815 }
816
817 if (requestBodyOut != null) {
818 requestBodyOut.close();
819 if (requestBodyOut instanceof RetryableOutputStream) {
820 ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);
821 }
822 }
823
824 requestOut.flush();
825 requestOut = socketOut;
826 // 讀取響應(yīng)頭
827 readResponseHeaders();
828 responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
829
830 if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
831 if (cachedResponseHeaders.validate(responseHeaders)) {
832 release(true);//如果是條件緩存且緩存有效則釋放連接并標(biāo)記為重用連接
833 ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
834 setResponse(combinedHeaders, cachedResponseBody);
835 if (responseCache instanceof ExtendedResponseCache) {
836 ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache;
837 httpResponseCache.trackConditionalCacheHit();
838 httpResponseCache.update(cacheResponse, getHttpConnectionToCache());
839 }
840 return;
841 } else {
842 IoUtils.closeQuietly(cachedResponseBody);
843 }
844 }
845
846 if (hasResponseBody()) {
// 將響應(yīng)數(shù)據(jù)寫入緩存
847 maybeCache(); // reentrant. this calls into user code which may call back into this!
848 }
849 // 獲取響應(yīng)體的輸入流江解。
850 initContentStream(getTransferStream());
851 }
注釋里面有幾個關(guān)鍵的內(nèi)部方法調(diào)用设预,一個一個來看吧徙歼。
readResponseHeaders讀取響應(yīng)行與響應(yīng)頭
5 private void readResponseHeaders() throws IOException {
576 RawHeaders headers;
577 do {
578 headers = new RawHeaders();
// 讀取響應(yīng)行
579 headers.setStatusLine(Streams.readAsciiLine(socketIn));
// 讀取其他響應(yīng)頭參數(shù)犁河,下面直接貼出來了
580 readHeaders(headers);
581 } while (headers.getResponseCode() == HTTP_CONTINUE);
582 setResponse(new ResponseHeaders(uri, headers), null);
583 }
624 private void readHeaders(RawHeaders headers) throws IOException {
625 // parse the result headers until the first blank line
626 String line;
// 直到讀到空行為止鳖枕,頭與數(shù)據(jù)以空行分開
627 while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) {
628 headers.addLine(line);
629 }
630 // 解析 cookie
631 CookieHandler cookieHandler = CookieHandler.getDefault();
632 if (cookieHandler != null) {
633 cookieHandler.put(uri, headers.toMultimap());
634 }
635 }
關(guān)鍵的東西在代碼里有注釋了,主要是讀響應(yīng)行桨螺,讀響應(yīng)頭并設(shè)置cookie宾符。而響應(yīng)頭與響應(yīng)數(shù)據(jù)之間的空格就是空行。其實請求的也是灭翔,如果包含有請求體時魏烫,請求頭與請求體之間也是空行分開。
release肝箱,這里先不貼代碼哄褒,后面會再繼續(xù)分析,只要記住在條件緩存的情況下會釋放connection煌张,且?guī)У膮?shù)為 true.
maybeCache呐赡,將響應(yīng)數(shù)據(jù)寫入HttpResponseCache,比較簡單骏融,不展開了链嘀。
getTransferStream,構(gòu)建返回給調(diào)用者的 InputStream档玻。終于拿到InputStream了怀泊。
readResponse()已經(jīng)解析完了,然而在getResponse中還有工作需要做误趴。其接下來會通過處理響應(yīng)頭來確認(rèn)是否需要重試或者重試的策略霹琼,是否需要授權(quán)(401,可能需要用戶名或者密碼),是否需要重定向次數(shù)已達到最大(5次)冤留。不需要重試的情況下標(biāo)記請求由請求池自動回收碧囊。需要重試的情況下,釋放并重用連接纤怒,因為這里調(diào)用了HttpEngine#release()方法時傳入的參數(shù)為 true糯而。這里同樣記住傳入的參數(shù)為true
6.小結(jié)
連接所涉及的東西有點多,也可能有些亂泊窘,也記不住了熄驼,那先總結(jié)一波吧。
6.1openConnection()方法
該方法主要是根據(jù)協(xié)議確定對應(yīng)的幫助類HttpHandler以及HttpURLConnectionImpl烘豹。之后的操作都基于HttpURLConnectionImpl來進行瓜贾。
6.2參數(shù)設(shè)置
在getOutpuStream()以及getInputStream()或者顯式直接調(diào)用connect()方法之前設(shè)置好參數(shù),否則會報異常携悯。這三個方法都會觸發(fā)狀態(tài) connected 為 true祭芦。
6.3Http協(xié)議包裝
在發(fā)出請求前,會構(gòu)造好請求行憔鬼,請求頭參數(shù)一起發(fā)送給服務(wù)器龟劲。并且注意到在HTTP的版本大于 0 的情況下胃夏,默認(rèn)開啟了 keep-alive
6.4緩存
根據(jù)HTTP的緩存規(guī)則,在getInputStream階段會對響應(yīng)回來的數(shù)據(jù)即response進行緩存昌跌。緩存的內(nèi)部算法采用的是DiskLRUCache實現(xiàn)仰禀,key 為對應(yīng) url 的MD5編碼。
6.5連接池
對當(dāng)前連接好的HTTP會通過連接池緩存起來蚕愤,連接池里已經(jīng)有相等的Address了就會進行重用答恶。其中Address相等需要host,port,sslfactory,proxy,tunel都相等。連接池的全部內(nèi)容這里其實還沒有講完萍诱,比如連接put以及回收悬嗓。
6.6TCP連接
getOutpuStream()以及getInputStream()或者顯式直接調(diào)用connect()方法最終都會觸發(fā)TCP的連接,也就是我們需要的HTTP連接裕坊。
三烫扼、HTTP通信
所謂的通信,就是利用getOutputStream()獲取輸出流碍庵,從而將數(shù)據(jù)寫到服務(wù)端映企。再利用getInputStream()獲取輸入流,讀取從服務(wù)端返回的數(shù)據(jù)静浴。
四堰氓、斷開連接
先來看disconnect()方法
@Override public final void disconnect() {
89 // Calling disconnect() before a connection exists should have no effect.
90 if (httpEngine != null) {
91 // We close the response body here instead of in
92 // HttpEngine.release because that is called when input
93 // has been completely read from the underlying socket.
94 // However the response body can be a GZIPInputStream that
95 // still has unread data.
96 if (httpEngine.hasResponse()) {
97 IoUtils.closeQuietly(httpEngine.getResponseBody());
98 }
99 httpEngine.release(false);
100 }
101 }
主要是進一步調(diào)用 HttpEngine#release()方法進行釋放。另外需要注意的是關(guān)閉了responseBody苹享,那這個又是什么呢双絮?后面會詳情講解。
/**
483 * Releases this engine so that its resources may be either reused or
484 * closed.
485 */
486 public final void release(boolean reusable) {
487 // If the response body comes from the cache, close it.
// 從緩存取的結(jié)果直接關(guān)掉
488 if (responseBodyIn == cachedResponseBody) {
489 IoUtils.closeQuietly(responseBodyIn);
490 }
491
492 if (!connectionReleased && connection != null) {
493 connectionReleased = true;
494
495 // We cannot reuse sockets that have incomplete output.
// 輸出數(shù)據(jù)流還沒有關(guān)閉不能重用
496 if (requestBodyOut != null && !requestBodyOut.closed) {
497 reusable = false;
498 }
499
500 // If the request specified that the connection shouldn't be reused,
501 // don't reuse it. This advice doesn't apply to CONNECT requests because
502 // the "Connection: close" header goes the origin server, not the proxy.
// 請求協(xié)議頭描述了connection為close的不能重用
503 if (requestHeaders.hasConnectionClose() && method != CONNECT) {
504 reusable = false;
505 }
506
507 // If the response specified that the connection shouldn't be reused, don't reuse it.
// 響應(yīng)協(xié)議頭描述了connection為close的不能重用
508 if (responseHeaders != null && responseHeaders.hasConnectionClose()) {
509 reusable = false;
510 }
511 // 不知道長度的輸入流不能重用
512 if (responseBodyIn instanceof UnknownLengthHttpInputStream) {
513 reusable = false;
514 }
515
516 if (reusable && responseBodyIn != null) {
517 // We must discard the response body before the connection can be reused.
518 try {
519 Streams.skipAll(responseBodyIn);
520 } catch (IOException e) {
521 reusable = false;
522 }
523 }
524
525 if (!reusable) {
526 connection.closeSocketAndStreams();
527 connection = null;
528 } else if (automaticallyReleaseConnectionToPool) {
529 HttpConnectionPool.INSTANCE.recycle(connection);
530 connection = null;
531 }
532 }
533 }
中間一大段都可以先不用太關(guān)注得问,看看注釋就行囤攀。看最后幾句宫纬,注意這里的 reuseable 傳遞進來的是false焚挠,那么最后是一定會關(guān)閉連接的了。也就是說一旦執(zhí)行disconnect()連接就會被關(guān)閉漓骚。
131 public void closeSocketAndStreams() {
132 IoUtils.closeQuietly(sslOutputStream);
133 IoUtils.closeQuietly(sslInputStream);
134 IoUtils.closeQuietly(sslSocket);
135 IoUtils.closeQuietly(outputStream);
136 IoUtils.closeQuietly(inputStream);
137 IoUtils.closeQuietly(socket);
138 }
最后把socket與輸入輸出流都關(guān)閉了蝌衔。也就是說整個連接都斷開了。那如果要保持連接一直是重用的蝌蹂,那就不能disconnect()了噩斟。這樣又是否會有內(nèi)存泄漏?
還記得前面提到過的嗎孤个?在兩種情況下框架會自動發(fā)生重用剃允,一個是重試情況下,一個條件緩存情況下。所以說這個連接重用還真的是弱呢斥废。
關(guān)于HttpConnectionPool#recycle()方法來繼續(xù)看代碼覆享。
public void recycle(HttpConnection connection) {
91 Socket socket = connection.getSocket();
92 try {
93 SocketTagger.get().untag(socket);
94 } catch (SocketException e) {
95 // When unable to remove tagging, skip recycling and close
96 System.logW("Unable to untagSocket(): " + e);
97 connection.closeSocketAndStreams();
98 return;
99 }
100 //
101 if (maxConnections > 0 && connection.isEligibleForRecycling()) {
102 HttpConnection.Address address = connection.getAddress();
103 synchronized (connectionPool) {
104 List<HttpConnection> connections = connectionPool.get(address);
105 if (connections == null) {
106 connections = new ArrayList<HttpConnection>();
107 connectionPool.put(address, connections);
108 }
// 未達到最大連接數(shù)
109 if (connections.size() < maxConnections) {
110 connection.setRecycled();
// 把連接加入到連接池
111 connections.add(connection);
112 return; // keep the connection open
113 }
114 }
115 }
116
117 // don't close streams while holding a lock!
// 不能重用就關(guān)閉
118 connection.closeSocketAndStreams();
119 }
在recycle()時判斷是否已經(jīng)達到最大連接數(shù),如果沒有就可以重用营袜,否則就會關(guān)閉連接。這里還需要關(guān)注一下maxConnections丑罪,默認(rèn)情況下每個URI最大的連接數(shù)為 5荚板。如何得來的?需要關(guān)注一下HttpConnectionPool的構(gòu)造函數(shù)吩屹。
private HttpConnectionPool() {
51 String keepAlive = System.getProperty("http.keepAlive");
52 if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
53 maxConnections = 0;
54 return;
55 }
56
57 String maxConnectionsString = System.getProperty("http.maxConnections");
58 this.maxConnections = maxConnectionsString != null
59 ? Integer.parseInt(maxConnectionsString)
60 : 5;
61 }
也就是說需要系統(tǒng)設(shè)置了 http.keepAlive 這個參數(shù)為true跪另,那么就可以進一步設(shè)置maxConnections的大小了∶核眩可以自己定義免绿,也可以使用默認(rèn)值為5.
五、總結(jié)
(1)至此擦盾,Android原生網(wǎng)絡(luò)庫嘲驾,僅HTTP部分終于分析完了,還是學(xué)習(xí)不到少東西的迹卢。
(2)Android原生網(wǎng)絡(luò)庫只是HTTP協(xié)議在網(wǎng)絡(luò)應(yīng)用層的實現(xiàn)辽故,有一定的封裝,但本身并沒有進行過多的擴展腐碱。
(3)這里面我們能看到最基本的一些Http協(xié)議相關(guān)的內(nèi)容誊垢,如請求行,請求頭以及請求體的封裝症见,解析喂走,默認(rèn)參數(shù)設(shè)置等。當(dāng)然也有對響應(yīng)行谋作,響應(yīng)頭以及響應(yīng)體的解析芋肠,封裝,默認(rèn)處理等遵蚜。對了业栅,還有一個重要的,那就是cookie谬晕,也有相應(yīng)的解析以及處理碘裕,包括請求和響應(yīng)時。
(4)Android原生網(wǎng)絡(luò)庫同樣也實現(xiàn)了響應(yīng)內(nèi)容的緩存攒钳,所用的算法也是DiskLRUCache
(5)關(guān)于重用帮孔,除了依賴Http的請求參數(shù) keep-alive通知服務(wù)器希望連接之外,還依賴于系統(tǒng)對 http.keepalive的設(shè)定。而同一個url默認(rèn)最多只能有5個連接文兢。