前言:現(xiàn)在做 Android 開發(fā)澳叉,應該絕大部分都是用 OkHttp 來做網(wǎng)絡請求的成洗∑垦辏可見 OkHttp 的強大遥椿。今天來總結下 OkHttp 的使用冠场,OkHttp 中用到了大量的 Builder 模式碴裙,建議看此文章前先閱讀 Android開發(fā)---Builder 模式必知必會
OkHttp 學習之前,應該對 Http 有一定的了解还棱,可以參考下這里:你需要了解的HTTP知識都在這里了诱贿!
開頭引用官網(wǎng)的自我介紹:
HTTP是現(xiàn)代應用網(wǎng)絡的方式。這是我們如何交換數(shù)據(jù)和媒體凭豪。有效地進行HTTP使您的東西加載更快嫂伞,并節(jié)省帶寬帖努。
OkHttp 有以下特點:
- HTTP / 2支持允許同一主機的所有請求共享套接字拼余。
- 連接池減少請求延遲(如果HTTP / 2不可用)匙监。
- 透明GZIP縮小下載大小亭姥。
- 響應緩存可以避免重復請求的網(wǎng)絡达罗。
ok粮揉,我們開始來探究一下 OkHttp 的使用滔蝉。
首先添加依賴
compile 'com.squareup.okhttp3:okhttp:3.8.0'
OkHttp 發(fā)起網(wǎng)絡請求非常簡單蝠引,具體步驟如下:
- 構建 OkHttpClient 客戶端
- 構建 Request 請求
- 創(chuàng)建 Call 對象螃概,發(fā)起網(wǎng)絡請求
- 處理 Response 響應結果
這么看來吊洼,只要搞懂這幾個關鍵類冒窍,跟調用請求的過程综液。對于 OkHttp 的基本使用就沒問題了谬莹。
一附帽、Request
Request 對應于 Http 的請求報文蕉扮,查看源碼可以看到慢显,Request 封裝了請求報文的一些字段荚藻。
發(fā)起網(wǎng)絡請求必然要配置這些請求報文字段应狱,Request
采用 Builder 模式除嘹,通過 Builder 內部類使我們靈活去配置 Request
對象尉咕,默認初始化Request request = new Request.Builder().build();
是配置了請求頭,請求方法GET
璃岳,也可以在調用build()
方法之前調用對應方法進行配置年缎。如果需要對 其中的某個字段做特殊配置悔捶,可以Request.Builder builder = request.newBuilder()
拿到 Builder 對象進行 "返廠重建"。
- Http 請求地址
url
- 請求方法单芜,例如(GET蜕该,POST等等),配置請求方法時調用
get()
堂淡,post()
等,內部都是調用method()
方法傳入請求方法與請求體扒腕,并對是否需要請求體進行判斷绢淀。 -
Headers
:維護 Http 消息的頭字段,內部也是通過 Builder 模式構建字符串鍵值對來維護消息頭袜匿。 -
Request
初始化時默認構造了Headers
的 Builder 實例更啄,我們初始化Request
可以調用header()
方法設置請求頭的name
稚疹,value
字段居灯。其實內部是調用了Headers
的 Builder 實例的set()
方法設置的,這種方法設置的head
表示name
是唯一的内狗。因為設置之前內部調用了removeAll()
方法先移除了相同的name
對應的value
怪嫌。 - 如果想添加多個
name
相同的請求頭,可以調用addHeader()
方法進行設置柳沙,其實內部是調用了Headers
的 Builder 實例的add()
方法設置的 - 也可以調用
removeHeader()
方法移除同一個name
的所有請求頭 - 可以通過
Request
的head()
方法獲得指定請求頭岩灭,·其實內部也是通過Headers
的get()
方法通過name
獲取的value
,headers()
方法獲得同一個name
的所有value
值赂鲤,以list
形式返回 - 請求體
RequestBody
(一般用來向服務器提交數(shù)據(jù)請求時用到) -
tag
噪径,設置請求標簽,用于取消網(wǎng)絡請求 -
CacheControl
用于控制緩存使用数初。
二找爱、構建 RequestBody
用于攜帶數(shù)據(jù)請求服務器,一般就是在特定的請求才會攜帶請求頭泡孩,例如 POST
车摄,PUT
等。查看 RequestBody
源碼可以看到有五個 static 方法用于創(chuàng)建 RequestBody
實例仑鸥,第一個參數(shù)都是 MediaType
表示數(shù)據(jù)的 MIME 類型吮播,第二個參數(shù)分別可選為:String
,File
眼俊,byte[]
或者ByteString
意狠,也就是提交的數(shù)據(jù)內容。
-
MediaType
類封裝了三個字段:type
疮胖、subtype
环戈、CharSet
誊役。分別對應 MIME 類型中的數(shù)據(jù)類
,數(shù)據(jù)類型下的子類
谷市,編碼類型
蛔垢。例如text/x-markdown; charset=utf-8
,text
代表文本大類(type)迫悠,x-markdown
代表文本類下的子類(subtype)鹏漆,charset=utf-8
則表示采用UTF-8編碼,內部封裝了一個解析方法parse()
创泄,只要傳入這些信息就能獲得MediaType
實例艺玲。可以參考鏈接Media Types和MIME 參考手冊 平時比較常用的 MIME 類型如下:json :application/json
鞠抑,xml:application/xml
饭聚,png:image/png
,jpg: image/jpeg
搁拙,gif:image/gif
除了直接使用靜態(tài)方法create()
構建之外秒梳, RequestBody
還有兩個直接子類FormBody
,MultipartBody
箕速,分別用于提交 Form 表單中的鍵值對和構建分塊表單請求體(既可以添加表單,又可以也可以添加文件等二進制數(shù)據(jù))
FormBody表單創(chuàng)建
通過內部類 Builder
構建 FormBody
實例酪碘,同時調用 add()
傳入需要傳的鍵值對,例如:
RequestBody formBody = new FormBody.Builder()
.add("username", "張少林")
.build();
構建分塊表單請求體 MultipartBody
MultipartBody
也是通過內部類 MultipartBody.Builder
動態(tài)構建實例盐茎,構建實例之前有以下幾個方法:
-
setType()
:設置MultipartBody
的MediaType
類型兴垦,一般會設置為MultipartBody.FORM
,也就是multipart/form-data
. -
addFormDataPart(String name, String value)
:添加字符串鍵值對 -
addFormDataPart(String name, @Nullable String filename, RequestBody body)
:添加表單文件 - 其中三個重載
addPart()
方法字柠,分別添加請求體探越,其中的addPart(@Nullable Headers headers, RequestBody body)
可以在添加RequestBody的時候,同時為其單獨設置請求頭
源碼淺析:查看MultipartBody.Builder
源碼發(fā)現(xiàn)窑业,addFormDataPart(String name, String value)
內部其實都是調用了addPart(Part part)
钦幔,而addPart(Part part)
內部是往全局實例化好的List<Part>
中 add 實例化好的part
。而MultipartBody.Part
其實就是包含了RequestBody
数冬,所以可以說 MultipartBody
是一個RequestBody
的集合节槐。
三、Response
Response對應于 Http 的響應報文拐纱,查看源碼可以看到铜异,Request 封裝了響應報文的一些字段。
封裝的字段有 對應的請求報文Request
秸架,響應碼 code
揍庄,響應消息 message
,響應體ResponseBody
东抹,網(wǎng)絡請求響應結果以及緩存響應結果等字段蚂子。同樣的沃测,Response
的構建也是通過內部類 Response.Builder 進行動態(tài)靈活配置,對其中的一些常用方法做簡單介紹:
-
isSuccessful()
:用來判斷請求是否成功 -
body()
:返回響應體ResponseBody
-
headers()
:獲取響應頭Headers
對象
四食茎、ResponseBody
通過 Response
的body()
方法可以得到響應體ResponseBody
蒂破,響應體必須最終要被關閉,否則會導致資源泄露别渔、App運行變慢甚至崩潰附迷。
響應體中的數(shù)據(jù)有可能很大,應該只讀取一次響應體的數(shù)據(jù)哎媚。調用ResponseBody
的bytes()
或string()
方法會將整個響應體數(shù)據(jù)寫入到內存中喇伯,可以通過source()
、byteStream()
或charStream()
進行流式處理拨与。
五稻据、OkHttpClient
OkHttp 網(wǎng)絡請求客戶端,封裝了 一些客戶端請求的常用配置买喧,例如請求超時時間捻悯、連接超時時間、讀取超時時間岗喉、緩存目錄秋度、代理等等。
同樣钱床,OkHttpClient 也是通過內部類 Builder 模式,動態(tài)靈活的配置參數(shù)埠居,從而構建 OkHttpClient 實例查牌。默認初始化 OkHttpClient client = new OkHttpClient()
內部已經(jīng)幫我們配置了一系列參數(shù),我們需要自己配置參數(shù)的話滥壕,只需要通過內部類 Builder 實例纸颜,在調用 build()
方法之前,調用對應方法動態(tài)配置即可绎橘。OkHttpClient client = new OkHttpClient.Builder().build();
例如我們動態(tài)配置讀取時間為30秒(默認配置為10秒):
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
當配置完成之后胁孙,我們需要對特定的請求配置不同的參數(shù),也可以OkHttpClient.Builder builder = okHttpClient.newBuilder();
拿到 Builder 類實例進行"返廠重建"
當然了称鳞,還有很多參數(shù)可以配置涮较,具體可以查閱文檔 OkHttpClient參數(shù)配置
注意:在創(chuàng)建 OkHttpClient 時,應當保持全局單例冈止, 每一個 OkHttpClient 實例都保持著自己的連接池和線程池 狂票,重用連接池和線程池會節(jié)省一些內部資源,假如每次請求都去創(chuàng)建新的客戶端實例熙暴,就會造成一定的資源浪費
六闺属、創(chuàng)建 Call 對象慌盯,發(fā)起網(wǎng)絡請求
查閱 OkHttpClient 源碼,我們看到有個 newCall()
方法掂器,傳入 Request
實例亚皂,返回一個 Call
對象。我們暫且不用考慮它的原理国瓮,只知道可以用它來發(fā)起網(wǎng)絡請求孕讳,返回 Response
響應報文。點擊進去查看 Call 接口巍膘,可以看到有兩個方法是用來請求網(wǎng)絡的厂财。
-
execute()
:同步的網(wǎng)絡請求,返回Response
響應結果峡懈,由于同步請求會阻塞當前線程璃饱,我們使用過程還需要開啟子線程,所以一般較少使用肪康。 -
enqueue(Callback responseCallback)
:加入隊列荚恶,也就是異步網(wǎng)絡請求,傳入回調接口磷支,發(fā)起網(wǎng)絡請求時谒撼,請求成功會回掉Callback
對象的onResponse
方法,同時返回Response
響應報文雾狈,對結果進行處理廓潜。請求失敗則回調Callback對象的onFailure
方法,對失敗進行處理善榛。由于異步請求不會阻塞當前線程辩蛋,所以一般使用異步請求,注意:不管是成功還是失敗的回調方法都是在子線程移盆,不能直接進行 ui 操作悼院。 -
isCanceled()
方法用于判斷網(wǎng)絡請求是否取消 -
isExecuted()
方法用于判斷網(wǎng)絡請求是否已經(jīng)執(zhí)行 -
request()
返回發(fā)起該請求的Request
請求報文 -
clone()
方法克隆一個Call
對象
七、實際使用
至此咒循,已經(jīng)理清了 OkHttp 的關鍵類以及網(wǎng)絡請求步驟据途,現(xiàn)在對于 OkHttp 的基本使用已經(jīng)沒多大問題了增拥,下面舉例幾個實際場景使用切蟋,滴滴滴铝穷,開車~~~
別忘了添加網(wǎng)絡權限:
<uses-permission android:name="android.permission.INTERNET"/>
同步 GET 請求
private void sync_get() throws IOException {
//構建okHttp客戶端
OkHttpClient okHttpClient = new OkHttpClient();
//構建請求報文
Request request = new Request.Builder()
.url("http://www.baidu.com")//配置請求百度首頁地址
.build();
//發(fā)起同步請求劳翰,返回響應報文
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
//獲取Headers 請求頭對象
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
//分別獲取請求頭的name,value值
String headName = headers.name(i);
String headValue = headers.value(i);
}
//將響應體中的數(shù)據(jù)轉為 String ,讀取到內存
final String string = response.body().string();
} else {
//失敗處理
}
}
注意: 同步請求必須開啟子線程齐莲,我這里省略部分代碼
異步 GET 請求
private void async_get() {
//構建okHttp客戶端
OkHttpClient okHttpClient = new OkHttpClient();
//構建請求報文
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
//發(fā)起異步請求纤子,返回響應報文
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//失敗處理
Log.e(TAG, "onFailure: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + Thread.currentThread().getName());
//獲取Headers 請求頭對象
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
//分別獲取請求頭的name,value值
String headName = headers.name(i);
String headValue = headers.value(i);
}
//將響應體中的數(shù)據(jù)轉為 String ,讀取到內存
final String string = response.body().string();
}
});
}
異步請求不需要自己開啟子線程胶台,打印回調方法的線程名發(fā)現(xiàn)沥阳,成功或者失敗回調默認都是在子線程
//06-21 22:52:01.567 30162-30277/com.sunnada.okhttpdemo E/MainActivity: onResponse: OkHttp http://www.baidu.com/...
POST 上傳 json 字符串
public void async_post() {
//解析數(shù)據(jù)類型為json
MediaType MEDIA_TYPE_JSON
= MediaType.parse("application/json; charset=utf-8");
//構建OkHttp 客戶端
OkHttpClient okHttpClient = new OkHttpClient();
//構建json數(shù)據(jù)源
User user = new User("張少林", "123456");
String json = new Gson().toJson(user);
//初始化請求體
RequestBody requestBody = RequestBody.create(MEDIA_TYPE_JSON, json);
//初始化請求報文
Request request = new Request.Builder()
.url("http://192.168.1.104:8080/users/uploadJson")//配置服務端請求地址
.post(requestBody)//配置請求方法,傳入請求體
.build();
//發(fā)起異步請求哪痰,回調處理結果
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//失敗回調
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
Log.e(TAG, "onResponse: " + response.body().string());
}
});
}
POST 上傳文件
public void postFile() {
//獲取手機中的圖片文件
List<File> files = FileUtils.listFilesInDir(SDCardUtils.getSDCardPath() + "/Pictures/Screenshots", false);
File file = files.get(1);
String fileName = file.getName();
//構建OkHttp 客戶端
OkHttpClient okHttpClient = new OkHttpClient();
if (!file.exists()) {
ToastUtils.showLong("文件不存在");
} else {
//構建文件請求頭
RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
Request request = new Request.Builder()
.url("http://192.168.1.104:8080/upload")
.post(requestBody)
.build();
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
Log.e(TAG, "onResponse: " + response.body().string());
}
});
}
}
POST 提交表單
public void post_form() {
//構建okHttp客戶端
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("username", "張少林")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("http://192.168.1.104:8080/users/register")
.post(formBody)
.build();
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
Log.e(TAG, "onResponse: " + response.body().string());
}
});
}
POST 圖文上傳
public void post_img_title() {
//獲取手機中的圖片文件
List<File> files = FileUtils.listFilesInDir(SDCardUtils.getSDCardPath() + "/Pictures/Screenshots", false);
File file = files.get(1);
MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)//設置表單類型
.addFormDataPart("title", "Logo")//添加表單鍵值對
.addFormDataPart("image", "logo.png", RequestBody.create(MEDIA_TYPE_PNG, file))//添加表單文件
.build();
Request request = new Request.Builder()
.url("xxxxxxx")
.post(requestBody)
.build();
client.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//請求失敗處理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
Log.e(TAG, "onResponse: " + response.body().string());
}
});
}
文件下載遂赠,這里下載一張圖片
public void downloadImg(){
OkHttpClient client = new OkHttpClient();
final Request request = new Request.Builder()
.get()
.url("https://www.baidu.com/img/bd_logo1.png")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//拿到字節(jié)流
InputStream is = response.body().byteStream();
int len = 0;
File file = new File(Environment.getExternalStorageDirectory(), "image.png");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while ((len = is.read(buf)) != -1){
fos.write(buf, 0, len);
}
fos.flush();
//關閉流
fos.close();
is.close();
}
});
}
滴滴,到站了晌杰,下車~~~
最后
結語:雖說正式開發(fā)中跷睦,一般會使用基于 OkHttp 封裝一層的網(wǎng)絡庫,例如 Retrofit 2肋演,OkGo抑诸,底層還是使用OkHttp 做網(wǎng)絡請求的,個人覺得絕對有必要理清 OkHttp 的基本使用爹殊,有時間甚至可以自己封裝一個網(wǎng)絡框架出來也是個好的磨練蜕乡。通過這次的總結,加上看 OkHttp 的部分源碼梗夸,收獲不小层玲,最大的感悟就是不應該只會調用 api ,而應該經(jīng)常去看看流行框架的源碼反症,好的框架源碼是值得一看的辛块。計劃下次對 OkHttp 的緩存,以及攔截器做一下總結~
一點點學習總結铅碍,一點點思考润绵,如有不足還請指出,如果喜歡胞谈,小手一點給個贊~~
如需轉載請注明出處尘盼,謝謝~
http://www.reibang.com/u/52ccd7428abe
最后的最后,感謝樂于分享的大神呜魄,參考鏈接:
- OkHttp 3.8.0 API 文檔
-
http://blog.csdn.net/iispring/article/details/51661195
http://m.blog.csdn.net/qq_31694651/article/details/52254188 - http://www.reibang.com/p/ca8a982a116b
更多原創(chuàng)文章會在公眾號第一時間推送,歡迎掃碼關注 張少林同學