Android 4.4以上使用HttpURLConnection底层使用OkHttp实现的源码分析
研究了一下HttpURLConnection的源码:
在使用的时候都是通过URL.openConnection()来获取HttpURLConnection对象,然后调用其connect方法进行链接,所以先从URL.penConnection()入手:
/*** Returns a new connection to the resource referred to by this URL.** @throws IOException if an error occurs while opening the connection.*/
public URLConnection openConnection() throws IOException {return streamHandler.openConnection(this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
接下来就要看一下streamHandler究竟是何方神圣?我们搜一下他的赋值,实在setupStreamHandler方法中进行的:
/*** Sets the receiver's stream handler to one which is appropriate for its* protocol.** <p>Note that this will overwrite any existing stream handler with the new* one. Senders must check if the streamHandler is null before calling the* method if they do not want this behavior (a speed optimization).** @throws MalformedURLException if no reasonable handler is available.*/
void setupStreamHandler() {// Check for a cached (previously looked up) handler for// the requested protocol.streamHandler = streamHandlers.get(protocol);if (streamHandler != null) {return;}// If there is a stream handler factory, then attempt to// use it to create the handler.if (streamHandlerFactory != null) {streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);if (streamHandler != null) {streamHandlers.put(protocol, streamHandler);return;}}// Check if there is a list of packages which can provide handlers.// If so, then walk this list looking for an applicable one.String packageList = System.getProperty("java.protocol.handler.pkgs");ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();if (packageList != null && contextClassLoader != null) {for (String packageName : packageList.split("\\|")) {String className = packageName + "." + protocol + ".Handler";try {Class<?> c = contextClassLoader.loadClass(className);streamHandler = (URLStreamHandler) c.newInstance();if (streamHandler != null) {streamHandlers.put(protocol, streamHandler);}return;} catch (IllegalAccessException ignored) {} catch (InstantiationException ignored) {} catch (ClassNotFoundException ignored) {}}}// Fall back to a built-in stream handler if the user didn't supply oneif (protocol.equals("file")) {streamHandler = new FileHandler();} else if (protocol.equals("ftp")) {streamHandler = new FtpHandler();} else if (protocol.equals("http")) {// 判断一下如果是HTTP协议,就会创建HtppHandler。看到这里明白了,原来使用的是okhttp.try {String name = "com.android.okhttp.HttpHandler";streamHandler = (URLStreamHandler) Class.forName(name).newInstance();} catch (Exception e) {throw new AssertionError(e);}} else if (protocol.equals("https")) {try {String name = "com.android.okhttp.HttpsHandler";streamHandler = (URLStreamHandler) Class.forName(name).newInstance();} catch (Exception e) {throw new AssertionError(e);}} else if (protocol.equals("jar")) {streamHandler = new JarHandler();}if (streamHandler != null) {streamHandlers.put(protocol, streamHandler);}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
这里我们就以HTTP协议为例说一下所以找到okhttp HttpHandler.openConnection()方法:
/** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements. See the NOTICE file distributed with* this work for additional information regarding copyright ownership.* The ASF licenses this file to You under the Apache License, Version 2.0* (the "License"); you may not use this file except in compliance with* the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.squareup.okhttp;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class HttpHandler extends URLStreamHandler {@Override protected URLConnection openConnection(URL url) throws IOException {// 调用了OKHttpClient()的方法return new OkHttpClient().open(url);}@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {if (url == null || proxy == null) {throw new IllegalArgumentException("url == null || proxy == null");}return new OkHttpClient().setProxy(proxy).open(url);}@Override protected int getDefaultPort() {return 80;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
接下来就悲剧了,因为我找不到OkHttpClient()类中有open方法。
仔细查看了文档后发现在OKHttp1.6.0的时候该方法就已经已经过时了。
@Deprecated
public HttpURLConnection open(URL url)
Deprecated. moved to OkUrlFactory.open.
- 1
- 2
- 3
那我们怎么往下分析呢?很显然Android sdk中使用的OkHttp不是最新版。所以我们可以使用1.5.0版本的OKHttp接着分析。
在项目build.gradle中进行配置 compile ‘com.squareup.okhttp:okhttp:1.5.0’ 然后开始愉快的查看源码。
package com.squareup.okhttp;import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.HttpAuthenticator;
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
import com.squareup.okhttp.internal.http.ResponseCacheAdapter;
import com.squareup.okhttp.internal.okio.ByteString;
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ResponseCache;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;/** Configures and creates HTTP connections. */
public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable {private final RouteDatabase routeDatabase;private Proxy proxy;private List<Protocol> protocols;private ProxySelector proxySelector;private CookieHandler cookieHandler;private OkResponseCache responseCache;private SSLSocketFactory sslSocketFactory;private HostnameVerifier hostnameVerifier;private OkAuthenticator authenticator;private ConnectionPool connectionPool;private boolean followProtocolRedirects = true;private int connectTimeout;private int readTimeout;public OkHttpClient() {routeDatabase = new RouteDatabase();}/*** Sets the default connect timeout for new connections. A value of 0 means no timeout.** @see URLConnection#setConnectTimeout(int)*/public void setConnectTimeout(long timeout, TimeUnit unit) {if (timeout < 0) {throw new IllegalArgumentException("timeout < 0");}if (unit == null) {throw new IllegalArgumentException("unit == null");}long millis = unit.toMillis(timeout);if (millis > Integer.MAX_VALUE) {throw new IllegalArgumentException("Timeout too large.");}connectTimeout = (int) millis;}/** Default connect timeout (in milliseconds). */public int getConnectTimeout() {return connectTimeout;}/*** Sets the default read timeout for new connections. A value of 0 means no timeout.** @see URLConnection#setReadTimeout(int)*/public void setReadTimeout(long timeout, TimeUnit unit) {if (timeout < 0) {throw new IllegalArgumentException("timeout < 0");}if (unit == null) {throw new IllegalArgumentException("unit == null");}long millis = unit.toMillis(timeout);if (millis > Integer.MAX_VALUE) {throw new IllegalArgumentException("Timeout too large.");}readTimeout = (int) millis;}/** Default read timeout (in milliseconds). */public int getReadTimeout() {return readTimeout;}/*** Sets the HTTP proxy that will be used by connections created by this* client. This takes precedence over {@link #setProxySelector}, which is* only honored when this proxy is null (which it is by default). To disable* proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.*/public OkHttpClient setProxy(Proxy proxy) {this.proxy = proxy;return this;}public Proxy getProxy() {return proxy;}/*** Sets the proxy selection policy to be used if no {@link #setProxy proxy}* is specified explicitly. The proxy selector may return multiple proxies;* in that case they will be tried in sequence until a successful connection* is established.** <p>If unset, the {@link ProxySelector#getDefault() system-wide default}* proxy selector will be used.*/public OkHttpClient setProxySelector(ProxySelector proxySelector) {this.proxySelector = proxySelector;return this;}public ProxySelector getProxySelector() {return proxySelector;}/*** Sets the cookie handler to be used to read outgoing cookies and write* incoming cookies.** <p>If unset, the {@link CookieHandler#getDefault() system-wide default}* cookie handler will be used.*/public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {this.cookieHandler = cookieHandler;return this;}public CookieHandler getCookieHandler() {return cookieHandler;}/*** Sets the response cache to be used to read and write cached responses.*/public OkHttpClient setResponseCache(ResponseCache responseCache) {return setOkResponseCache(toOkResponseCache(responseCache));}public ResponseCache getResponseCache() {return responseCache instanceof ResponseCacheAdapter? ((ResponseCacheAdapter) responseCache).getDelegate(): null;}public OkHttpClient setOkResponseCache(OkResponseCache responseCache) {this.responseCache = responseCache;return this;}public OkResponseCache getOkResponseCache() {return responseCache;}/*** Sets the socket factory used to secure HTTPS connections.** <p>If unset, a lazily created SSL socket factory will be used.*/public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {this.sslSocketFactory = sslSocketFactory;return this;}public SSLSocketFactory getSslSocketFactory() {return sslSocketFactory;}/*** Sets the verifier used to confirm that response certificates apply to* requested hostnames for HTTPS connections.** <p>If unset, the* {@link javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()* system-wide default} hostname verifier will be used.*/public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {this.hostnameVerifier = hostnameVerifier;return this;}public HostnameVerifier getHostnameVerifier() {return hostnameVerifier;}/*** Sets the authenticator used to respond to challenges from the remote web* server or proxy server.** <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}* authenticator will be used.*/public OkHttpClient setAuthenticator(OkAuthenticator authenticator) {this.authenticator = authenticator;return this;}public OkAuthenticator getAuthenticator() {return authenticator;}/*** Sets the connection pool used to recycle HTTP and HTTPS connections.** <p>If unset, the {@link ConnectionPool#getDefault() system-wide* default} connection pool will be used.*/public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {this.connectionPool = connectionPool;return this;}public ConnectionPool getConnectionPool() {return connectionPool;}/*** Configure this client to follow redirects from HTTPS to HTTP and from HTTP* to HTTPS.** <p>If unset, protocol redirects will be followed. This is different than* the built-in {@code HttpURLConnection}'s default.*/public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {this.followProtocolRedirects = followProtocolRedirects;return this;}public boolean getFollowProtocolRedirects() {return followProtocolRedirects;}public RouteDatabase getRoutesDatabase() {return routeDatabase;}/*** @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol protocols}* that can be selected. Please switch to {@link #setProtocols(java.util.List)}.*/@Deprecatedpublic OkHttpClient setTransports(List<String> transports) {List<Protocol> protocols = new ArrayList<Protocol>(transports.size());for (int i = 0, size = transports.size(); i < size; i++) {try {Protocol protocol = Util.getProtocol(ByteString.encodeUtf8(transports.get(i)));protocols.add(protocol);} catch (IOException e) {throw new IllegalArgumentException(e);}}return setProtocols(protocols);}/*** Configure the protocols used by this client to communicate with remote* servers. By default this client will prefer the most efficient transport* available, falling back to more ubiquitous protocols. Applications should* only call this method to avoid specific compatibility problems, such as web* servers that behave incorrectly when SPDY is enabled.** <p>The following protocols are currently supported:* <ul>* <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>* <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">spdy/3.1</a>* <li><a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-09">HTTP-draft-09/2.0</a>* </ul>** <p><strong>This is an evolving set.</strong> Future releases may drop* support for transitional protocols (like spdy/3.1), in favor of their* successors (spdy/4 or http/2.0). The http/1.1 transport will never be* dropped.** <p>If multiple protocols are specified, <a* href="https://technotes.googlecode.com/git/nextprotoneg.html">NPN</a> will* be used to negotiate a transport. Future releases may use another mechanism* (such as <a href="http://tools.ietf.org/html/draft-friedl-tls-applayerprotoneg-02">ALPN</a>)* to negotiate a transport.** @param protocols the protocols to use, in order of preference. The list* must contain "http/1.1". It must not contain null.*/public OkHttpClient setProtocols(List<Protocol> protocols) {protocols = Util.immutableList(protocols);if (!protocols.contains(Protocol.HTTP_11)) {throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);}if (protocols.contains(null)) {throw new IllegalArgumentException("protocols must not contain null");}this.protocols = Util.immutableList(protocols);return this;}/*** @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol* protocols} that can be selected. Please switch to {@link* #getProtocols()}.*/@Deprecatedpublic List<String> getTransports() {List<String> transports = new ArrayList<String>(protocols.size());for (int i = 0, size = protocols.size(); i < size; i++) {transports.add(protocols.get(i).name.utf8());}return transports;}public List<Protocol> getProtocols() {return protocols;}public HttpURLConnection open(URL url) {return open(url, proxy);}HttpURLConnection open(URL url, Proxy proxy) {String protocol = url.getProtocol();// 将该对象clone后设置一些其他的属性返回,里面会设置一个默认的连接池。OkHttpClient copy = copyWithDefaults();copy.proxy = proxy;// 返回了HttpURLConnectionImpl,并且把clone后的OKHttpClient对象传递进去。if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);throw new IllegalArgumentException("Unexpected protocol: " + protocol);}/*** Returns a shallow copy of this OkHttpClient that uses the system-wide* default for each field that hasn't been explicitly configured.*/OkHttpClient copyWithDefaults() {OkHttpClient result = clone();if (result.proxySelector == null) {result.proxySelector = ProxySelector.getDefault();}if (result.cookieHandler == null) {result.cookieHandler = CookieHandler.getDefault();}if (result.responseCache == null) {result.responseCache = toOkResponseCache(ResponseCache.getDefault());}if (result.sslSocketFactory == null) {result.sslSocketFactory = getDefaultSSLSocketFactory();}if (result.hostnameVerifier == null) {result.hostnameVerifier = OkHostnameVerifier.INSTANCE;}if (result.authenticator == null) {result.authenticator = HttpAuthenticator.SYSTEM_DEFAULT;}if (result.connectionPool == null) {// 会给OkHttpClient设置一个默认的连接池result.connectionPool = ConnectionPool.getDefault();}if (result.protocols == null) {result.protocols = Util.HTTP2_SPDY3_AND_HTTP;}return result;}/*** Java and Android programs default to using a single global SSL context,* accessible to HTTP clients as {@link SSLSocketFactory#getDefault()}. If we* used the shared SSL context, when OkHttp enables NPN for its SPDY-related* stuff, it would also enable NPN for other usages, which might crash them* because NPN is enabled when it isn't expected to be.* <p>* This code avoids that by defaulting to an OkHttp created SSL context. The* significant drawback of this approach is that apps that customize the* global SSL context will lose these customizations.*/private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {if (sslSocketFactory == null) {try {SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, null, null);sslSocketFactory = sslContext.getSocketFactory();} catch (GeneralSecurityException e) {throw new AssertionError(); // The system has no TLS. Just give up.}}return sslSocketFactory;}/** Returns a shallow copy of this OkHttpClient. */@Override public OkHttpClient clone() {try {return (OkHttpClient) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}private OkResponseCache toOkResponseCache(ResponseCache responseCache) {return responseCache == null || responseCache instanceof OkResponseCache? (OkResponseCache) responseCache: new ResponseCacheAdapter(responseCache);}/*** Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.** <p>This code configures OkHttp to handle all HTTP and HTTPS connections* created with {@link URL#openConnection()}: <pre> {@code** OkHttpClient okHttpClient = new OkHttpClient();* URL.setURLStreamHandlerFactory(okHttpClient);* }</pre>*/public URLStreamHandler createURLStreamHandler(final String protocol) {if (!protocol.equals("http") && !protocol.equals("https")) return null;return new URLStreamHandler() {@Override protected URLConnection openConnection(URL url) {return open(url);}@Override protected URLConnection openConnection(URL url, Proxy proxy) {return open(url, proxy);}@Override protected int getDefaultPort() {if (protocol.equals("http")) return 80;if (protocol.equals("https")) return 443;throw new AssertionError();}};}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
接着看一下HttpURLConnectionImpl类,它是HttpURLConnection的子类:
public class HttpURLConnectionImpl extends HttpURLConnection {.....
}
- 1
- 2
- 3
到这里new URL(url).openConnection()方法已经分析完了,其实就是返回了一个HtppURLConnectionImpl对象。
我们在使用HttpURLConnection都是这样使用:
String url = "http://www.baidu.com"
URL url = new URL(url);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
// 设置一些请求头等参数
...
connection.connect();
// 然后调用一些其他的获取结果或者状态的方法。
...
connection.getResponseCode();
connection.getOuoputStream();
connection.getInputStream();
....
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
上面分析了new URL().openConnection()那我们这里就接着分析第二步了,就是调用connect()方法的处理:
这里看一下HttpURLConnectionImpl.connect()方法:
@Override public final void connect() throws IOException {
initHttpEngine();
boolean success;
do {success = execute(false);
} while (!success);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
接着看一下initHttpEngine()方法的实现:
private void initHttpEngine() throws IOException {if (httpEngineFailure != null) {throw httpEngineFailure;} else if (httpEngine != null) {return;}connected = true;try {if (doOutput) {if (method.equals("GET")) {// they are requesting a stream to write to. This implies a POST methodmethod = "POST";} else if (!HttpMethod.hasRequestBody(method)) {// If the request method is neither POST nor PUT nor PATCH, then you're not writingthrow new ProtocolException(method + " does not support writing");}}// 将newHttpEngine方法的返回值赋值给HttpEngine的成员变量。httpEngine = newHttpEngine(method, null, null);} catch (IOException e) {httpEngineFailure = e;throw e;}}private HttpEngine newHttpEngine(String method, Connection connection,RetryableSink requestBody) {Request.Builder builder = new Request.Builder().url(getURL()).method(method, null /* No body; that's passed separately. */);Headers headers = requestHeaders.build();for (int i = 0; i < headers.size(); i++) {builder.addHeader(headers.name(i), headers.value(i));}boolean bufferRequestBody;if (fixedContentLength != -1) {bufferRequestBody = false;builder.header("Content-Length", Long.toString(fixedContentLength));} else if (chunkLength > 0) {bufferRequestBody = false;builder.header("Transfer-Encoding", "chunked");} else {bufferRequestBody = true;}Request request = builder.build();// If we're currently not using caches, make sure the engine's client doesn't have one.OkHttpClient engineClient = client;if (engineClient.getOkResponseCache() != null && !getUseCaches()) {engineClient = client.clone().setOkResponseCache(null);}// 将之前通过构造函数传递进来的OkHttpClient对象clone一份后再传递给HttpEnginereturn new HttpEngine(engineClient, request, bufferRequestBody, connection, null, requestBody);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
到这里我们知道他会创建一个HttpEngine类,我们先不管它,接着看一下execute()方法的内部实现:
/**
* Sends a request and optionally reads a response. Returns true if the
* request was successfully executed, and false if the request can be
* retried. Throws an exception if the request failed permanently.
*/
private boolean execute(boolean readResponse) throws IOException {
try {// 调用了HttpEngine的sendRequest方法。httpEngine.sendRequest();route = httpEngine.getRoute();handshake = httpEngine.getConnection() != null? httpEngine.getConnection().getHandshake(): null;// 读取结果,我们先不分析这里,等把sendRequest部分全部分析完成后再回来分析readResponse()部分。if (readResponse) {httpEngine.readResponse();}return true;
} catch (IOException e) {HttpEngine retryEngine = httpEngine.recover(e);if (retryEngine != null) {httpEngine = retryEngine;return false;}// Give up; recovery is not possible.httpEngineFailure = e;throw e;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
到这里,可以大胆的猜测一下了HttpEngine
应该就是实际在Socket
链接上进行数据收发的类。 当然这只是猜测,接着看一下它的实现:
/*** Handles a single HTTP request/response pair. Each HTTP engine follows this* lifecycle:* <ol>* <li>It is created.* <li>The HTTP request message is sent with sendRequest(). Once the request* is sent it is an error to modify the request headers. After* sendRequest() has been called the request body can be written to if* it exists.* <li>The HTTP response message is read with readResponse(). After the* response has been read the response headers and body can be read.* All responses have a response body input stream, though in some* instances this stream is empty.* </ol>** <p>The request and response may be served by the HTTP response cache, by the* network, or by both in the event of a conditional GET.*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
文档验证了我们的想法。因为它内部实现代码比较多,所以就不全部贴了,按照重要的一步步分析,既然上面调用了sendRequest()
方法,这里就从他入手:
/**
* Figures out what the response source will be, and opens a socket to that
* source if necessary. Prepares the request headers and gets ready to start
* writing the request body if it exists.
*/
public final void sendRequest() throws IOException {if (responseSource != null) return; // Already sent.if (transport != null) throw new IllegalStateException();// 设置一些请求头,正式这个方法内部默认设置了`Keep-Alive`的值,也就是在Android Level 9之前因为Bug,我们需要关闭它的具体处理位置。 prepareRawRequestHeaders();// 处理cacheOkResponseCache responseCache = client.getOkResponseCache();Response cacheResponse = responseCache != null? responseCache.get(request): null;long now = System.currentTimeMillis();CacheStrategy cacheStrategy = new CacheStrategy.Factory(now, request, cacheResponse).get();responseSource = cacheStrategy.source;request = cacheStrategy.request;if (responseCache != null) {// 记录下当前的请求是来自网络请求还是来时缓存中的数据。responseCache.trackResponse(responseSource);}if (responseSource != ResponseSource.NETWORK) {validatingResponse = cacheStrategy.response;}if (cacheResponse != null && !responseSource.usesCache()) {closeQuietly(cacheResponse.body()); // We don't need this cached response. Close it.}if (responseSource.requiresConnection()) {// Open a connection unless we inherited one from a redirect.if (connection == null) {// 调用connect方法,内部会重新创建一个connection,连接到服务器、重定向或者通过代理。connect();}// 通过Connection创建一个HttpTransport类。这个和后面的connection类一起看, Transport接口提供了一个用户写Request头和数据的输出流。transport = (Transport) connection.newTransport(this);// Create a request body if we don't have one already. We'll already have// one if we're retrying a failed POST.if (hasRequestBody() && requestBodyOut == null) {// 通过transport创建一个请求体的输出流,requestBodyOut是Sink接口的实现类,其实就是将请求头和请求体发送给服务器。这部分跟下去内容比较多,就不往下跟了。// 到这里就已经完成了与服务器的连接功能,并且把请求内容发送给服务器。请求部分就执行完了,可以回去了,还知道开头是哪吗?哈哈。接下来的就是从服务器接口读取返回数据了。 requestBodyOut = transport.createRequestBody(request);}} else {// We're using a cached response. Recycle a connection we may have inherited from a redirect.if (connection != null) {// 回收connection,这里就看到了连接池,这个client就是构造函数中传递进来的OKHttpClientclient.getConnectionPool().recycle(connection);connection = null;}// No need for the network! Promote the cached response immediately.this.response = validatingResponse;if (validatingResponse.body() != null) {initContentStream(validatingResponse.body().source());}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
接着看一下connect()
方法:
/** Connect to the origin server either directly or via a proxy. */
private void connect() throws IOException {if (connection != null) throw new IllegalStateException();if (routeSelector == null) {String uriHost = request.url().getHost();if (uriHost == null || uriHost.length() == 0) {throw new UnknownHostException(request.url().toString());}SSLSocketFactory sslSocketFactory = null;HostnameVerifier hostnameVerifier = null;if (request.isHttps()) {sslSocketFactory = client.getSslSocketFactory();hostnameVerifier = client.getHostnameVerifier();}Address address = new Address(uriHost, getEffectivePort(request.url()), sslSocketFactory,hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getProtocols());// RoteSeclector类介绍. Selects routes to connect to an origin server. Each connection requires a// choice of proxy server, IP address, and TLS mode. Connections may also be// recycled.注意他把OkHttpClient中的connection pool传递进来了。routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(),client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());}// roteSeclecrot.next()方法的注释Returns the next route address to attempt.这一步非常重要。connection = routeSelector.next(request.method());if (!connection.isConnected()) {// connection 进行连接了啊。他里面会用Socket开始连了。。后面我们再细看。简单的说Connection管理了Socket,后面我们要重点看这个类。connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());if (connection.isSpdy()) client.getConnectionPool().share(connection);client.getRoutesDatabase().connected(connection.getRoute());} else if (!connection.isSpdy()) {connection.updateReadTimeout(client.getReadTimeout());}route = connection.getRoute();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
接下来我们要先看一下routeSelector.next()方法如何返回connection对象,然后在看Connection.connect()方法:
/**
* Returns the next route address to attempt.
*
* @throws NoSuchElementException if there are no more routes to attempt.
*/
public Connection next(String method) throws IOException {// 使用连接池获取Connection的地方。pool就是OkHttpClient中的连接池。// Always prefer pooled connections over new connections.for (Connection pooled; (pooled = pool.get(address)) != null; ) {// 匹配get方法,或者判断是否可读,http1.x是通过判断socket是否关闭来判断是否可读的。if (method.equals("GET") || pooled.isReadable()) return pooled;// 不满足重用,就关闭。pooled.close();}// Compute the next route to attempt.if (!hasNextTlsMode()) {if (!hasNextInetSocketAddress()) {if (!hasNextProxy()) {if (!hasNextPostponed()) {throw new NoSuchElementException();}return new Connection(pool, nextPostponed());}lastProxy = nextProxy();resetNextInetSocketAddress(lastProxy);}lastInetSocketAddress = nextInetSocketAddress();resetNextTlsMode();}boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);if (routeDatabase.shouldPostpone(route)) {postponedRoutes.add(route);// We will only recurse in order to skip previously failed routes. They will be// tried last.return next(method);}// 没有的话也会去创建,并把OkHttpClient中的连接池传递进去。return new Connection(pool, route);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
再看一下Connection
类的实现以及其connect()
方法:
public final class Connection implements Closeable {private final ConnectionPool pool;private final Route route;private Socket socket;private InputStream in;private OutputStream out;private BufferedSource source;private BufferedSink sink;private boolean connected = false;private HttpConnection httpConnection;private SpdyConnection spdyConnection;private int httpMinorVersion = 1; // Assume HTTP/1.1private long idleStartTimeNs;private Handshake handshake;private int recycleCount;// 传递进来的连接池。public Connection(ConnectionPool pool, Route route) {this.pool = pool;this.route = route;}public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)throws IOException {if (connected) throw new IllegalStateException("already connected");socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();// 连socket了,内部调用了socket.connect()方法。Connects this socket to the given remote host address and port specified// by the SocketAddress {@code remoteAddr} with the specified timeout. The// connecting method will block until the connection is established or an// error occurred.Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);socket.setSoTimeout(readTimeout);in = socket.getInputStream();out = socket.getOutputStream();if (route.address.sslSocketFactory != null) {// 完成TLS握手和验证upgradeToTls(tunnelRequest);} else {initSourceAndSink();// 创建HttpConnection.A socket connection that can be used to send HTTP/1.1 messages. // 这个HttpConnection有什么用呢?就是下面的newTransport方法中会用。而且还要把连接池传递进去?httpConnection = new HttpConnection(pool, this, source, sink);}// 这样就已经连接上了connected = true;}// 该方法决定了使用的协议是SPDY还是HTTP/** Returns the transport appropriate for this connection. */public Object newTransport(HttpEngine httpEngine) throws IOException {return (spdyConnection != null)? new SpdyTransport(httpEngine, spdyConnection): new HttpTransport(httpEngine, httpConnection);}}
````到这里就已经把发送请求到服务器的部分全部分析完了,就想上面所说的我们应该回去了,回去分析发送完请求后的部分。
我们这个分析是在`HttpURLConnectionImpl.execute()`方法中的`HttpEngine.sendRequest()`方法开始一直分析下来的,
所以我们还是要回到`HttpURLConnectionImpl.execute()`方法中. <div class="se-preview-section-delimiter"></div>```java
private boolean execute(boolean readResponse) throws IOException {
try {// 上面已经把sendRequest部分全部分析完了,该方法会与服务器通过Socket建立连接并把请求部分发送给服务器。httpEngine.sendRequest();route = httpEngine.getRoute();handshake = httpEngine.getConnection() != null? httpEngine.getConnection().getHandshake(): null;if (readResponse) {// 发送完请求之后该干什么呢? 当然是读取返回数据了。。。 没错就是它。但是不要忘了在connect()方法传递过来的时候这个值是false。// 所以这一步在这里是不执行的,但是我们也分析下,方便以后理解。那这个值什么时候是true呢?就是在getResponse()方法中。httpEngine.readResponse();}return true;
} catch (IOException e) {HttpEngine retryEngine = httpEngine.recover(e);if (retryEngine != null) {httpEngine = retryEngine;return false;}// Give up; recovery is not possible.httpEngineFailure = e;throw e;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
接着看HttpEngine.readResponse()
方法吧。注释说的非常明白。
/**
* Flushes the remaining request header and body, parses the HTTP response
* headers and starts reading the HTTP response body if it exists.
*/
public final void readResponse() throws IOException {
if (response != null) return;
if (responseSource == null) throw new IllegalStateException("call sendRequest() first!");
if (!responseSource.requiresConnection()) return;// Flush the request body if there's data outstanding.
if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {bufferedRequestBody.flush();
}if (sentRequestMillis == -1) {if (OkHeaders.contentLength(request) == -1 && requestBodyOut instanceof RetryableSink) {// We might not learn the Content-Length until the request body has been buffered.long contentLength = ((RetryableSink) requestBodyOut).contentLength();request = request.newBuilder().header("Content-Length", Long.toString(contentLength)).build();}transport.writeRequestHeaders(request);
}if (requestBodyOut != null) {if (bufferedRequestBody != null) {// This also closes the wrapped requestBodyOut.bufferedRequestBody.close();} else {requestBodyOut.close();}if (requestBodyOut instanceof RetryableSink) {transport.writeRequestBody((RetryableSink) requestBodyOut);}
}transport.flushRequest();response = transport.readResponseHeaders().request(request).handshake(connection.getHandshake()).header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis)).header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis())).setResponseSource(responseSource).build();
connection.setHttpMinorVersion(response.httpMinorVersion());
receiveHeaders(response.headers());if (responseSource == ResponseSource.CONDITIONAL_CACHE) {// 检查缓存是否可用,如果可用就用当前缓存的response。并且释放该连接。if (validatingResponse.validate(response)) {transport.emptyTransferStream();releaseConnection();response = combine(validatingResponse, response);// Update the cache after combining headers but before stripping the// Content-Encoding header (as performed by initContentStream()).OkResponseCache responseCache = client.getOkResponseCache();responseCache.trackConditionalCacheHit();responseCache.update(validatingResponse, cacheableResponse());if (validatingResponse.body() != null) {initContentStream(validatingResponse.body().source());}return;} else {closeQuietly(validatingResponse.body());}
}if (!hasResponseBody()) {// Don't call initContentStream() when the response doesn't have any content.responseTransferSource = transport.getTransferStream(cacheRequest);responseBody = responseTransferSource;return;
}// 设置cacheRequest的值
maybeCache();
// 设置返回数据了,transport这里也就是HttpTransport。他的getTransferStream返回的是一个Source接口的实现类。也就是返回的数据。
initContentStream(transport.getTransferStream(cacheRequest));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
先看一下maybeCache()方法:
private void maybeCache() throws IOException {
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;// Should we cache this response for this request?
if (!CacheStrategy.isCacheable(response, request)) {responseCache.maybeRemove(request);return;
}// Offer this request to the cache.
cacheRequest = responseCache.put(cacheableResponse());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
再看一下initContentStream()
方法:
/**
* Initialize the response content stream from the response transfer source.
* These two sources are the same unless we're doing transparent gzip, in
* which case the content source is decompressed.
*
* <p>Whenever we do transparent gzip we also strip the corresponding headers.
* We strip the Content-Encoding header to prevent the application from
* attempting to double decompress. We strip the Content-Length header because
* it is the length of the compressed content, but the application is only
* interested in the length of the uncompressed content.
*
* <p>This method should only be used for non-empty response bodies. Response
* codes like "304 Not Modified" can include "Content-Encoding: gzip" without
* a response body and we will crash if we attempt to decompress the zero-byte
* source.
*/
private void initContentStream(Source transferSource) throws IOException {
responseTransferSource = transferSource;
if (transparentGzip && "gzip".equalsIgnoreCase(response.header("Content-Encoding"))) { // 没有结果时response为null,有结果了就会给他赋值。response = response.newBuilder().removeHeader("Content-Encoding").removeHeader("Content-Length").build();responseBody = new GzipSource(transferSource);
} else {responseBody = transferSource;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
到这里又执行完了,responseBody已经被赋值了,他是一个Source接口的实现类。也就是说到这里,这次网络请求就完成了,也收到了服务器返回的数据。
也就是说到这里我们已经分析了:
HttpURLConnection connection = (HttpURLConnection)new URL(url).openConnection();
connection.connect();
- 1
- 2
接下来就是分析connection.getResponseCode()以及connection.getOutputStream()这两个方法了。
先看一下getResponseCode()方法:
@Override public final int getResponseCode() throws IOException {
// 看到了吗?这里就是刚才我们说的execute()方法的参数什么时候会为true的地方。
return getResponse().getResponse().code();
}
- 1
- 2
- 3
- 4
那我们接着看一下getResponse()
方法,其实就是直接读取响应头的响应值:
/**
* Aggressively tries to get the final HTTP response, potentially making
* many HTTP requests in the process in order to cope with redirects and
* authentication.
*/
private HttpEngine getResponse() throws IOException {
initHttpEngine();// 如果已经有返回数据了就直接返回
if (httpEngine.hasResponse()) {return httpEngine;
}while (true) {// 参数为true了。if (!execute(true)) {continue;}Retry retry = processResponseHeaders();if (retry == Retry.NONE) {httpEngine.releaseConnection();return httpEngine;}// The first request was insufficient. Prepare for another...String retryMethod = method;Sink requestBody = httpEngine.getRequestBody();// Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM// redirect should keep the same method, Chrome, Firefox and the// RI all issue GETs when following any redirect.int responseCode = httpEngine.getResponse().code();if (responseCode == HTTP_MULT_CHOICE|| responseCode == HTTP_MOVED_PERM|| responseCode == HTTP_MOVED_TEMP|| responseCode == HTTP_SEE_OTHER) {retryMethod = "GET";requestHeaders.removeAll("Content-Length");requestBody = null;}if (requestBody != null && !(requestBody instanceof RetryableSink)) {throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);}if (retry == Retry.DIFFERENT_CONNECTION) {httpEngine.releaseConnection();}Connection connection = httpEngine.close();httpEngine = newHttpEngine(retryMethod, connection, (RetryableSink) requestBody);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
接着看一下getOutputStream()
的源码:
@Override public final OutputStream getOutputStream() throws IOException {
// 看到了吗?他内部会先去调用connect()方法
connect();// 这里可能有人会有说,getOutputStream和request body有什么关系,应该是response body才对啊。
// 不要弄混了啊,getOutputStream是要把post请求的数据输入给请求。
BufferedSink sink = httpEngine.getBufferedRequestBody();
if (sink == null) {throw new ProtocolException("method does not support a request body: " + method);
} else if (httpEngine.hasResponse()) {throw new ProtocolException("cannot write request body after response has been read");
}return sink.outputStream();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
顺便再看一下getInputStream()
方法:
@Override public final InputStream getInputStream() throws IOException {
if (!doInput) {throw new ProtocolException("This protocol does not support input");
}
// 它也会直接调用getResponse()方法,这个比较好理解。
HttpEngine response = getResponse();// if the requested file does not exist, throw an exception formerly the
// Error page from the server was returned if the requested file was
// text/html this has changed to return FileNotFoundException for all
// file types
if (getResponseCode() >= HTTP_BAD_REQUEST) {throw new FileNotFoundException(url.toString());
}InputStream result = response.getResponseBodyBytes();
if (result == null) {throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
}
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
再顺便看一下HttpURLConnection.disconnect()
方法,因为这个方法可能很多人不清楚该不该调用他,不过注释说的很明白了:
/*** Releases this connection so that its resources may be either reused or* closed.** <p>Unlike other Java implementations, this will not necessarily close* socket connections that can be reused. You can disable all connection* reuse by setting the {@code http.keepAlive} system property to {@code* false} before issuing any HTTP requests.*/
@Override public final void disconnect() {
// Calling disconnect() before a connection exists should have no effect.
if (httpEngine != null) {// 调用HttpEngine.close()方法httpEngine.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
看一下HttpEngine.close()方法:
/**
* Release any resources held by this engine. If a connection is still held by
* this engine, it is returned.
*/
public final Connection close() {
if (bufferedRequestBody != null) {// This also closes the wrapped requestBodyOut.closeQuietly(bufferedRequestBody);
} else if (requestBodyOut != null) {closeQuietly(requestBodyOut);
}// If this engine never achieved a response body, its connection cannot be reused.
if (responseBody == null) {closeQuietly(connection);connection = null;return null;
}// Close the response body. This will recycle the connection if it is eligible.
closeQuietly(responseBody);// Clear the buffer held by the response body input stream adapter.
closeQuietly(responseBodyBytes);// HttpTransport.canReuseConnection()用于判断该Connection是否可复用
// Close the connection if it cannot be reused.
if (transport != null && !transport.canReuseConnection()) {closeQuietly(connection);connection = null;return null;
}Connection result = connection;
connection = null;
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
继续看一下closeQuietly(connection)方法:
/**
* Closes {@code closeable}, ignoring any checked exceptions. Does nothing
* if {@code closeable} is null.
*/
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {try {// 这里也就是Connection的close()方法closeable.close();} catch (RuntimeException rethrown) {throw rethrown;} catch (Exception ignored) {}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
再接着看一下Connection.close()方法:
@Override public void close() throws IOException {
// 直接调用了socket.close()方法。这些socket也关了。
socket.close();
}
- 1
- 2
- 3
- 4
再看Socket.close()方法:
package com.squareup.okhttp;/*** Closes the socket. It is not possible to reconnect or rebind to this* socket thereafter which means a new socket instance has to be created.** @throws IOException* if an error occurs while closing the socket.*/
public synchronized void close() throws IOException {isClosed = true;isConnected = false;// RI compatibility: the RI returns the any address (but the original local port) after// close.localAddress = Inet4Address.ANY;impl.close();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
到这里就都看完了,最后我们再看一下上面用到的连接池,也就是ConnectionPool类: 因为在上面的分析中,我们发现此类贯穿了很多类。
它为OkHttpClient中的对象,后贯穿到HttpEngine、Connection、HttpConnection等。所以要分析下。
/*** Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP* requests that share the same {@link com.squareup.okhttp.Address} may share a* {@link com.squareup.okhttp.Connection}. This class implements the policy of* which connections to keep open for future use.** <p>The {@link #getDefault() system-wide default} uses system properties for* tuning parameters:* <ul>* <li>{@code http.keepAlive} true if HTTP and SPDY connections should be* pooled at all. Default is true.* <li>{@code http.maxConnections} maximum number of idle connections to* each to keep in the pool. Default is 5.* <li>{@code http.keepAliveDuration} Time in milliseconds to keep the* connection alive in the pool before closing it. Default is 5 minutes.* This property isn't used by {@code HttpURLConnection}.* </ul>** <p>The default instance <i>doesn't</i> adjust its configuration as system* properties are changed. This assumes that the applications that set these* parameters do so before making HTTP connections, and that this class is* initialized lazily.*/
public class ConnectionPool {private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min// 这个就是getDefault方法所返回的默认连接池。private static final ConnectionPool systemDefault;static {String keepAlive = System.getProperty("http.keepAlive");// 存活时间String keepAliveDuration = System.getProperty("http.keepAliveDuration");// 最大空闲连接数String maxIdleConnections = System.getProperty("http.maxConnections");long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration): DEFAULT_KEEP_ALIVE_DURATION_MS;if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {systemDefault = new ConnectionPool(0, keepAliveDurationMs);} else if (maxIdleConnections != null) {systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);} else {systemDefault = new ConnectionPool(5, keepAliveDurationMs);}}/** The maximum number of idle connections for each address. */private final int maxIdleConnections;private final long keepAliveDurationNs;private final LinkedList<Connection> connections = new LinkedList<Connection>();/** We use a single background thread to cleanup expired connections. */private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),Util.threadFactory("OkHttp ConnectionPool", true));private final Runnable connectionsCleanupRunnable = new Runnable() {@Override public void run() {List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);int idleConnectionCount = 0;synchronized (ConnectionPool.this) {for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious(); ) {Connection connection = i.previous();if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {i.remove();expiredConnections.add(connection);if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;} else if (connection.isIdle()) {idleConnectionCount++;}}for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {Connection connection = i.previous();if (connection.isIdle()) {expiredConnections.add(connection);i.remove();--idleConnectionCount;}}}for (Connection expiredConnection : expiredConnections) {Util.closeQuietly(expiredConnection);}}};public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {this.maxIdleConnections = maxIdleConnections;this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;}/*** Returns a snapshot of the connections in this pool, ordered from newest to* oldest. Waits for the cleanup callable to run if it is currently scheduled.*/List<Connection> getConnections() {waitForCleanupCallableToRun();synchronized (this) {return new ArrayList<Connection>(connections);}}/*** Blocks until the executor service has processed all currently enqueued* jobs.*/private void waitForCleanupCallableToRun() {try {executorService.submit(new Runnable() {@Override public void run() {}}).get();} catch (Exception e) {throw new AssertionError();}}public static ConnectionPool getDefault() {return systemDefault;}/** Returns total number of connections in the pool. */public synchronized int getConnectionCount() {return connections.size();}/** Returns total number of spdy connections in the pool. */public synchronized int getSpdyConnectionCount() {int total = 0;for (Connection connection : connections) {if (connection.isSpdy()) total++;}return total;}/** Returns total number of http connections in the pool. */public synchronized int getHttpConnectionCount() {int total = 0;for (Connection connection : connections) {if (!connection.isSpdy()) total++;}return total;}/** Returns a recycled connection to {@code address}, or null if no such connection exists. */public synchronized Connection get(Address address) {Connection foundConnection = null;for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious(); ) {Connection connection = i.previous();if (!connection.getRoute().getAddress().equals(address)|| !connection.isAlive()|| System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {continue;}i.remove();if (!connection.isSpdy()) {// 不是spdy连接try {// Platforml类对当前Android平台做了适配。Platform.get().tagSocket(connection.getSocket());} catch (SocketException e) {Util.closeQuietly(connection);// When unable to tag, skip recycling and closePlatform.get().logW("Unable to tagSocket(): " + e);continue;}}// 找到可复用的ConnectionfoundConnection = connection;break;}// 针对spdy连接,添加到连接池中if (foundConnection != null && foundConnection.isSpdy()) {connections.addFirst(foundConnection); // Add it back after iteration.}executorService.execute(connectionsCleanupRunnable);return foundConnection;}/*** Gives {@code connection} to the pool. The pool may store the connection,* or close it, as its policy describes.** <p>It is an error to use {@code connection} after calling this method.*/public void recycle(Connection connection) {if (connection.isSpdy()) {return;}if (!connection.isAlive()) {Util.closeQuietly(connection);return;}try {Platform.get().untagSocket(connection.getSocket());} catch (SocketException e) {// When unable to remove tagging, skip recycling and close.Platform.get().logW("Unable to untagSocket(): " + e);Util.closeQuietly(connection);return;}synchronized (this) {connections.addFirst(connection);connection.incrementRecycleCount();connection.resetIdleStartTime();}executorService.execute(connectionsCleanupRunnable);}/*** Shares the SPDY connection with the pool. Callers to this method may* continue to use {@code connection}.*/public void share(Connection connection) {if (!connection.isSpdy()) throw new IllegalArgumentException();executorService.execute(connectionsCleanupRunnable);if (connection.isAlive()) {synchronized (this) {connections.addFirst(connection);}}}/** Close and remove all connections in the pool. */public void evictAll() {List<Connection> connections;synchronized (this) {connections = new ArrayList<Connection>(this.connections);this.connections.clear();}for (int i = 0, size = connections.size(); i < size; i++) {Util.closeQuietly(connections.get(i));}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
/*** Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP* requests that share the same {@link com.squareup.okhttp.Address} may share a* {@link com.squareup.okhttp.Connection}. This class implements the policy of* which connections to keep open for future use.** <p>The {@link #getDefault() system-wide default} uses system properties for* tuning parameters:* <ul>* <li>{@code http.keepAlive} true if HTTP and SPDY connections should be* pooled at all. Default is true.* <li>{@code http.maxConnections} maximum number of idle connections to* each to keep in the pool. Default is 5.* <li>{@code http.keepAliveDuration} Time in milliseconds to keep the* connection alive in the pool before closing it. Default is 5 minutes.* This property isn't used by {@code HttpURLConnection}.* </ul>** <p>The default instance <i>doesn't</i> adjust its configuration as system* properties are changed. This assumes that the applications that set these* parameters do so before making HTTP connections, and that this class is* initialized lazily.*/
public class ConnectionPool {private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min// 这个就是getDefault方法所返回的默认连接池。private static final ConnectionPool systemDefault;static {String keepAlive = System.getProperty("http.keepAlive");// 存活时间String keepAliveDuration = System.getProperty("http.keepAliveDuration");// 最大空闲连接数String maxIdleConnections = System.getProperty("http.maxConnections");long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration): DEFAULT_KEEP_ALIVE_DURATION_MS;if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {systemDefault = new ConnectionPool(0, keepAliveDurationMs);} else if (maxIdleConnections != null) {systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);} else {systemDefault = new ConnectionPool(5, keepAliveDurationMs);}}/** The maximum number of idle connections for each address. */private final int maxIdleConnections;private final long keepAliveDurationNs;private final LinkedList<Connection> connections = new LinkedList<Connection>();/** We use a single background thread to cleanup expired connections. */private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),Util.threadFactory("OkHttp ConnectionPool", true));private final Runnable connectionsCleanupRunnable = new Runnable() {@Override public void run() {List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);int idleConnectionCount = 0;synchronized (ConnectionPool.this) {for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious(); ) {Connection connection = i.previous();if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {i.remove();expiredConnections.add(connection);if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;} else if (connection.isIdle()) {idleConnectionCount++;}}for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {Connection connection = i.previous();if (connection.isIdle()) {expiredConnections.add(connection);i.remove();--idleConnectionCount;}}}for (Connection expiredConnection : expiredConnections) {Util.closeQuietly(expiredConnection);}}};public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {this.maxIdleConnections = maxIdleConnections;this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;}/*** Returns a snapshot of the connections in this pool, ordered from newest to* oldest. Waits for the cleanup callable to run if it is currently scheduled.*/List<Connection> getConnections() {waitForCleanupCallableToRun();synchronized (this) {return new ArrayList<Connection>(connections);}}/*** Blocks until the executor service has processed all currently enqueued* jobs.*/private void waitForCleanupCallableToRun() {try {executorService.submit(new Runnable() {@Override public void run() {}}).get();} catch (Exception e) {throw new AssertionError();}}public static ConnectionPool getDefault() {return systemDefault;}/** Returns total number of connections in the pool. */public synchronized int getConnectionCount() {return connections.size();}/** Returns total number of spdy connections in the pool. */public synchronized int getSpdyConnectionCount() {int total = 0;for (Connection connection : connections) {if (connection.isSpdy()) total++;}return total;}/** Returns total number of http connections in the pool. */public synchronized int getHttpConnectionCount() {int total = 0;for (Connection connection : connections) {if (!connection.isSpdy()) total++;}return total;}/** Returns a recycled connection to {@code address}, or null if no such connection exists. */public synchronized Connection get(Address address) {Connection foundConnection = null;for (ListIterator<Connection> i = connections.listIterator(connections.size());i.hasPrevious(); ) {Connection connection = i.previous();if (!connection.getRoute().getAddress().equals(address)|| !connection.isAlive()|| System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {continue;}i.remove();if (!connection.isSpdy()) {// 不是spdy连接try {// Platforml类对当前Android平台做了适配。Platform.get().tagSocket(connection.getSocket());} catch (SocketException e) {Util.closeQuietly(connection);// When unable to tag, skip recycling and closePlatform.get().logW("Unable to tagSocket(): " + e);continue;}}// 找到可复用的ConnectionfoundConnection = connection;break;}// 针对spdy连接,添加到连接池中if (foundConnection != null && foundConnection.isSpdy()) {connections.addFirst(foundConnection); // Add it back after iteration.}executorService.execute(connectionsCleanupRunnable);return foundConnection;}/*** Gives {@code connection} to the pool. The pool may store the connection,* or close it, as its policy describes.** <p>It is an error to use {@code connection} after calling this method.*/public void recycle(Connection connection) {if (connection.isSpdy()) {return;}if (!connection.isAlive()) {Util.closeQuietly(connection);return;}try {Platform.get().untagSocket(connection.getSocket());} catch (SocketException e) {// When unable to remove tagging, skip recycling and close.Platform.get().logW("Unable to untagSocket(): " + e);Util.closeQuietly(connection);return;}synchronized (this) {connections.addFirst(connection);connection.incrementRecycleCount();connection.resetIdleStartTime();}executorService.execute(connectionsCleanupRunnable);}/*** Shares the SPDY connection with the pool. Callers to this method may* continue to use {@code connection}.*/public void share(Connection connection) {if (!connection.isSpdy()) throw new IllegalArgumentException();executorService.execute(connectionsCleanupRunnable);if (connection.isAlive()) {synchronized (this) {connections.addFirst(connection);}}}/** Close and remove all connections in the pool. */public void evictAll() {List<Connection> connections;synchronized (this) {connections = new ArrayList<Connection>(this.connections);this.connections.clear();}for (int i = 0, size = connections.size(); i < size; i++) {Util.closeQuietly(connections.get(i));}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
文章比较长。。看了一天,就一口气写完了,希望能看到这里。
Android 4.4以上使用HttpURLConnection底层使用OkHttp实现的源码分析相关推荐
- Java中ConcurrentHashMap底层实现原理(JDK1.8)源码分析2
https://blog.csdn.net/programmer_at/article/details/79715177 https://blog.csdn.net/qq_41737716/categ ...
- Java面试绕不开的问题: Java中HashMap底层实现原理(JDK1.8)源码分析
这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JD ...
- Android布局优化之ViewStub、include、merge使用与源码分析
在开发中UI布局是我们都会遇到的问题,随着UI越来越多,布局的重复性.复杂度也会随之增长.Android官方给了几个优化的方法,但是网络上的资料基本上都是对官方资料的翻译,这些资料都特别的简单,经常会 ...
- android 系统复位(Reset 恢复出厂设置)9.0源码分析
今天来从源码的角度 分析一下android 系统复位的流程 1. 从点击 Reset 看起.点击Reset后,会弹出Erase everything.故而从在Setting中搜索此字段开始. 因为我这 ...
- hashmap实现原理_Java中HashMap底层实现原理(JDK1.8)源码分析
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依 ...
- Wifi模块—源码分析Wifi启动(Android P)
一.前言 Android P在wifi这块改动挺大的,Wifi到AndoidO之后不再使用jni,所以AndroidP也一样不再使用jni来实现Java代码与本地的C/C++代码交互,而是使用HIDL ...
- 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- Android Touch事件分发(源码分析)
Android一文让你轻松搞定Touch事件分发 源码分析 下面,咱们一起通过源码,全面解析事件分发机制,即按顺序讲解: Activity事件分发机制 ViewGroup事件分发机制 View事件分发 ...
- 【Android 电量优化】JobScheduler 相关源码分析 ( ConnectivityController 底层源码分析 | 构造函数 | 追踪任务更新 | 注册接收者监听连接变化 )
文章目录 一.ConnectivityController 连接控制器引入 二.ConnectivityController 构造方法解析 ( 注册接收者 ) 三.mConnectivityRecei ...
最新文章
- java什么时会出现gc_面试题:java GC发生在会么时候,对什么东西,做了什么事情...
- 网站SEO优化中导航对用户体验的重要性
- 初学python还是swift-请问零基础学习python 和swift哪个更好入门呢?
- mybaits三:全局配置文件
- 国务院学位委员会关于授予具有研究生毕业同等学力人员硕士、博士学位的规定
- mysql压力测试并优化_MySQL压力测试索引优化效果演示全过程
- 新加坡建设绿色高层数据中心的构想有所争议
- eclipse 导入maven项目_手把手的Spring Boot Web 项目教程,Hello Spring Boot
- .Net开发的两个小技巧
- Linux 下的推迟执行
- 幼儿园计算机课程名称,幼儿园课程建设30:计算机与幼儿园课程的整合(3)
- Sql Server中的几个系统表(二)
- 李纪为:初入NLP领域的一些小建议
- 小游戏—九宫格(拼图游戏)
- 推荐一个文字生成图片的网站
- datax与datax-web安装部署
- Openpcd安装过程记录
- IDE、SATA、SCSI、SAS、FC、SSD硬盘类型介绍
- 计算机分级时无法度量视频播放,无法度量视频播放性能怎么办-无法度量视频播放性能的解决方法 - 河东软件园...
- php 法定节假日接口,通过百度接口获取每一个月的工作和法定假日
热门文章
- Blazor中内置的Open Iconic图标集
- 计算机缺少codex64.dll,win10x64缺少dll依赖库致使程序无法运行
- 当前流行搜索引擎爬虫IP列表
- 纯净IP地址--跨境人的爱恨情仇
- 【Ubuntu】 提示无法定位软件包 xxx
- 牛客小白月赛27 B.乐团派对
- 【python--爬虫】爬取淘女郎照片
- java.io.IOException: Incomplete output stream
- ECS.Day3笔记
- matlab 非线性仿真,科学网—含高阶色散和高阶非线性项的非线性耦合仿真Matlab源程序 - 王又法的博文...