前言:不去追逐,永遠不會擁有。不往前走,永遠原地停留。不經歷風雨,怎麼見彩虹。
一、概述
OKHttp是處理網絡請求的開源框架,Andorid當前最火熱的網絡框架,Retrofit的底層也是OKHttp,用於替換HttpUrlConnection和Apache HttpClient(API23 6.0已經移除)。
概況起來說OKHttp是一款優秀HTTP框架,它支持GET和POST請求,支持Http的文件上傳和下載,支持加載圖片,支持下載文件透明的GZIP壓縮,支持響應緩存避免重複的網絡請求,支持使用連接池來降低響應延遲的問題。
OKHttp的優點:
1.支持HTTP2/SPDY,這使得對同一個主機發出的所有請求都可以共享相同的套接字連接。
2.如果HTTP2/SPDY不可用OkHttp,會使用連接池來複用連接以提高效率。
3.提供了對 GZIP 的默認支持來降低傳輸內容的大小
4.提供了對 HTTP 響應的緩存機制,可以避免不必要的網絡請求
5.當網絡出現問題時,OkHttp會自動重試一個主機的多個 IP 地址
二、基本的使用
2.1、配置工程
(1)首先在清單文件AndroidManifest.xml中添加網絡權限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
(2)在build.gradle文件中添加okhttp依賴:
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
2.2、使用步驟:
(1)創建OkHttpClient實例
(2)通過Builder輔助類構建Request請求對象
(3)OkHttpClient實例回調請求,得到Call對象
(4)同步/異步執行請求,獲取Response對象
2.3、方法詳解
(1)創建OkHttpClient實例
//方式一:創建OkHttpClient實例,使用默認構造函數,創建默認配置OkHttpClient(官方建議全局只有一個實例)
OkHttpClient okHttpClient = new OkHttpClient();
//方式二:通過new OkHttpClient.Builder() 一步步配置一個OkHttpClient實例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
//方式三:如果要求使用現有的實例,可以通過newBuilder().build()方法進行構造
OkHttpClient client = okHttpClient.newBuilder().build();
使用默認構造函數創建OkHttpClient實例,創建默認配置OkHttpClient(官方建議全局只有一個實例)。也可以通過new OkHttpClient.Builder() 一步步配置一個OkHttpClient實例,如果要求使用現有的實例,可以通過newBuilder().build()方法進行構造。
(2)超時設置
我們在創建OkHttpClient實例的時候也會設置相關的屬性,通過.Builder().build()的形式設置,比如超時時間設置:
//1.構建OkHttpClient實例
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(2, TimeUnit.SECONDS)//鏈接超時爲2秒,單位爲秒
.writeTimeout(2, TimeUnit.SECONDS)//寫入超時
.readTimeout(2, TimeUnit.SECONDS)//讀取超時
.build();
//2.通過Builder輔助類構建請求對象
final Request request = new Request.Builder()
.url("http://httpbin.org/delay/10")//URL地址
.build();//構建
//創建線程,在子線程中運行
new Thread(new Runnable() {
@Override
public void run() {
try {
//3.通過mOkHttpClient調用請求得到Call
final Call call = okHttpClient.newCall(request);
//4.執行同步請求,獲取響應體Response對象
Response response = call.execute();
Log.e(TAG, "請求(超時)==" + response);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "請求(超時)==" + e.toString());
}
}
}).start();
我這裏設置了請求10秒後才請求成功,請求超時時間爲2秒,讀寫超時時間爲2秒,網絡請求是耗時的請求操作,需要另外開子線程運行,拋出了超時異常,測試一下效果:
這裏引出了http請求的幾個重要的角色,Request是OKHttp的訪問請求,Builder是訪問輔助類,Response是OKHttp的請求響應
Request(請求):每一個HTTP請求中,都應該包含一個URL,一個GET或POST方法,以及Header和其他參數,還可以包含特定內容類型的數據流。
Responses(響應):響應則包含一個回覆代碼(200代表成功,404代表未找到),Header和定製可選的body。
另外可以根據response.code()獲取返回的狀態碼。OKHttp:簡單來說,通過OkHttpClient可以發送一個http請求,並且可以讀取該請求的響應,它是一個生產Call的工廠。收益於一個共享的響應緩存/線程池/複用的鏈接等因素,絕大多數應用使用一個OKHttpClient實例,便可滿足整個應用的Http請求
(3)HTTP頭部的設置和讀取
HTTP頭部的數據結構是Map<String,List<String>>類型,也就是說,對於每個HTTP的頭,可能有多個值,但是大部分的HTTP頭只有一個值,只有少部分HTTP頭允許多個值,至於name的取值說明請參考:HTTP頭部參數說明
OKHTTP的處理方式是:
- header(name, value) 來設置HTTP頭的唯一值, 如果請求中已經存在響應的信息那麼直接替換掉;
- addHeader(name, value) 來補充新值,如果請求頭中已經存在name的name-value, 那麼還會繼續添加,請求頭中便會存在多個name相同value不同的“鍵值對”;
- header(name, value) 讀取唯一值或多個值的最後一個值;
- headers(name) 獲取所有值。
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")//設置唯一值
.addHeader("Server", "application/json; q=0.5")//設置新值
.addHeader("Server", "application/vnd.github.v3+json")//設置新值
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求(HTTP頭)異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.e(TAG, "header:Date==" + response.header("Date"));
Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
Log.e(TAG, "headers:Server==" + response.headers("Server"));
Log.e(TAG, "headers:Vary==" + response.headers("Vary"));
Log.e(TAG, "Post請求(HTTP頭)異步響應Success==" + response.body().string());
}
});
在Request請求中通過.Builder().build()的形式設置url(請求地址),也可以設置該請求的頭部信息,response的body有很多種輸出方法,string()只是其中之一,注意是string()不是toString()。如果是下載文件就是response.body().bytes()。我們來看看打印出來的結果:
(4)GET請求(同步)
new Thread(new Runnable() {
@Override
public void run() {
//通過Builder輔助類構建請求對象
Request request = new Request.Builder()
.get()//get請求
.url("https://www.baidu.com")//請求地址
.build();//構建
try {
//通過mOkHttpClient調用請求得到Call
final Call call = mOkHttpClient.newCall(request);
//執行同步請求,獲取Response對象
Response response = call.execute();
if (response.isSuccessful()) {//如果請求成功
String string = response.body().string();
Log.e(TAG, "get同步請求success==" + string);
//響應體的string()對於小文檔來說十分方便高效,但是如果響應體太大(超過1M),應避免使用string()方法,
//因爲它會把整個文檔加載到內存中,對用超多1M的響應body,應該使用流的方式來處理。
//response.body().bytes();//字節數組類型
//response.body().byteStream();//字節流類型
//response.body().charStream();//字符流類型
printHeads(response.headers());
} else {
Log.e(TAG, "get同步請求failure==");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
/**
* 打印請求頭信息
*
* @param headers 請求頭集合
*/
private void printHeads(Headers headers) {
if (headers == null) return;
for (int i = 0; i < headers.size(); i++) {
Log.e(TAG, "請求頭==" + headers.name(i) + ":" + headers.value(i));
}
}
在請求Request中聲明爲GET請求,設置url請求地址,調用實例mOkHttpClient.new Call(request)調用請求,返回Call,通過同步方法call.execute()同步執行,這裏需要開啓一個子線程運行。需要手動通過response.isSuccessful()判斷請求是否成功,同時通過響應體得到Heander打印了請求頭的相關信息,看看得出的結果:
(5)GET請求(異步)
同步和異步不一樣的地方是Call對象調用的方法,call.enqueue(callback)實現函數回調成功和失敗兩方法,異步方法可以不用開啓子線程執行。
注意:同步是阻塞式的,串聯執行,同步發生在當前線程內。異步是併發式的,會再次創建子線程處理耗時操作。
//通過Builder輔助類構建Request對象,鏈式編程
Request request = new Request.Builder()
.url("https://www.baidu.com")
.get()
.build();
//異步
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
Log.e(TAG, "get異步響應失敗==" + e.toString());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//主線程中更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
//TODO 在主線程中更新UI的操作
}
});
Log.e(TAG, "get異步當前線程,線程id==" + Thread.currentThread().getId());
String result = response.body().string();
Log.e(TAG, "get異步響應成功==" + result);
printHeads(response.headers());
}
});
Log.e(TAG, "主線程,線程id==" + Thread.currentThread().getId());
打印log如下:
onFailure()和onResponse()分別是在請求失敗和成功時會調用的方法。這裏有個要注意的地方,onFailure()和onResponse()是在異步線程裏執行的,所以如果你在Android把更新UI的操作寫在這兩個方法裏面是會報錯的,這個時候可以用runOnUiThread這個方法進行更新UI操作。
(6)POST提交String請求(同步)
post請求創建request和get是一樣的,只是post請求需要提交一個表單,就是RequestBody。表單的格式有好多種,post請求提交參數需要構建RequestBody對象,post提交的過程需要將提交的內容封裝到一個RequestBody中,同時需要設置類型MediaType,MediaType用於描述Http請求和響應體的內容類型,也就是Content-Type,通過.Builder().build()的形式設置URL(請求地址),RequestBody(參數容器)。
//構建RequestBody對象,post提交的過程需要將提交的內容封裝到一個RequestBody中
//MediaType用於描述Http請求和響應體的內容類型,也就是Content-Type
MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
RequestBody requestBody = RequestBody.create("提交的內容", mediaType);
final Request request = new Request.Builder()
.post(requestBody)
.url("https://api.github.com/markdown/raw")
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
Log.e(TAG, "Post請求String同步響應success==" + response.body().string());
} else {
Log.e(TAG, "Post請求String同步響應failure==" + response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Post請求String同步響應failure==" + e.getMessage());
}
}
}).start();
請求結果如下:
RequestBody的數據格式都要指定Content-Type,常見的有三種:
- application/x-www-form-urlencoded 數據是個普通表單;
- multipart/form-data 數據裏有文件;
- application/json 數據是個json。
MediaType mediaType = MediaType.parse("image/png");
RequestBody requestBody = RequestBody.create(xxx.png, mediaType);
改變MediaType 中的內容即可設置不同的內容類型,比如image/png表示便攜式網絡圖形(Portable Network Graphics,PNG)是一種無損壓縮的位圖圖形格式,支持索引、灰度、RGB三種顏色方案以及Alpha通道等特性。
(7)POST提交String請求(異步)
RequestBody requestBody = RequestBody.create("提交內容", MediaType.parse("text/plain; charset=utf-8"));
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求String異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String string = response.body().string();
Log.e(TAG, "Post請求String異步響應success==" + string);
}
});
異步請求主要是有回調方法,和其他部分和同步差不多,我們來看看效果:
(8)POST提交鍵值對請求(異步)
請求參數提交鍵值對需要用到FormBody,FormBody繼承自RequestBody,通過.add("key", "value")形式添加:
//提交鍵值對需要用到FormBody,FormBody繼承自RequestBody
FormBody formBody = new FormBody.Builder()
//添加鍵值對(通多Key-value的形式添加鍵值對參數)
.add("key", "value")
.build();
final Request request = new Request.Builder()
.post(formBody)
.url("url")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求(鍵值對)異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post請求(鍵值對)異步響應Success==" + result);
}
});
(9)POST提交文件請求(異步)
post提交文件,將文件傳入RequestBody中即可:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
Request request = new Request.Builder()
.post(requestBody)
.url("https://api.github.com/markdown/raw")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求(文件)異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post請求(文件)異步響應Success==" + result);
}
請求效果如下:源碼中有例子
(10)POST提交表單請求(異步)
使用FormEncodingBuilder來構建和HTML標籤相同效果的請求體,鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼
//使用FormEncodingBuilder來構建和HTML標籤相同效果的請求體,鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼
FormBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求(表單)異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post請求(表單)異步響應Success==" + result);
}
});
(11)POST提交流請求(同步)
以流的方式Post提交請求體,請求體的內容由流寫入產生,這裏是流直接寫入OKIO的BufferedSink。你的程序可能會使用OutputStream, 你可以使用BufferedSink.outputStream()來獲取,這裏需要重寫RequestBody中的幾個方法,將本地數據放入到Http協議的請求體中,然後發送到服務器。
//以流的方式Post提交請求體,請求體的內容由流寫入產生,這裏是流直接寫入OKIO的BufferedSink。
// 你的程序可能會使用OutputStream, 你可以使用BufferedSink.outputStream()來獲取
final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
//重寫RequestBody中的幾個方法,將本地數據放入到Http協議的請求體中,然後發送到服務器
final RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
//返回內容類型
return mediaType;
}
@Override
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
//輸入數據頭
bufferedSink.writeUtf8("Numbers\\n");
bufferedSink.writeUtf8("-------\\n");
//構造數據
for (int i = 2; i < 997; i++) {
bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
};
//構建請求
final Request request = new Request.Builder().
url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
//開啓線程
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
String result = response.body().toString();
Log.e(TAG, "Post請求(流)異步響應Success==" + result);
} else {
Log.e(TAG, "Post請求(流)異步響應failure==" + response);
throw new IOException("Unexpected code " + response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) {
return factor(x) + "x" + i;
}
}
return Integer.toString(n);
}
請求結果如下:
(12)POST提交分塊請求(異步)
MultipartBuilder可以構建複雜的請求體,與HTML文件上傳形式兼容,多塊請求頭中的每個請求體都是一個親求體,可以定義自己的請求體,這些請求體可以用來描述這塊請求,例如他的Content-Disposition,如果Content-Length和Content-Type可用的話,他們會被自動添加到請求頭中。
MediaType mediaType = MediaType.parse("image/png");
String IMGUR_CLIENT_ID = "...";
//構建body
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
new File("website/static/logo-square.png")))
.build();
//構建請求
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(multipartBody)
.build();
//執行請求
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post請求(分塊)異步響應failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post請求(分塊)異步響應Success==" + result);
}
});
請求結果如下:
如果你上傳一個文件不是一張圖片,但是MediaType.parse("image/png")裏的"image/png"不知道該填什麼,可以參考下這個頁面。
至此,本文結束!
源碼地址:https://github.com/FollowExcellence/Rxjava_Retrofit
轉載請聲明出處:https://mp.csdn.net/postedit/101029208 謝謝!
相關文章:
- Retrofit2的介紹和簡單使用
- OKHttp3的用法介紹和解析
- 從源碼角度解釋OKHttp3的關鍵流程和重要操作
- 詳細介紹了RxJava的使用(基本創建、快速創建、延遲創建等操作符)
- RxJava轉換、組合、合併等操作符的使用
- RxJava延遲、do相關、錯誤處理等操作符的使用
- RxJava過濾、其他操作符的使用
上述幾篇都是android開發必須掌握的,後續會完善其他部分!