? 在java進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候毙芜,使用的基本單位請(qǐng)求工具,就是最常用的
HttpURLConnection争拐,有人是這是java標(biāo)準(zhǔn)庫(kù)提供的基本小部件(whatever)腋粥。在進(jìn)行高性能晦雨,高可用性選型的時(shí)候,很有必要隘冲,對(duì)HttpURLConnection最更加底層的了解闹瞧。
? ? ? ? 首先,在使用層面展辞,無論使用什么網(wǎng)絡(luò)框架奥邮,對(duì)于緩存,請(qǐng)求超時(shí)配置罗珍,請(qǐng)求體數(shù)據(jù)體的配置洽腺,https證書信任,等層面的東西覆旱,在工具框架庫(kù)的層面蘸朋,大同小異,只是封裝的模式扣唱,提供的功能差異藕坯,不夠根本的差距。
深層次一點(diǎn)噪沙,在進(jìn)行socket請(qǐng)求時(shí)炼彪,請(qǐng)求的重試,重定向正歼,socket連接池辐马,協(xié)議的支持等層面,會(huì)更深一層次反應(yīng)朋腋,這個(gè)框架基礎(chǔ)的性能齐疙。是的,這里旭咽,我們?nèi)匀欢际窃趕okect的基礎(chǔ)上的贞奋。
*******************************下面,剖析HttpURLConnection的本來面目******************
? ? ? ? 這個(gè)類的使用穷绵,這里不說了轿塔,網(wǎng)上到處都是,怎么做數(shù)據(jù)的設(shè)置提交仲墨,和數(shù)據(jù)的接收和關(guān)閉等等勾缭。
首先 HttpURLConnection 和 HttpsURLConnection 都是抽象類。
[java]view plaincopy
abstract?public?class?HttpURLConnection?extends?URLConnection?{}??
abstract?public?class?HttpsURLConnection?extends?HttpURLConnection{}??
HttpURLConnection,和HttpsURLConnection只是做了http狀態(tài)碼的定義目养,默認(rèn)head定義俩由,和ssl工廠,hostName審核器的判斷定義癌蚁。真真正正的connect()?的實(shí)現(xiàn)幻梯,都是沒有的兜畸。也就是說,這兩個(gè)類抽象的部分碘梢,就是網(wǎng)絡(luò)請(qǐng)求的根本部分咬摇。這樣確實(shí)有解耦的作用。
通過源碼煞躬,我們找到肛鹏,HttpURLConnection實(shí)例對(duì)象的獲得。
[java]view plaincopy
URLStreamHandler?handler;??
public?URLConnection?openConnection()?throws?java.io.IOException?{??
return?handler.openConnection(mUrl);??
????}??
//這個(gè)靜態(tài)方法恩沛,是獲得對(duì)URL流處理器的根本在扰,這里面,實(shí)現(xiàn)了URLStreamHandler的單例化复唤,運(yùn)行時(shí)只需要獲得一次??
static?URLStreamHandler?getURLStreamHandler(String?protocol)?{??
//這里面健田,有一個(gè)工廠生成,這個(gè)工廠佛纫,可以通過外部實(shí)現(xiàn)接口,??
handler?=?factory.createURLStreamHandler(protocol);??
}??
這個(gè)用來生成URLStreamHandler的工程可以通過setURLStreamHandlerFactory設(shè)置总放。但是我們默認(rèn)情況下呈宇,都是采用標(biāo)準(zhǔn)庫(kù)的自己的實(shí)現(xiàn)。抽象類 URLStreamHandler 是所有流協(xié)議處理程序的通用超類.
這里才是整片文檔的關(guān)鍵局雄,
[java]view plaincopy
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?(Exception?ignored)?{??
"white-space:pre">??????????//刪除了一些異常處理甥啄,這里看主體。代碼在URL.java中炬搭。??
????????}??
????}??
}??
從代碼看出來蜈漓,jdk把具體的實(shí)現(xiàn),交給了虛擬機(jī)的運(yùn)行時(shí)宫盔。
[java]view plaincopy
java.protocol.handler.pkgs融虽,這個(gè)虛擬機(jī)環(huán)境變量。這個(gè)值灼芭,可以通過命令改變有额,從而改變網(wǎng)絡(luò)協(xié)議的具體實(shí)現(xiàn)。??
????java.protocol.handler.pkgs=com.acme.protocol??
????java.protocol.handler.pkgs=com.acme.protocol|com.acme.protocol2??
通過上面的代碼塊可以看出來彼绷,會(huì)默認(rèn)優(yōu)先加載前面的可加載類??
????類的命名模式為?[package_path].[protocol].Handler??
而可用來實(shí)現(xiàn)handler的類名稱數(shù)據(jù)巍佑,是在編譯是的一個(gè)類路徑下。我們可以根據(jù)工廠有自己的協(xié)議實(shí)現(xiàn)寄悯,也可以通過這種熱加載機(jī)制萤衰,在虛擬器啟動(dòng)時(shí),改變虛擬機(jī)java.protocol.handler.pkg這個(gè)環(huán)境變量key的值猜旬。
如果根據(jù)這個(gè)環(huán)境變量脆栋,還是沒有能夠加載URLStreamHandler的類倦卖,jdk1.8默認(rèn)有幾個(gè)固定的判斷。
[java]view plaincopy
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);??
????}??
}??
這里有關(guān)于這種機(jī)制的討論:https://accu.org/index.php/journals/1434
這里筹吐,找到兩個(gè)實(shí)現(xiàn)糖耸,可以看一下:
1>.HttpConnection對(duì)于網(wǎng)絡(luò)的實(shí)現(xiàn),是默認(rèn)外包的丘薛,外包方大多取決于定制的平臺(tái)嘉竟,大多還是sun自己httpClient的子孫或者嫡系,這個(gè)我沒有找到關(guān)于這個(gè)的實(shí)現(xiàn)洋侨。
在rt.jar包中有一種實(shí)現(xiàn)(基本上可以把這個(gè)作為大多數(shù)情況下jdk的實(shí)現(xiàn))舍扰。sun.net.www.protocol.https.HttpsURLConnectionImpl(DelegateHttpsURLConnection):
[java]view plaincopy
sun.net.www.protocol.http.HttpURLConnection?extends?java.net.HttpURLConnection{??
public?void?connect()?throws?IOException?{??
synchronized(this)?{??
this.connecting?=?true;??
????????}??
this.plainConnect();//這里首先會(huì)進(jìn)行?URLtoSocketPermission?檢查(只是協(xié)議主機(jī)路徑等的合法性)??
????}??
}??
上面可以看到sun.net.www.protocol里有一套具體的請(qǐng)求實(shí)現(xiàn),
[java]view plaincopy
plainConnect0();//此方法首先會(huì)使用cacheHandler設(shè)置cachedResponse,作為部分字段希坚。??
//然后這個(gè)方法包含走代理的邏輯边苹。instProxy作為代理。??
//最后無論是否會(huì)使用代理或者代理的層級(jí)裁僧,都會(huì)動(dòng)過HttpClient.New()方法使用HttpClient.??
通過調(diào)用getInputStream()->getInputStream0也會(huì)間接地調(diào)用getOutputStream()个束,而調(diào)用getOutputStream()則會(huì)間接使用getOutputStream0()使用HttpClient? http.getOutputStream()方法。最終的請(qǐng)求都是HttpClient的父類NetworkClient所做聊疲,根據(jù)傳遞的信息配置和使用?InetSocketAddress茬底。
? ? 2>.下面是OKHttp的實(shí)現(xiàn),這個(gè)就不用多說了获洲,利用Okhttp框架的源碼部分阱表,新版本系統(tǒng)已經(jīng)作為默認(rèn)的實(shí)現(xiàn):
[java]view plaincopy
public?final?class?HttpHandler?extends?URLStreamHandler?{??
@Override?protected?URLConnection?openConnection(URL?url)?throws?IOException?{??
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");??
????????}??
//‘com.squareup.okhttp:okhttp:1.5.0’??
return?new?OkHttpClient().setProxy(proxy).open(url);//開放出來的Builder類并沒有open方法。??
????}??
@Override?protected?int?getDefaultPort()?{??
return?80;??
????}??
}??
[java]view plaincopy
//上面對(duì)網(wǎng)絡(luò)請(qǐng)求贡珊,會(huì)發(fā)到excute上最爬。。门岔。后面的東西就是OKHttp的部分了爱致。??
execute(boolean?readResponse)?throws?IOException?{??
//?調(diào)用了HttpEngine的sendRequest方法。??
??httpEngine.sendRequest();??
??route?=?httpEngine.getRoute();??
handshake?=?httpEngine.getConnection()?!=null???httpEngine.getConnection().getHandshake():?null;??
}??
整體就是:HttpURLConnection的抽象方法connect等? <--需要URLStreamHandler來產(chǎn)生 <--handler需要自定義的工廠創(chuàng)建或者jdk自己實(shí)現(xiàn)的
網(wǎng)上固歪,別人的文檔蒜鸡,來源未知:
Android 2.2版本之前,bug比如說對(duì)一個(gè)可讀的InputStream調(diào)用close()方法時(shí)牢裳,就有可能會(huì)導(dǎo)致連接池失效了逢防。那么我們通常的解決辦法就是直接禁用掉連接池的功能:
在Android 4.0版本中,我們又添加了一些響應(yīng)的緩存機(jī)制蒲讯。當(dāng)緩存被安裝后(調(diào)用HttpResponseCache的install()方法)忘朝,所有的HTTP請(qǐng)求都會(huì)滿足以下三種情況:
比較輕便,靈活判帮,易于擴(kuò)展
在3.0后以及4.0中都進(jìn)行了改善局嘁,如對(duì)HTTPS的支持
在4.0中溉箕,還增加了對(duì)緩存的支持
在android 2.2及以下版本中HttpUrlConnection存在著一些bug,所以建議在android 2.3以后使用HttpUrlConnection悦昵,2.3之前使用HttpClient肴茄。