因為這個系列是屬于Android的基礎系列,是以并不會涉及到很深的内容層次。作為網絡這一個子產品而言,我們經曆了幾個階段。從最初的HttpClicent(已被廢棄)到後面的HttpUrlConnection,當然在HttpUrlConnection的基礎上出現了很多的網絡架構,諸如:Volley,XUtils等等。但是随着OkHttp的推出,原來的這些網絡架構都失去了色彩,OkHttp以其獨特的魅力迅速獲得了廣大開發者的喜愛,今天我們就來看看關于OkHttp的那些事。
OkHttp的基本特性
(1)支援HTTP 2.0,允許所有同一個主機位址的請求共享一個socket連結。
(2)連接配接池複用減少請求延時,同步避免了資源浪費。
(3)透明的GZIP壓縮減少響應資料的大小。
(4)自帶緩存機制,可以避免一些重複請求。
(5)多IP使用,當服務斷開後,自動切換備用IP位址,重新發起連接配接。
OkHttp的基本使用
添加依賴
// 網絡請求架構
// define a BOM and its version
api(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
// define any required OkHttp artifacts without version
api("com.squareup.okhttp3:okhttp")
api("com.squareup.okhttp3:logging-interceptor")
異步get請求
主要的步驟為:
(1)建立OkHttp執行個體。
(2)建構請求參數 預設為get()請求,可以不寫。
(3)建構請求。
(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。
/**
* 普通的Get請求
*/
fun okHttpGet(view: View) {
val url = "http://www.baidu.com"
// 1.建立OkHttp執行個體
val okHttpClient = OkHttpClient()
// 2.建構請求參數 預設為get()請求,可以不寫
val request = Request.Builder().url(url).get().build()
// 3.建構請求
val call = okHttpClient.newCall(request)
// 4.發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.i("okHttp", "請求失敗${e.printStackTrace()}")
}
override fun onResponse(call: Call, response: Response) {
Log.i(
"okHttp",
response.protocol.toString() + " " + response.code + " " + response.message
)
val headers = response.headers
for (i in 0 until headers.size) {
Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
}
Log.i("okHttp", "onResponse: " + response.body!!.string())
}
})
}
異步post請求
主要的步驟為:
(1)建立OkHttp執行個體。
(2)建構請求參數 預設為get()請求,可以不寫。
(3)建構請求。
(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。
/**
* Post方式送出String
*/
fun okHttpPost(view: View) {
val url = "http://www.5mins-sun.com:8081/user/direct_login"
//1.建構OkHttp執行個體
val okHttpClient = OkHttpClient()
//2.建構請求參數
val mediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
val map = mutableMapOf<String, Any>()
map["phone"] = "13701659446"
val requestBody = Gson().toJson(map)
val request = Request.Builder().url(url).post(requestBody.toRequestBody(mediaType)).build()
//3.建構請求
val call = okHttpClient.newCall(request)
//4.發送請求
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.i("okHttp", "請求失敗${e.printStackTrace()}")
}
override fun onResponse(call: Call, response: Response) {
Log.i(
"okHttp",
response.protocol.toString() + " " + response.code + " " + response.message
)
val headers = response.headers
for (i in 0 until headers.size) {
Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
}
Log.i("okHttp", "onResponse: " + response.body!!.string())
}
})
}
異步post送出流
/**
* Post方式送出流
*/
fun okHttpPostStream(view: View) {
val url = "http://www.5mins-sun.com:8081/user/direct_login"
//1.建構OkHttp執行個體
val okHttpClient = OkHttpClient()
//2.建構請求參數
val requestBody = object : RequestBody() {
override fun contentType(): MediaType? {
return "application/json; charset=utf-8".toMediaTypeOrNull()
}
override fun writeTo(sink: BufferedSink) {
val map = mutableMapOf<String, Any>()
map["phone"] = "13701659446"
val requestBody = Gson().toJson(map)
sink.writeUtf8(requestBody)
}
}
val request = Request.Builder().url(url).post(requestBody).build()
//3.建構請求
val call = okHttpClient.newCall(request)
//4.發送請求
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.i("okHttp", "請求失敗${e.printStackTrace()}")
}
override fun onResponse(call: Call, response: Response) {
Log.i(
"okHttp",
response.protocol.toString() + " " + response.code + " " + response.message
)
val headers = response.headers
for (i in 0 until headers.size) {
Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
}
Log.i("okHttp", "onResponse: " + response.body!!.string())
}
})
}
異步post送出檔案
/**
* Post送出檔案
*/
fun okHttpPostFile(view: View) {
val url = "http://www.5mins-sun.com:8081/manage/test_save_file_by_stream"
//1.建構OkHttp執行個體
val okHttpClient = OkHttpClient()
//2.建構請求參數
val file = File(Environment.getExternalStorageDirectory().absolutePath + "/zf.txt")
val mediaType = "application/octet-stream".toMediaTypeOrNull()
val request = Request.Builder().url(url).post(RequestBody.create(mediaType, file)).build()
//3.建構請求
val call = okHttpClient.newCall(request)
//4.發送請求
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.i("okHttp", "請求失敗${e.printStackTrace()}")
}
override fun onResponse(call: Call, response: Response) {
Log.i(
"okHttp",
response.protocol.toString() + " " + response.code + " " + response.message
)
val headers = response.headers
for (i in 0 until headers.size) {
Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
}
Log.i("okHttp", "onResponse: " + response.body!!.string())
}
})
}
異步post送出表單
fun okHttpPostForm(view: View) {
val url = "http://www.5mins-sun.com:8081/manage/test_save_file"
//1.建構OkHttp執行個體
val okHttpClient = OkHttpClient()
//2.建構請求參數
val file = File(Environment.getExternalStorageDirectory().absolutePath + "/zhoufn.txt")
val mediaType = "application/octet-stream".toMediaTypeOrNull()
val fileBody = RequestBody.create(mediaType, file)
val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("files", file.name, fileBody).build()
val request = Request.Builder().url(url).post(requestBody).build()
//3.建構請求
val call = okHttpClient.newCall(request)
//4.發送請求
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.i("okHttp", "請求失敗${e.printStackTrace()}")
}
override fun onResponse(call: Call, response: Response) {
Log.i(
"okHttp",
response.protocol.toString() + " " + response.code + " " + response.message
)
val headers = response.headers
for (i in 0 until headers.size) {
Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
}
Log.i("okHttp", "onResponse: " + response.body!!.string())
}
})
}
其實仔細觀察不難發現,無論是哪種方式互動,其大抵的步驟都是一樣的,即:
(1)建立OkHttp執行個體。
(2)建構請求參數 預設為get()請求,可以不寫。
(3)建構請求。
(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。
唯一的差別在于第2步建構請求參數的時候有所不同,當使用get請求方式的時候,無須設定RequestBody,而在使用post請求的時候,必須設定RequestBody,那麼,什麼是RequestBody呢?
RequestBody
在使用RequestBody的時候,我們一般是通過調用它的onCreate來拿到它的執行個體,先看下面這張圖:
注意看,RequestBody.create()有4個重載的方法,裡面的參數都不盡相同,其中第一個參數是MediaType,那麼,什麼是mediaType呢?
MediaType:表示要傳遞的資料的MIME類型,即你要傳遞的是什麼類型的東西。比如,需要傳遞的是json格式的字元串,你就可以設定為application/json; charset=utf-8;再比如,需要傳遞的是圖檔檔案,可以設定為image/png等等。假設類型設定不對,傳回碼會傳回為415。
接着看,除了MediaType,後面的參數主要有:File檔案,String字元串等等。當然,在實際的開發過程中,我們用到最多的也就是這兩個。其中File代表的是上傳的是檔案,String代表的是上傳的是字元串。而在上面的例子中,我們使用的是MultipartBody來對檔案進行上傳,如果我們僅僅隻想傳遞Map集合呢?其實對于這種key-value形式的資料,我們可以按照表單來傳遞,具體的寫法為:
val okHttpClient = OkHttpClient()
val requestBody: RequestBody = FormBody.Builder()
.add("key", "value")
.build()
val request: Request = Builder()
.url("url")
.post(requestBody)
.build()
OkHttp的攔截器
OkHttp的優秀之處還在于其強大的攔截器功能,先上一張圖
有沒有覺得這種模式很熟悉,其實這是Java中23種設計模式之一的責任鍊模式,從上到下鍊到底,一層嵌套着一層。其中最上層可選的為我們自定義的攔截器,在這裡我們可以監聽列印自己想要的東西,比如:
(1)給我們所有的網絡請求接口添加通用字段
public class HttpHeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
int userId = IOFactoryUtil.getIOFactoryUtil().getDefaultHandler().getInt("user_id", 0);
if (userId > 0) {
Request request = original.newBuilder()
.header("userID", String.valueOf(userId))
.build();
return chain.proceed(request);
}
return chain.proceed(original);
}
}
(2)列印我們所有的請求參數
public class HttpLoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.currentTimeMillis();
okhttp3.Response response = chain.proceed(chain.request());
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
okhttp3.MediaType mediaType = response.body().contentType();
String content = response.body().string();
Log.d("NetRequest", "請求位址:| " + request.toString());
printParams(request.body());
Log.d("NetRequest", "請求體傳回:| Response:" + content);
Log.d("NetRequest", "----------請求耗時:" + duration + "毫秒----------");
return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build();
}
private void printParams(RequestBody body) {
Buffer buffer = new Buffer();
try {
body.writeTo(buffer);
Charset charset = Charset.forName("UTF-8");
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(UTF_8);
}
String params = buffer.readString(charset);
Log.d("NetRequest", "請求參數: | " + params);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面隻是簡單的舉了兩個例子,當然還可以設定更多,具體根據自己的實際需求而定。
除了我們自定義的攔截器,系統還幫我們自動添加了一些攔截器,具體來說:
(1)RetryAndFollowUpInterceptor
處理重定向和錯誤的攔截器
(2)BridgeInterceptor
添加必要的請求頭資訊、gzip處理等。
(3)CacheInterceptor
緩存處理
(4)ConnectInterceptor
處理網絡連接配接
(5)CallServerInterceptor
通路伺服器
看到這裡,真心的不由得佩服OkHttp攔截器的設計之巧妙,通過多層不同作用的攔截器的使用,讓各個環節都能各司其職而且職責分明。
注意事項:
(1)OkHttp在全局盡量保持執行個體,這樣所有的請求都可以共享連接配接池、線程池和配置資訊。
(2)OkHttp的預設連接配接網絡時間、讀取資料時間、寫入資料時間為10s,我們可以手動重新設定時間,具體的方法為:readTimeout、connectTimeout、writeTimeout。
(3)onResponse回調的參數是response,一般情況下,比如我們希望獲得傳回的字元串,可以通過response.body().string()擷取;如果希望獲得傳回的二進制位元組數組,則調用response.body().bytes();如果你想拿到傳回的inputStream,則調用response.body().byteStream()。
(4)在okhttp3.Callback的回調方法裡面有個參數是Call 這個call可以單獨取消相應的請求,随便在onFailure或者onResponse方法内部執行call.cancel()都可以。如果想取消所有的請求,則可以okhttpclient.dispatcher().cancelAll();。