一:okhttp是什么
? ? ? ?大家都知道HTTP是現(xiàn)代應(yīng)用網(wǎng)絡(luò)請(qǐng)求的方式咆槽,是一個(gè)簡(jiǎn)單的請(qǐng)求-響應(yīng)協(xié)議来吩。在數(shù)據(jù)和媒體中進(jìn)行交換俐银。有效地請(qǐng)求使您的東西加載更快,并節(jié)省帶寬理逊。OKHttp是安卓開發(fā)中十分常用的網(wǎng)絡(luò)請(qǐng)求框架橡伞。
二:為什么使用
? ? ? 1:緩存響應(yīng)數(shù)據(jù)來(lái)減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求
? ? ? ?2:可以從很多常用的連接問(wèn)題中自動(dòng)恢復(fù);(Okhttp處理了很多的網(wǎng)絡(luò)疑難雜癥:會(huì)從很多常用的連接問(wèn)題中自動(dòng)恢復(fù)晋被。如果您的服? ? ? ? 務(wù)器配置了多個(gè)IP地址兑徘,當(dāng)?shù)谝粋€(gè)IP連接失敗時(shí),OkHttp會(huì)嘗試連接下一個(gè)IP羡洛。)
? ? ? ?3:支持gzip壓縮響應(yīng)體
? ? ? ?4:使用簡(jiǎn)單挂脑,可擴(kuò)展性非常的強(qiáng);(責(zé)任鏈模式使得很容易添加一個(gè)自定義攔截器對(duì)請(qǐng)求和返回結(jié)果進(jìn)行處理)
三:用來(lái)干什么
? ? ? 1:支持一般的get請(qǐng)求欲侮,post請(qǐng)求
? ? ? ?2:基于Http的文件上傳最域,文件下載,上傳下載的進(jìn)度回調(diào)
? ? ? ?3:圖片加載
? ? ? ?4:支持請(qǐng)求回調(diào)锈麸,對(duì)象返回
? ? ? ?5:? ?表單請(qǐng)求
四:請(qǐng)求流程
? ? ? ?1:生成一個(gè)OkHttpClient(用以總的控制)
? ? ? ?2:用各種鍵值對(duì)包裝我們的Request
? ? ? ?3:將請(qǐng)求加入隊(duì)列生成一個(gè)Call管理請(qǐng)求
? ? ? ?4:若是同步請(qǐng)求直接執(zhí)行excute等待處理返回Response,若為異步則實(shí)現(xiàn)Callback回調(diào)牺蹄,在onResponse里獲取Response參數(shù)
五:同步與異步的實(shí)現(xiàn)
在發(fā)起請(qǐng)求時(shí)忘伞,整個(gè)框架主要通過(guò)Call來(lái)封裝每一次的請(qǐng)求。同時(shí)Call持有OkHttpClient和一份HttpEngine沙兰。而每一次的同步或者異步請(qǐng)求都會(huì)有Dispatcher的參與氓奈。
Dispatcher
Dispatcher內(nèi)部實(shí)現(xiàn)了懶加載無(wú)邊界限制的線程池方式,同時(shí)該線程池采用了 SynchronousQueue這種阻塞隊(duì)列鼎天。SynchronousQueue每個(gè)插入操作必須等待另一個(gè)線程的移除操作舀奶,同樣任何一個(gè)移除操作都等待另一個(gè)線程的插入操作。因此此隊(duì)列內(nèi)部其實(shí)沒有任何一個(gè)元素斋射,或者說(shuō)容量是0育勺,嚴(yán)格說(shuō)并不是一種容器但荤。由于隊(duì)列沒有容量,因此不能調(diào)用peek操作涧至,因?yàn)橹挥幸瞥貢r(shí)才有元素腹躁。顯然這是一種快速傳遞元素的方式,也就是說(shuō)在這種情況下元素總是以最快的方式從插入者(生產(chǎn)者)傳遞給移除者(消費(fèi)者)南蓬,這在多任務(wù)隊(duì)列中是最快處理任務(wù)的方式纺非。對(duì)于高頻繁請(qǐng)求的場(chǎng)景,無(wú)疑是最適合的赘方。
同步
Dispatcher會(huì)在同步執(zhí)行任務(wù)隊(duì)列中記錄當(dāng)前被執(zhí)行過(guò)得任務(wù)Call烧颖,同時(shí)在當(dāng)前線程中去執(zhí)行Call的getResponseWithInterceptorChain()方法,直接獲取當(dāng)前的返回?cái)?shù)據(jù)Response窄陡;
異步
異步執(zhí)行是通過(guò)Call.enqueue(Callback responseCallback)來(lái)執(zhí)行炕淮,在Dispatcher中添加一個(gè)封裝了Callback的Call的匿名內(nèi)部類Runnable來(lái)執(zhí)行當(dāng)前 的Call。這里一定要注意的地方這個(gè)AsyncCall是Call的匿名內(nèi)部類泳梆。AsyncCall的execute方法仍然會(huì)回調(diào)到Call的 getResponseWithInterceptorChain方法來(lái)完成請(qǐng)求鳖悠,同時(shí)將返回?cái)?shù)據(jù)或者狀態(tài)通過(guò)Callback來(lái)完成。
六:攔截器
攔截器可以注冊(cè)為應(yīng)用攔截器和網(wǎng)絡(luò)攔截器优妙。假如有一個(gè)壓縮攔截器和一個(gè)檢驗(yàn)和攔截器:你需要決定是先數(shù)據(jù)進(jìn)行壓縮然后檢驗(yàn)和乘综,還是先檢驗(yàn)和然后進(jìn)行壓縮。OkHttp使用列表來(lái)跟蹤攔截器套硼,并且攔截器按順序被調(diào)用卡辰。
應(yīng)用攔截器
1:不需要關(guān)心像重定向和重試這樣的中間響應(yīng)。
2:總是調(diào)用一次邪意,即使HTTP響應(yīng)從緩存中獲取服務(wù)九妈。
3:監(jiān)視應(yīng)用原始意圖。不關(guān)心OkHttp注入的像If-None-Match頭雾鬼。
4:允許短路并不調(diào)用Chain.proceed()萌朱。
5:允許重試并執(zhí)行多個(gè)Chain.proceed()調(diào)用。
網(wǎng)絡(luò)攔截器
1:可以操作像重定向和重試這樣的中間響應(yīng)策菜。
2:對(duì)于短路網(wǎng)絡(luò)的緩存響應(yīng)不會(huì)調(diào)用晶疼。
3:監(jiān)視即將要通過(guò)網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)。
4:訪問(wèn)運(yùn)輸請(qǐng)求的Connection又憨。
七:線程池技術(shù)
相比我們對(duì)于異步任務(wù)的需求應(yīng)該遇到了不少翠霍,首先想到的便是Thread,handler等異步機(jī)制蠢莺,Java已經(jīng)做了很好的封裝寒匙,但是當(dāng)我們需要使用許多異步任務(wù),而這些任務(wù)只做了一點(diǎn)點(diǎn)事情就完成了它的使命躏将,當(dāng)我們不斷的創(chuàng)建锄弱,銷毀線程的時(shí)候考蕾,對(duì)系統(tǒng)的開銷是相當(dāng)大的,因?yàn)檫@些過(guò)程也是耗費(fèi)時(shí)間的棵癣,這個(gè)時(shí)候我們就需要用到線程池了辕翰,我們只要往池子里放任務(wù),他就會(huì)自動(dòng)幫你管理線程的創(chuàng)建與銷毀狈谊,利用緩存復(fù)用減少線程銷毀創(chuàng)建喜命,優(yōu)化系統(tǒng)性能。以下是線程池的優(yōu)點(diǎn):
1:通過(guò)對(duì)線程進(jìn)行緩存河劝,減少了創(chuàng)建銷毀的時(shí)間損失
2:通過(guò)控制線程數(shù)量閥值壁榕,減少了當(dāng)線程過(guò)少時(shí)帶來(lái)的CPU閑置(比如說(shuō)長(zhǎng)時(shí)間卡在I\O上了)與線程過(guò)多時(shí)對(duì)JVM的內(nèi)存與線程切換壓力而Dispatcher內(nèi)部的核心即線程池
int corePoolSize: 最小并發(fā)線程數(shù),這里并發(fā)同時(shí)包括空閑與活動(dòng)的線程赎瞎,如果是0的話牌里,空閑一段時(shí)間后所有線程將全部被銷毀。
int maximumPoolSize: 最大線程數(shù)务甥,當(dāng)任務(wù)進(jìn)來(lái)時(shí)可以擴(kuò)充的線程最大值牡辽,當(dāng)大于了這個(gè)值就會(huì)根據(jù)丟棄處理機(jī)制來(lái)處理
long keepAliveTime: 當(dāng)線程數(shù)大于corePoolSize時(shí),多余的空閑線程的最大存活時(shí)間敞临,類似于HTTP中的Keep-alive
TimeUnit unit: 時(shí)間單位态辛,一般用秒
BlockingQueue workQueue: 工作隊(duì)列
ThreadFactory threadFactory: 單個(gè)線程的工廠挺尿,可以打Log奏黑,設(shè)置Daemon(即當(dāng)JVM退出時(shí)编矾,線程自動(dòng)結(jié)束)等
OkHttp內(nèi)Dispatcher原理其實(shí)當(dāng)我們調(diào)用client.newCall(request).enqueue(new callback(){...})的時(shí)候?qū)嵸|(zhì)是下圖
反向代理模式
為了解決單生產(chǎn)者多消費(fèi)者問(wèn)題,OkHttp采用了反向代理模式限寞,來(lái)解決非阻塞缎岗,高并發(fā)問(wèn)題
Dispatch模式
OkHttp內(nèi)Dispatcher原理其實(shí)當(dāng)我們調(diào)用client.newCall(request).enqueue(new callback(){...})的時(shí)候?qū)嵸|(zhì)是判斷線程池內(nèi)有無(wú)空閑拦盹,否則進(jìn)入等待隊(duì)列,AsyncCall實(shí)質(zhì)是Runnable溪椎,當(dāng)任務(wù)執(zhí)行完畢后普舆,總會(huì)調(diào)用finally{client.dispatcher().finished(this);}清除此任務(wù)。我們回頭看看OkhttpClient初始化內(nèi)其它一些參數(shù)Expires校读,Cache-Control沼侣,Last-Modified,If-Modified-Since歉秫,ETag蛾洛,If-None-Match...這些都牽扯到緩存問(wèn)題。
Response response = getResponseWithInterceptorChain();
其實(shí)這個(gè)方法是返回我們的response對(duì)象雁芙。到這里整個(gè)流程已經(jīng)完畢轧膘。
使用OkHttpClient 的時(shí)候需要注意以下幾點(diǎn):
1:最好只使用一個(gè)共享的OkHttpClient 實(shí)例,將所有的網(wǎng)絡(luò)請(qǐng)求都通過(guò)這個(gè)實(shí)例處理兔甘。因?yàn)槊總€(gè)OkHttpClient 實(shí)例都有自己的連接池和線程池谎碍,重用這個(gè)實(shí)例能降低延時(shí),減少內(nèi)存消耗裂明,而重復(fù)創(chuàng)建新實(shí)例則會(huì)浪費(fèi)資源椿浓。
2:OkHttpClient的線程池和連接池在空閑的時(shí)候會(huì)自動(dòng)釋放,所以一般情況下不需要手動(dòng)關(guān)閉闽晦,但是如果出現(xiàn)極端內(nèi)存不足的情況扳碍,可以使用以下代碼釋放內(nèi)存:
???? ?client.dispatcher().executorService().shutdown(); ??//清除并關(guān)閉線程池
??????client.connectionPool().evictAll(); ????????????????//清除并關(guān)閉連接池
??????client.cache().close(); ??????
3:如果對(duì)一些請(qǐng)求需要特殊定制,可以使用
OkHttpClient eagerClient = client.newBuilder()
????.readTimeout(500, TimeUnit.MILLISECONDS)
????.build();
這樣創(chuàng)建的實(shí)例與原實(shí)例共享線程池仙蛉、連接池和其他設(shè)置項(xiàng)笋敞,只需進(jìn)行少量配置就可以實(shí)現(xiàn)特殊需求。
Request
Request是網(wǎng)絡(luò)請(qǐng)求的對(duì)象荠瘪,其本身的構(gòu)造函數(shù)是private的夯巷,只能通過(guò)Request.Builder來(lái)構(gòu)建。下面代碼展示了常用的設(shè)置項(xiàng)哀墓。
Request request = new?Request.Builder()
????????.url("https://api.github.com/repos/square/okhttp/issues")? ? ? ? ? ? ? ? ? ? ? ? ?//設(shè)置訪問(wèn)url
????????.get()? //類似的有post趁餐、delete、patch篮绰、head后雷、put等方法,對(duì)應(yīng)不同的網(wǎng)絡(luò)請(qǐng)求方法
????????.header("User-Agent", "OkHttp Headers.java") ??????????????????????????????????????//設(shè)置header
????????.addHeader("Accept", "application/json; q=0.5")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//添加header
????????.removeHeader("User-Agent")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //移除header
????????.headers(new?Headers.Builder().add("User-Agent", "OkHttp Headers.java").build()) ??
? ? ? ? ?//移除原有所有header,并設(shè)置新header
????????.addHeader("Accept", "application/vnd.github.v3+json")
????????.build();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//構(gòu)建request
RequestBody
在Request中使用post臀突、patch等方法時(shí)勉抓,需要傳入一個(gè)RequestBody參數(shù),除了上一節(jié)講到的構(gòu)造方法外候学,RequestBody還有兩個(gè)子類:FormBody和MultipartBody藕筋。
FromBody用于提交表單鍵值對(duì),其作用類似于HTML中的<form>標(biāo)記梳码。
RequestBody formBody = new?FormBody.Builder() ????????????????????????????//提交表單鍵值對(duì)
????????.add("platform", "android")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //添加鍵值對(duì)
????????.add("name", "XXX")
????????.add("subject", "Hello")
????????.addEncoded(URLEncoder.encode("詳細(xì)","GBK"), URLEncoder.encode("無(wú)","GBK"))
? ? ? ? //添加已編碼的鍵值對(duì)
????????.add("描述","你好")? ? ? ? ? //其實(shí)會(huì)自動(dòng)編碼隐圾,但是無(wú)法控制編碼格式
????????.build();
使用MultipartBody.Builder可以構(gòu)建與HTML文件上傳格式兼容的復(fù)雜請(qǐng)求體。多塊請(qǐng)求體中每塊請(qǐng)求都是一個(gè)獨(dú)立的請(qǐng)求體边翁,都可以定義自己的請(qǐng)求頭翎承。這些請(qǐng)求頭應(yīng)該用于描述對(duì)應(yīng)的請(qǐng)求體,例如Content-Disposition符匾,Content-Length叨咖,和Content-Type會(huì)自動(dòng)被添加到請(qǐng)求頭中。
Call
Call對(duì)象表示一個(gè)已經(jīng)準(zhǔn)備好可以執(zhí)行的請(qǐng)求啊胶,用這個(gè)對(duì)象可以查詢請(qǐng)求的執(zhí)行狀態(tài)甸各,或者取消當(dāng)前請(qǐng)求。它具有以下方法:
Call call=client.newCall(request); ???????????????//獲取Call對(duì)象
Response response=call.execute(); ????????????????//同步執(zhí)行網(wǎng)絡(luò)請(qǐng)求焰坪,不要在主線程執(zhí)行
call.enqueue(new?Callback()); ????????????????????//異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求
call.cancel(); ???????????????????????????????????//取消請(qǐng)求
call.isCanceled(); ???????????????????????????????//查詢是否取消
call.isExecuted(); ???????????????????????????????//查詢是否被執(zhí)行過(guò)
要注意的是趣倾,每個(gè)Call對(duì)象只能執(zhí)行一次請(qǐng)求。如果想重復(fù)執(zhí)行相同的請(qǐng)求某饰,可以:
Call reCall=client.newCall(call.request()); ??????//獲取另一個(gè)相同配置的Call對(duì)象
Response
Response是網(wǎng)絡(luò)請(qǐng)求的結(jié)果下面是一些常用方法:
Response response=call.execute(); ???????????????//獲取Response對(duì)象
response.code(); ????????????????????????????????//請(qǐng)求的狀態(tài)碼
response.isSuccessful(); ????????????????????????//如果狀態(tài)碼為[200..300)儒恋,則表明請(qǐng)求成功
Headers headers=response.headers(); ?????????????//獲取響應(yīng)頭
List<String> names=response.headers("name"); ????//獲取響應(yīng)頭中的某個(gè)字段
ResponseBody body=response.body(); ??????????????//獲取響應(yīng)體
其中ResponseBody代表響應(yīng)體,用于操作網(wǎng)絡(luò)請(qǐng)求返回的內(nèi)容黔漂。常用方法如下:
body.contentLength(); ??????????????????????????//body的長(zhǎng)度
String content=body.string(); ??????????????????//以字符串形式解碼bodybyte[] byteContent=body.bytes(); ???????????????//以字節(jié)數(shù)組形式解碼body
InputStreamReader reader=body.charStream(); ????//將body以字符流的形式解碼
InputStream inputStream=body.byteStream(); ?????//將body以字節(jié)流的形式解碼
ResponseBody
1:ResponseBody必須關(guān)閉诫尽,不然可能造成資源泄漏,你可以通過(guò)以下方法關(guān)閉ResponseBody,對(duì)同一個(gè)ResponseBody只要關(guān)閉一次就可以了炬守。
Response.close();
Response.body().close();
Response.body().source().close();
Response.body().charStream().close();
Response.body().byteString().close();
Response.body().bytes();
Response.body().string();
2:ResponseBody只能被消費(fèi)一次牧嫉,也就是string(),bytes(),byteStream()或 charStream()方法只能調(diào)用其中一個(gè)。
3:如果ResponseBody中的數(shù)據(jù)很大减途,則不應(yīng)該使用bytes() 或 string()方法酣藻,它們會(huì)將結(jié)果一次性讀入內(nèi)存,而應(yīng)該使用byteStream()或 charStream()鳍置,以流的方式讀取數(shù)據(jù)辽剧。
八:OkHttp中的設(shè)計(jì)模式
1:責(zé)任鏈模式:攔截器鏈
2:?jiǎn)卫J剑壕€程池
3:觀察者模式:各種回調(diào)監(jiān)聽
4:策略模式:緩存策略
5:Builder模式:OkHttpClient的構(gòu)建過(guò)程