轉(zhuǎn)載注明出處:簡書-十個雨點
上一篇中,我們學習了如何使用OkHttp進行Get和Post操作软啼,本篇將教你如何對網(wǎng)絡請求和應答進行更多定制。
Call call = new OkHttpClient().newCall(new Request.Builder().build());
Response response=call.execute();
上面一行代碼中展示了使用OkHttp發(fā)送一次請求涉及的類溉委,我們來一一解析点待。
OkHttpClient
OkHttpClient是OkHttp的核心功能的執(zhí)行者,可以通過
OkHttpClient client=new OkHttpClient();
來創(chuàng)建默認的OkHttpClient對象畴椰,也可以使用:
OkHttpClient client=new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) //設置連接超時
.readTimeout(60, TimeUnit.SECONDS) //設置讀超時
.writeTimeout(60,TimeUnit.SECONDS) //設置寫超時
.retryOnConnectionFailure(true) //是否自動重連
.build(); //構(gòu)建OkHttpClient對象
來構(gòu)建自定義的OkHttpClient 對象臊诊,上面代碼只給出了常用的設置項,其他設置項比較復雜斜脂,我們下篇再說抓艳。
使用OkHttpClient 的時候需要注意以下幾點:
- 最好只使用一個共享的OkHttpClient 實例,將所有的網(wǎng)絡請求都通過這個實例處理帚戳。因為每個OkHttpClient 實例都有自己的連接池和線程池玷或,重用這個實例能降低延時,減少內(nèi)存消耗片任,而重復創(chuàng)建新實例則會浪費資源偏友。
- OkHttpClient的線程池和連接池在空閑的時候會自動釋放,所以一般情況下不需要手動關閉对供,但是如果出現(xiàn)極端內(nèi)存不足的情況位他,可以使用以下代碼釋放內(nèi)存:
client.dispatcher().executorService().shutdown(); //清除并關閉線程池
client.connectionPool().evictAll(); //清除并關閉連接池
client.cache().close(); //清除cache
- 如果對一些請求需要特殊定制,可以使用
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
這樣創(chuàng)建的實例與原實例共享線程池产场、連接池和其他設置項鹅髓,只需進行少量配置就可以實現(xiàn)特殊需求。
Request
Request是網(wǎng)絡請求的對象涝动,其本身的構(gòu)造函數(shù)是private的迈勋,只能通過Request.Builder來構(gòu)建。下面代碼展示了常用的設置項醋粟。
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues") //設置訪問url
.get() //類似的有post靡菇、delete、patch米愿、head厦凤、put等方法,對應不同的網(wǎng)絡請求方法
.header("User-Agent", "OkHttp Headers.java") //設置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育苟,并設置新header
.addHeader("Accept", "application/vnd.github.v3+json")
.build(); //構(gòu)建request
RequestBody
在Request中使用post较鼓、patch等方法時,需要傳入一個RequestBody參數(shù),除了上一節(jié)講到的構(gòu)造方法外博烂,RequestBody還有兩個子類:FormBody和MultipartBody香椎。
FromBody用于提交表單鍵值對,其作用類似于HTML中的<form>標記禽篱。
RequestBody formBody = new FormBody.Builder() //提交表單鍵值對
.add("platform", "android") //添加鍵值對
.add("name", "XXX")
.add("subject", "Hello")
.addEncoded(URLEncoder.encode("詳細","GBK"), //添加已編碼的鍵值對
URLEncoder.encode("無","GBK"))
.add("描述","你好") //其實會自動編碼畜伐,但是無法控制編碼格式
.build();
使用MultipartBody.Builder可以構(gòu)建與HTML文件上傳格式兼容的復雜請求體。多塊請求體中每塊請求都是一個獨立的請求體躺率,都可以定義自己的請求頭玛界。這些請求頭應該用于描述對應的請求體,例如Content-Disposition悼吱,Content-Length慎框,和Content-Type會自動被添加到請求頭中。
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MediaType.parse("image/png"), new File("website/static/logo.png")))
.addFormDataPart("discription","beautiful")
.build();
了上面這些現(xiàn)成的類和方法以外后添,還可以用繼承RequestBody的方式自定義實現(xiàn)笨枯,比如下例所示是以流的方式POST提交RequestBody,其中BufferedSink是Okio的API遇西,可以使用BufferedSink.outputStream()來得到OutputStream猎醇。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
Call
Call對象表示一個已經(jīng)準備好可以執(zhí)行的請求,用這個對象可以查詢請求的執(zhí)行狀態(tài)努溃,或者取消當前請求。它具有以下方法:
Call call=client.newCall(request); //獲取Call對象
Response response=call.execute(); //同步執(zhí)行網(wǎng)絡請求阻问,不要在主線程執(zhí)行
call.enqueue(new Callback()); //異步執(zhí)行網(wǎng)絡請求
call.cancel(); //取消請求
call.isCanceled(); //查詢是否取消
call.isExecuted(); //查詢是否被執(zhí)行過
要注意的是梧税,每個Call對象只能執(zhí)行一次請求。如果想重復執(zhí)行相同的請求称近,可以:
Call reCall=client.newCall(call.request()); //獲取另一個相同配置的Call對象
Response
Response是網(wǎng)絡請求的結(jié)果下面是一些常用方法:
Response response=call.execute(); //獲取Response對象
response.code(); //請求的狀態(tài)碼
response.isSuccessful(); //如果狀態(tài)碼為[200..300)第队,則表明請求成功
Headers headers=response.headers(); //獲取響應頭
List<String> names=response.headers("name"); //獲取響應頭中的某個字段
ResponseBody body=response.body(); //獲取響應體
其中ResponseBody代表響應體,用于操作網(wǎng)絡請求返回的內(nèi)容刨秆。常用方法如下:
body.contentLength(); //body的長度
String content=body.string(); //以字符串形式解碼body
byte[] byteContent=body.bytes(); //以字節(jié)數(shù)組形式解碼body
InputStreamReader reader=body.charStream(); //將body以字符流的形式解碼
InputStream inputStream=body.byteStream(); //將body以字節(jié)流的形式解碼
ResponseBody還有一些注意事項:
- ResponseBody必須關閉凳谦,不然可能造成資源泄漏,你可以通過以下方法關閉ResponseBody,對同一個ResponseBody只要關閉一次就可以了衡未。
Response.close();
Response.body().close();
Response.body().source().close();
Response.body().charStream().close();
Response.body().byteString().close();
Response.body().bytes();
Response.body().string();
ResponseBody只能被消費一次尸执,也就是string(),bytes(),byteStream()或 charStream()方法只能調(diào)用其中一個。
如果ResponseBody中的數(shù)據(jù)很大缓醋,則不應該使用bytes() 或 string()方法如失,它們會將結(jié)果一次性讀入內(nèi)存,而應該使用byteStream()或 charStream()送粱,以流的方式讀取數(shù)據(jù)褪贵。