OkHttp不需要多介紹了,已經是網絡框架界的大佬了,很多網絡框架都基於OkHttp封裝,也有很多涉及到網絡的第三方框架都可以支持使用OkHttp替換網絡。
OkHttp的4.0.x版本已經全部由java
替換到了Kotlin
,API的一些使用也會有些不同,具體的參考Upgrading to OkHttp 4
由於不熟悉Kotlin代碼,本文使用的OkHttp的版本爲
3.14.2
,是3.14.x的最後一個版本
接入
OkHttp在3.13.x
以上的版本需要在Android 5.0+ (API level 21+)和Java 1.8的環境開發。
同時還需要再添加Okio的依賴庫,而Okio在1.x
版本是基於Java實現的,2.x
則是Kotlin實現的。
dependencies {
//...
//OkHttp
implementation 'com.squareup.okhttp3:okhttp:3.14.2'
implementation 'com.squareup.okio:okio:1.17.4'
}
3.12.x
以及以下的版本支持Android 2.3+ (API level 9+)和Java 1.7的開發環境
Get請求
請求分爲同步請求和異步請求,先看看同步請求
public void getSyn(final String url) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//創建OkHttpClient對象
OkHttpClient client = new OkHttpClient();
//創建Request
Request request = new Request.Builder()
.url(url)//訪問連接
.get()
.build();
//創建Call對象
Call call = client.newCall(request);
//通過execute()方法獲得請求響應的Response對象
Response response = call.execute();
if (response.isSuccessful()) {
//處理網絡請求的響應,處理UI需要在UI線程中處理
//...
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
這就是一段同步Get請求的代碼,同步網絡請求需要在子線程中執行,而處理UI需要回到UI線程中處理。
在看看Get的異步請求,這時就不需要自己創建子線程了,但是處理UI同樣需要在UI線程中處理,不能再請求響應的回調方法中處理
public void getAsyn(String url) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).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 {
if(response.isSuccessful()){
String result = response.body().string();
//處理UI需要切換到UI線程處理
}
}
});
}
Request.Builder
中默認的使用Get請求,所以可以不調用get()
方法
看了兩種不同的Get請求,基本流程都是先創建一個OkHttpClient
對象,然後通過Request.Builder()
創建一個Request
對象,OkHttpClient
對象調用newCall()
並傳入Request
對象就能獲得一個Call
對象。而同步和異步不同的地方在於execute()
和enqueue()
方法的調用,調用execute()
爲同步請求並返回Response
對象;調用enqueue()
方法測試通過callback的形式返回Response
對象。
注意:無論是同步還是異步請求,接收到
Response
對象時均在子線程中,其中通過Response
對象獲取請求結果需要在子線程中完成,在得到結果後再切換到UI線程改變UI
Post請求
Post請求與Get請求不同的地方在於Request.Builder
的post()
方法,post()
方法需要一個RequestBody
的對象作爲參數
public void post(String url,String key,String value){
OkHttpClient client = new OkHttpClient();
FormBody body = new FormBody.Builder()
.add(key,value)
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.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 {
if(response.isSuccessful()){
String result = response.body().string();
//處理UI需要切換到UI線程處理
}
}
});
}
RequestBody
是一個抽象類,分別有FormBody
和MultipartBody
兩個子類,上面這個例子使用的是FormBody
,用於傳輸表單類型的參數。MultipartBody
則支持多類型的參數傳遞,例如:在傳輸表單類型的參數的同時,還是可以傳輸文件。創建一個MultipartBody
對象再調用post()
方法就OK了。
MultipartBody body = new MultipartBody.Builder()
// 添加表單參數
// .addFormDataPart(key,value)
.addFormDataPart(name, fileName, RequestBody.create(MediaType.get("application/octet-stream"), file))
.build();
RequestBody
前面的代碼可以看到使用了RequestBody.create()
方法,改方法的返回值也是一個RequestBody
對象。
其實Post請求就是包含了RequestBody
對象,MultipartBody
則是可以支持多中以及多個RequestBody
對象。
RequestBody
提供了5個重載的create()
靜態方法,如下圖
如果只需要上傳文件,請求就比較簡單了,如下:
public void uploadFile(String url, File file) {
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(MediaType.get("application/octet-stream"), file);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Call call = client.newCall(request);
//call.enqueue();
//...
}
這個MediaType.get()
的傳值是"application/octet-stream"
,這是二進制流傳輸,在不知道文件類型的情況下可以這麼操作,具體的傳參可以參考Content-Type的類型使用對照表
PS:如果要監聽文件的上傳進度就沒這麼簡單了
這是生成一個上傳JSON的RequestBody
對象的代碼
MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
String jsonStr = "{\"username\":\"Sia\"}";//json數據.
RequestBody body = RequestBody.create(jsonType, josnStr);
設置超時時間
OkHttp可以設置調用、連接和讀寫的超時時間,都是通過OkHttpClient.Builder
設置的。如果不主動設置,OkHttp將使用默認的超時設置。
OkHttpClient mClient = new OkHttpClient.Builder()
.callTimeout(6_000, TimeUnit.MILLISECONDS)
.connectTimeout(6_000, TimeUnit.MILLISECONDS)
.readTimeout(20_000, TimeUnit.MILLISECONDS)
.writeTimeout(20_000, TimeUnit.MILLISECONDS)
.build();
設置請求Header
請求的Header是通過Request.Builder
對象的相關方法來維護的,如下:
- headers(Headers headers)
- header(String name, String value)
- addHeader(String name, String value)
- removeHeader(String name)
addHeader
和removeHeader
方法比較好理解,分別是添加和移除header信息。header(String name, String value)
這是會重新設置指定name
的header信息。
headers(Headers headers)
則是會移除掉原有的所有header信息,將參數headers
的header信息添加到請求中。這是這幾個方法的一些差別。
使用的話都是Builder模式的鏈式調用,舉個栗子
Request request = new Request.Builder()
.header("Accept","image/webp")
.addHeader("Charset","UTF-8")
.url(url)
.build();
Cookie也是header信息中的一個字段,通過Header相關方法添加就好了
請求部分的基礎使用基本上就這些了,具體的一些用法可以參考官方文檔https://square.github.io/okhttp/recipes/
Interceptors(攔截器)
攔截器是OkHttp當中一個比較強大的機制,可以監視、重寫和重試調用請求。
這是一個比較簡單的Interceptor
的實現,對請求的發送和響應進行了一些信息輸出。
class LoggingInterceptor implements Interceptor {
public static final String TAG = "Http_log";
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.i(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
需要實現其中intercept(Interceptor.Chain chain)
方法,同時必須調用chain.proceed(request)
代碼,也就是網絡請求真正發生的地方。
攔截器可以設置多個,並且攔截器的調用是有順序的。官網舉的例子是,同時添加一個壓縮攔截器和一個校驗攔截器,需要決定數據是先被壓縮在校驗,還是先校驗在壓縮。
攔截器還分爲應用攔截器(Application Interceptors)和網絡攔截器(Network Interceptors)
上圖可以看出應用攔截器是處於應用和OkHttp核心之間,而網絡攔截器則是在OkHttp核心與網絡之間,這裏就直接搬運官網的示例,使用LoggingInterceptor
來看一下兩種攔截器的差異。
OkHttp自身有5個Interceptor的實現,有興趣可以閱讀源碼
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- CallServerInterceptor
Application Interceptors
先看看應用攔截器,通過OkHttpClient.Builder
的addInterceptor
方法添加攔截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
看請求和響應的兩個鏈接是不同的,URL http://www.publicobject.com/helloworld.txt
會重定向到 https://publicobject.com/helloworld.txt
,OkHttp會自動跟隨重定向,而應用攔截器只被調用一次,並且chain.proceed()
返回的Response
對象是具有重定向響應對象。
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
Network Interceptors
再來看看網絡攔截器,通過OkHttpClient.Builder
的addNetworkInterceptor
方法添加攔截器
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
結果日誌:
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
從日誌來看,攔截器運行了兩次,第一次請求了http://www.publicobject.com/helloworld.txt
,第二次則是重定向到https://publicobject.com/helloworld.txt
。同時通過網絡攔截能獲得更多的header信息。更多的關於Interceptor
的使用以及它們各自的優缺點,請參考OkHttp官方說明文檔。
其他
OkHttp已經出來很久了,本文只是爲了完成自己的執念吧!還有Events的使用沒有寫出來,這個東西平常也沒用過,後續研究了會補充上。