之前寫過(guò)一篇關(guān)于網(wǎng)絡(luò)請(qǐng)求相關(guān)的文章,主要關(guān)于一些網(wǎng)絡(luò)基礎(chǔ).這篇?jiǎng)t重點(diǎn)講一講Android下httpUrlConnect的內(nèi)容。
相關(guān)鏈接
淺談Android網(wǎng)絡(luò)通信的前世今生--網(wǎng)絡(luò)基礎(chǔ)
Android下的Http client
Android提供了三種Http client:
- HttpURLConnection
- Apache HttpClient
- okHttp
HttpURLConnection
HttpUrlConnection是JDK里提供的聯(lián)網(wǎng)API,我們知道Android SDK是基于Java的,所以當(dāng)然優(yōu)先考慮HttpUrlConnection這種最原始最基本的API,其實(shí)大多數(shù)開源的聯(lián)網(wǎng)框架基本上也是基于JDK的HttpUrlConnection進(jìn)行的封裝罷了
HttpClient(不建議使用)
HttpClient是開源組織Apache提供的Java請(qǐng)求網(wǎng)絡(luò)框架,其最早是為了方便Java服務(wù)器開發(fā)而誕生的,是對(duì)JDK中的HttpUrlConnection各API進(jìn)行了封裝和簡(jiǎn)化,提高了性能并且降低了調(diào)用API的繁瑣,不過(guò)官方已經(jīng)不建議使用。
okhttp
OKHttp是現(xiàn)在主流應(yīng)用使用的網(wǎng)絡(luò)請(qǐng)求方式, 用來(lái)交換數(shù)據(jù)和內(nèi)容, 有效的使用OKHttp可以使你的APP變的更快和減少流量的使用。
- 支持SPDY,可以合并多個(gè)到同一個(gè)主機(jī)的請(qǐng)求
- 使用連接池技術(shù)減少請(qǐng)求的延遲(如果SPDY是可用的話)
- 使用GZIP壓縮減少傳輸?shù)臄?shù)據(jù)量
- 緩存響應(yīng)避免重復(fù)的網(wǎng)絡(luò)請(qǐng)求
當(dāng)你的網(wǎng)絡(luò)出現(xiàn)擁擠的時(shí)候,就是OKHttp大顯身手的時(shí)候,它可以避免常見的網(wǎng)絡(luò)問(wèn)題,如果你的服務(wù)是部署在不同的IP上面的,如果第一個(gè)連接失敗,OkHTtp會(huì)嘗試其他的連接。這對(duì)現(xiàn)在IPv4+IPv6中常見的把服務(wù)冗余部署在不同的數(shù)據(jù)中心上也是很有必要的。OkHttp將使用現(xiàn)在TLS特性(SNI ALPN)來(lái)初始化新的連接,如果握手失敗,將切換到TLS 1.0。
目前使用率最高的當(dāng)屬okHttp,不過(guò)我們這篇文章還是要先講講Android下網(wǎng)絡(luò)請(qǐng)求的先輩 HttpUrlConnect,本文基于Android API26
簡(jiǎn)單使用
URL url=new URL("www.baidu.com");
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(conn.getInputStream());
其中關(guān)鍵的兩個(gè)過(guò)程
1.openConnection() 建立tcp連接
2.getInputSteam() 發(fā)送http請(qǐng)求,并獲取返回流
openConnection()
URL.java
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
其中 handler是URLStreamHandler的實(shí)例,handler的創(chuàng)建是在URL的構(gòu)造函數(shù)中,其中調(diào)用了getURLStreamHandler()方法、
static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = handlers.get(protocol);
if (handler == null) {
boolean checkedWithFactory = false;
// Use the factory (if any)
if (factory != null) {
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
// Try java protocol handler
if (handler == null) {
final String packagePrefixList = System.getProperty(protocolPathProp,"");
StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|");
while (handler == null &&
packagePrefixIter.hasMoreTokens()) {
String packagePrefix = packagePrefixIter.nextToken().trim();
try {
String clsName = packagePrefix + "." + protocol +
".Handler";
Class<?> cls = null;
try {
ClassLoader cl = ClassLoader.getSystemClassLoader();
cls = Class.forName(clsName, true, cl);
} catch (ClassNotFoundException e) {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null) {
cls = Class.forName(clsName, true, contextLoader);
}
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (ReflectiveOperationException ignored) {
}
}
}
// Fallback to built-in stream handler.
// Makes okhttp the default http/https handler
if (handler == null) {
try {
// BEGIN Android-changed
// Use of okhttp for http and https
// Removed unnecessary use of reflection for sun classes
if (protocol.equals("file")) {
handler = new sun.net.www.protocol.file.Handler();
} else if (protocol.equals("ftp")) {
handler = new sun.net.www.protocol.ftp.Handler();
} else if (protocol.equals("jar")) {
handler = new sun.net.www.protocol.jar.Handler();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpsHandler").newInstance();
}
// END Android-changed
} catch (Exception e) {
throw new AssertionError(e);
}
}
synchronized (streamHandlerLock) {
URLStreamHandler handler2 = null;
// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = handlers.get(protocol);
if (handler2 != null) {
return handler2;
}
// Check with factory if another thread set a
// factory since our last check
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}
if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}
// Insert this handler into the hashtable
if (handler != null) {
handlers.put(protocol, handler);
}
}
}
return handler;
}
從handlers中取
如果URLStreamHandlerFactory不為空.讓URLStreamHandlerFactory生成
-
根據(jù)協(xié)議
protocol生成,注意老版本中http和https用的是streamHandler = new HttpHandler();
streamHandler = new HttpsHandler();
后來(lái)的版本用的是,底層換成了okhttp的類
handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();
handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();
最后還有個(gè)并發(fā)檢查,避免因?yàn)槎嗑€程的原因?qū)е律啥鄠€(gè)handler實(shí)例
synchronized (streamHandlerLock) {
URLStreamHandler handler2 = null;
// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = handlers.get(protocol);
if (handler2 != null) {
return handler2;
}
// Check with factory if another thread set a
// factory since our last check
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}
if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}
// Insert this handler into the hashtable
if (handler != null) {
handlers.put(protocol, handler);
}
}
URLStreamHandler是一個(gè)抽象類,子類包括http,https,ftp等實(shí)現(xiàn)類,這里我們只看http的實(shí)現(xiàn)類
protected URLConnection openConnection(URL var1) throws IOException {
return this.openConnection(var1, (Proxy)null);
}
protected URLConnection openConnection(URL var1, Proxy var2) throws IOException {
return new HttpURLConnection(var1, var2, this);
}
這里會(huì)生成一個(gè)HttpUrlConnection()對(duì)象,注意這里的HttpUrlConnection()對(duì)象和最開始的HttpUrlConnection()對(duì)象不是一個(gè),包名不同,一個(gè)是sun公司的包,一個(gè)是谷歌官方的包。
sun/HttpURLConnection.java
protected HttpURLConnection(URL var1, Proxy var2, Handler var3) {
super(var1);
....
if(this.instProxy instanceof ApplicationProxy) {
try {
this.cookieHandler = CookieHandler.getDefault();
} catch (SecurityException var5) {
;
}
} else {
this.cookieHandler = (CookieHandler)AccessController.doPrivileged(new PrivilegedAction() {
public CookieHandler run() {
return CookieHandler.getDefault();
}
});
}
this.cacheHandler = (ResponseCache)AccessController.doPrivileged(new PrivilegedAction() {
public ResponseCache run() {
return ResponseCache.getDefault();
}
});
}
去掉部分不重要的代碼之后,剩下的代碼貌似都在獲取緩存值。沒有進(jìn)行正常的網(wǎng)絡(luò)連接。
此時(shí)猜測(cè) API26的HttpUrlConnection的網(wǎng)絡(luò)請(qǐng)求發(fā)生在 getInputStream()中,而 openConnection()是為獲取上次請(qǐng)求的緩存狀態(tài)
getInputStream()
URLConnection.java中
public InputStream getInputStream() throws IOException {
throw new UnknownServiceException("protocol doesn't support input");
}
同樣URLConnection是個(gè)抽象類,跳到對(duì)應(yīng)的HttpUrlConnection.java類中
public synchronized InputStream getInputStream() throws IOException {
this.connecting = true;
SocketPermission var1 = this.URLtoSocketPermission(this.url);
if(var1 != null) {
try {
return (InputStream)AccessController.doPrivilegedWithCombiner(new PrivilegedExceptionAction() {
public InputStream run() throws IOException {
return HttpURLConnection.this.getInputStream0();
}
}, (AccessControlContext)null, new Permission[]{var1});
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
} else {
return this.getInputStream0();
}
}
一系列處理最后都會(huì)跳轉(zhuǎn)到getInputStream0()函數(shù)內(nèi)
getInputStream0()函數(shù)中的代碼過(guò)多,就不貼出來(lái)了。來(lái)大概講下流程
//判斷是否有緩存,有的話直接返回
if(this.inputStream != null) {
return this.inputStream;
}
//是否正在進(jìn)行輸入輸出字符流的行為,如果有的判斷輸出流,因?yàn)檫@時(shí)網(wǎng)絡(luò)連接還未建立,所以只可能是輸出流到緩存
if(this.streaming()) {
if(this.strOutputStream == null) {
this.getOutputStream();
}
this.strOutputStream.close();
if(!this.strOutputStream.writtenOK()) {
throw new IOException("Incomplete output stream");
}
}
同樣 getOutputStream 會(huì)跳到getOutputStream0()中
getOutputStream()
//會(huì)對(duì)URL做一次校驗(yàn),主要針對(duì)host,header,protocol和authority等
SocketPermission var1 = this.URLtoSocketPermission(this.url);
getOutputStream0()
//有寫流的行為的話,就一定是POST請(qǐng)求,因?yàn)镚ET請(qǐng)求不需要請(qǐng)求體
//所以強(qiáng)制修改method為POST
if(this.method.equals("GET")) {
this.method = "POST";
}
//檢查是否有可用的鏈接,沒有則進(jìn)行重新鏈接
if(!this.checkReuseConnection()) {
this.connect();
}
checkReuseConnection()
//先檢查connected,再檢查reuseClient,說(shuō)明reuseClient是connect的基礎(chǔ)連接
private boolean checkReuseConnection() {
if(this.connected) {
return true;
} else if(this.reuseClient != null) {
this.http = this.reuseClient;
this.http.setReadTimeout(this.getReadTimeout());
this.http.reuse = false;
this.reuseClient = null;
this.connected = true;
return true;
} else {
return false;
}
}
回到getOutputStream0()
//添加請(qǐng)求部分
if(this.streaming() && this.strOutputStream == null) {
this.writeRequests();
}
接下來(lái)回到主流程 connect()部分會(huì)調(diào)用 plainConnect0()方法
有緩存拿緩存,緩存信息完整的話就不需要重新進(jìn)行網(wǎng)絡(luò)連接了
if(this.cacheHandler != null && this.getUseCaches()) {
try {
//對(duì)url做一次處理,兼容一些缺少/等情況的url
URI var1 = ParseUtil.toURI(this.url);
if(var1 != null) {
this.cachedResponse = this.cacheHandler.get(var1, this.getRequestMethod(), this.getUserSetHeaders().getHeaders());
if("https".equalsIgnoreCase(var1.getScheme()) && !(this.cachedResponse instanceof SecureCacheResponse)) {
this.cachedResponse = null;
}
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Cache Request for " + var1 + " / " + this.getRequestMethod());
logger.finest("From cache: " + (this.cachedResponse != null?this.cachedResponse.toString():"null"));
}
if(this.cachedResponse != null) {
this.cachedHeaders = this.mapToMessageHeader(this.cachedResponse.getHeaders());
this.cachedInputStream = this.cachedResponse.getBody();
}
}
} catch (IOException var6) {
;
}
if(this.cachedHeaders != null && this.cachedInputStream != null) {
this.connected = true;
return;
}
this.cachedResponse = null;
}
沒有連接緩存的話
//第一次連接還是再次連接對(duì)應(yīng)的方法不一樣
if(!this.failedOnce) {
this.http = this.getNewHttpClient(this.url, (Proxy)null, this.connectTimeout);
this.http.setReadTimeout(this.readTimeout);
} else {
this.http = this.getNewHttpClient(this.url, (Proxy)null, this.connectTimeout, false);
this.http.setReadTimeout(this.readTimeout);
}
不管是否是第一次連接都會(huì)生成一個(gè)HttpClient對(duì)象,而這個(gè)對(duì)象才是連接網(wǎng)絡(luò)的主要成員。其中變量var3表示是否是第一次發(fā)起連接,如果是,并且httpClient對(duì)象var5是有緩存的,這時(shí)需要做一些緩存清理,變量重置的操作。因?yàn)樾枰匦麻_啟一個(gè)新的連接,需要先把老的連接清理掉
public static HttpClient New(URL var0, Proxy var1, int var2, boolean var3, HttpURLConnection var4) throws IOException {
if(var1 == null) {
var1 = Proxy.NO_PROXY;
}
HttpClient var5 = null;
if(var3) {
var5 = kac.get(var0, (Object)null);
if(var5 != null && var4 != null && var4.streaming() && var4.getRequestMethod() == "POST" && !var5.available()) {
var5.inCache = false;
var5.closeServer();
var5 = null;
}
if(var5 != null) {
if(var5.proxy != null && var5.proxy.equals(var1) || var5.proxy == null && var1 == null) {
synchronized(var5) {
var5.cachedHttpClient = true;
assert var5.inCache;
var5.inCache = false;
if(var4 != null && var5.needsTunneling()) {
var4.setTunnelState(TunnelState.TUNNELING);
}
logFinest("KeepAlive stream retrieved from the cache, " + var5);
}
} else {
synchronized(var5) {
var5.inCache = false;
var5.closeServer();
}
var5 = null;
}
}
}
//生成HttpClient對(duì)象,進(jìn)行網(wǎng)絡(luò)連接
if(var5 == null) {
var5 = new HttpClient(var0, var1, var2);
} else {
SecurityManager var6 = System.getSecurityManager();
if(var6 != null) {
if(var5.proxy != Proxy.NO_PROXY && var5.proxy != null) {
var6.checkConnect(var0.getHost(), var0.getPort());
} else {
var6.checkConnect(InetAddress.getByName(var0.getHost()).getHostAddress(), var0.getPort());
}
}
var5.url = var0;
}
return var5;
}
**HttpClient.java **
protected HttpClient(URL var1, Proxy var2, int var3) throws IOException {
this.cachedHttpClient = false;
this.poster = null;
this.failedOnce = false;
this.ignoreContinue = true;
this.usingProxy = false;
this.keepingAlive = false;
this.keepAliveConnections = -1;
this.keepAliveTimeout = 0;
this.cacheRequest = null;
this.reuse = false;
this.capture = null;
this.proxy = var2 == null?Proxy.NO_PROXY:var2;
this.host = var1.getHost();
this.url = var1;
this.port = var1.getPort();
if(this.port == -1) {
this.port = this.getDefaultPort();
}
this.setConnectTimeout(var3);
this.capture = HttpCapture.getCapture(var1);
//開啟連接服務(wù)
this.openServer();
}
openServer()函數(shù)內(nèi)調(diào)用 openServer(this.host, this.port);
傳入host和port開啟 serverSocket.setTcpNoDelay(true);服務(wù)
網(wǎng)絡(luò)連接成功,http請(qǐng)求參數(shù)也已經(jīng)設(shè)置成功,接下來(lái)只等待網(wǎng)絡(luò)回調(diào)把數(shù)據(jù)流寫會(huì)緩存即可。
補(bǔ)充說(shuō)明
- HttpURLConnection的connect()函數(shù),實(shí)際上只是建立了一個(gè)與服務(wù)器的tcp連接,并沒有實(shí)際發(fā)送http請(qǐng)求。 無(wú)論是post還是get,http請(qǐng)求實(shí)際上直到HttpURLConnection的getInputStream()這個(gè)函數(shù)里面才正式發(fā)送出去。
- 在用POST方式發(fā)送URL請(qǐng)求時(shí),URL請(qǐng)求參數(shù)的設(shè)定順序是重中之重, 對(duì)connection對(duì)象的一切配置(那一堆set函數(shù)) 都必須要在connect()函數(shù)執(zhí)行之前完成。而對(duì)outputStream的寫操作,又必須要在inputStream的讀操作之前。 這些順序?qū)嶋H上是由http請(qǐng)求的格式?jīng)Q定的。
- http請(qǐng)求實(shí)際上由兩部分組成, 一個(gè)是http頭,所有關(guān)于此次http請(qǐng)求的配置都在http頭里面定義, 一個(gè)是正文content。 connect()函數(shù)會(huì)根據(jù)HttpURLConnection對(duì)象的配置值生成http頭部信息,因此在調(diào)用connect函數(shù)之前, 就必須把所有的配置準(zhǔn)備好。
- 在http頭后面緊跟著的是http請(qǐng)求的正文,正文的內(nèi)容是通過(guò)outputStream流寫入的,實(shí)際上outputStream不是一個(gè)網(wǎng)絡(luò)流,充其量是個(gè)字符串流,往里面寫入的東西不會(huì)立即發(fā)送到網(wǎng)絡(luò), 而是存在于內(nèi)存緩沖區(qū)中,待outputStream流關(guān)閉時(shí),根據(jù)輸入的內(nèi)容生成http正文。 至此,http請(qǐng)求的東西已經(jīng)全部準(zhǔn)備就緒。在getInputStream()函數(shù)調(diào)用的時(shí)候,就會(huì)把準(zhǔn)備好的http請(qǐng)求 正式發(fā)送到服務(wù)器了,然后返回一個(gè)輸入流,用于讀取服務(wù)器對(duì)于此次http請(qǐng)求的返回信息。由于http 請(qǐng)求在getInputStream的時(shí)候已經(jīng)發(fā)送出去了(包括http頭和正文),因此在getInputStream()函數(shù) 之后對(duì)connection對(duì)象進(jìn)行設(shè)置(對(duì)http頭的信息進(jìn)行修改)或者寫入outputStream(對(duì)正文進(jìn)行修改) 都是沒有意義的了,執(zhí)行這些操作會(huì)導(dǎo)致異常的發(fā)生。
參考文章
Android-淺析-HttpURLConnection
網(wǎng)絡(luò)請(qǐng)求HttpURLConnection剖析
Android每周一輪子:HttpURLConnection