OkHttp用法详解
OkHttp的基本使用
1、首先在工程的app模塊下添加okhttp的依賴
implementation 'com.squareup.okhttp3:okhttp:3.12.0'同步GET請求
同步GET的意思是一直等待http請求, 直到返回了響應. 在這之間會阻塞進程, 所以通過get不能在Android的主線程中執行, 否則會報錯.
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);Headers responseHeaders = response.headers();for (int i = 0; i < responseHeaders.size(); i++) {System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));}System.out.println(response.body().string()); }OkHttpClient實現了Call.Factory接口, 是Call的工廠類, Call負責發送執行請求和讀取響應.
Request代表Http請求, 通過Request.Builder輔助類來構建.client.newCall(request)通過傳入一個http request, 返回一個Call調用. 然后執行execute()方法, 同步獲得
Response代表Http請求的響應. response.body()是ResponseBody類, 代表響應體, 可以通過responseBody.string()獲得字符串的表達形式, 或responseBody.bytes()獲得字節數組的表達形式, 這兩種形式都會把文檔加入到內存. 也可以通過responseBody.charStream()和responseBody.byteStream()返回流來處理.
上述代碼完成的功能是下載一個文件, 打印他的響應頭, 以string形式打印響應體.
響應體的string()方法對于小文檔來說十分方便高效. 但是如果響應體太大(超過1MB), 應避免使用 string()方法, 因為它會將把整個文檔加載到內存中.
對于超過1MB的響應body, 應使用流的方式來處理響應body. 這和我們處理xml文檔的邏輯是一致的, 小文件可以載入內存樹狀解析, 大文件就必須流式解析.
異步GET
異步GET是指在另外的工作線程中執行http請求, 請求時不會阻塞當前的線程, 所以可以在Android主線程中使用.
下面是在一個工作線程中下載文件, 當響應可讀時回調Callback接口. 當響應頭準備好后, 就會調用Callback接口, 所以讀取響應體時可能會阻塞. OkHttp現階段不提供異步api來接收響應體。
Post提交String
下面是使用HTTP POST提交請求到服務. 這個例子提交了一個json字符串到web服務. 因為整個請求體都在內存中, 因此避免使用此api提交大文檔(大于1MB)
OkHttpClient okHttpClient=new OkHttpClient();RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "{username:hyman,password:123}");Request.Builder builder = new Request.Builder();Request request = builder.url(mBaseUrl + "postString").post(requestBody).build();Call call = okHttpClient.newCall(request);//4.執行call//方法一Response response=call.execute();//匯拋出IO異常,同步方法//方法二,異步方法,放到隊列中,處于子線程中,無法更新UIcall.enqueue(new Callback() {//請求時失敗時調用@Overridepublic void onFailure(Call call, IOException e) {}//請求成功時調用@Overridepublic void onResponse(Call call, Response response) throws IOException {//處于子線程中,能夠進行大文件下載,但是無法更新UIfinal String res = response.body().string();//請求成功時返回的東西//InputStream is=response.body().byteStream();// 執行IO操作時,能夠下載很大的文件,并且不會占用很大內存/*** runOnUiThread方法切換到主線程中,或者用handler機制也可以*/runOnUiThread(new Runnable() {@Overridepublic void run() {// 更新ui}});}});Post提交流
以流的方式POST提交請求體. 請求體的內容由流寫入產生. 這個例子是流直接寫入Okio的BufferedSink. 你的程序可能會使用OutputStream, 你可以使用BufferedSink.outputStream()來獲取. OkHttp的底層對流和字節的操作都是基于Okio庫, Okio庫也是Square開發的另一個IO庫, 填補I/O和NIO的空缺, 目的是提供簡單便于使用的接口來操作IO.
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()); }Post提交文件
File file = new File(Environment.getExternalStorageDirectory(), "banner.png");if (!file.exists()) {return;}//1.拿到okHttpClient對象OkHttpClient okHttpClient = new OkHttpClient();//mime typeRequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);Request.Builder builder = new Request.Builder();Request request = builder.url(mBaseUrl + "postFile").post(requestBody).build();Call call = okHttpClient.newCall(request);//4.執行call//方法一Response response=call.execute();//匯拋出IO異常,同步方法//方法二,異步方法,放到隊列中,處于子線程中,無法更新UIcall.enqueue(new Callback() {//請求時失敗時調用@Overridepublic void onFailure(Call call, IOException e) {Log.d("報錯了嗎", "onFailure: 報了");e.printStackTrace();}//請求成功時調用@Overridepublic void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {//處于子線程中,能夠進行大文件下載,但是無法更新UILog.d("執行成功", "onResponse: 成功");final String res = response.body().string();//請求成功時返回的東西//InputStream is=response.body().byteStream();// 執行IO操作時,能夠下載很大的文件,并且不會占用很大內存/*** runOnUiThread方法切換到主線程中,或者用handler機制也可以*/runOnUiThread(new Runnable() {@Overridepublic void run() {// 更新ui}});}});Post提交表單
使用FormEncodingBuilder來構建和HTML標簽相同效果的請求體. 鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼.
//1.拿到okHttpClient對象OkHttpClient okHttpClient = new OkHttpClient();//2.構造Request請求對象//構造requestBody,get請求不需要FormBody.Builder requestBodyBuilder = new FormBody.Builder();//提交到服務器端的請求體RequestBody requestBody = requestBodyBuilder.add("username", "AI").add("password", "12345678").build();Request.Builder builder = new Request.Builder();Request request = builder.url(mBaseUrl + "login").post(requestBody).build();Call call = okHttpClient.newCall(request);//4.執行call//方法一Response response=call.execute();//匯拋出IO異常,同步方法//方法二,異步方法,放到隊列中,處于子線程中,無法更新UIcall.enqueue(new Callback() {//請求時失敗時調用@Overridepublic void onFailure(Call call, IOException e) {}//請求成功時調用@Overridepublic void onResponse(Call call, Response response) throws IOException {//處于子線程中,能夠進行大文件下載,但是無法更新UIfinal String res = response.body().string();//請求成功時返回的東西//InputStream is=response.body().byteStream();// 執行IO操作時,能夠下載很大的文件,并且不會占用很大內存/*** runOnUiThread方法切換到主線程中,或者用handler機制也可以*/runOnUiThread(new Runnable() {@Overridepublic void run() {// 更新ui}});}});Post提交分塊請求
MultipartBody.Builder可以構建復雜的請求體, 與HTML文件上傳形式兼容. 多塊請求體中每塊請求都是一個請求體, 可以定義自己的請求頭. 這些請求頭可以用來描述這塊請求, 例如它的Content-Disposition. 如果Content-Length和Content-Type可用的話, 他們會被自動添加到請求頭中.
File file = new File(Environment.getExternalStorageDirectory(), "uz_splash_bg.png");if (!file.exists()) {return;}OkHttpClient okHttpClient=new OkHttpClient();//mime typeMultipartBody.Builder multipartBuilder = new MultipartBody.Builder();RequestBody requestBody = multipartBuilder.setType(MultipartBody.FORM).addPart(Headers.of("Content-Disposition", "form-data; name=\"" + "username" + "\""),RequestBody.create(null, "hyman")).addPart(Headers.of("Content-Disposition", "form-data; name=\"" + "password" + "\""),RequestBody.create(null, "123")).addFormDataPart("mPhoto", "hyman.png", RequestBody.create(MediaType.parse("application/octet-stream"), file)).build();//mPhoto是File的一個域,value//顯示上傳進度,把post的中的requestBody改為countingRequestBody就行了/**CountingRequestBody countingRequestBody=new CountingRequestBody(requestBody, new CountingRequestBody.Listener() {@Override public void onRequestProgress(long byteWritten, long contentLength) {L.e(byteWritten+"/"+contentLength);}});*/Request.Builder builder = new Request.Builder();Request request = builder.url(mBaseUrl + "uploadInfo").post(requestBody).build();Call call = okHttpClient.newCall(request);//4.執行call//方法一Response response=call.execute();//匯拋出IO異常,同步方法//方法二,異步方法,放到隊列中,處于子線程中,無法更新UIcall.enqueue(new Callback() {//請求時失敗時調用@Overridepublic void onFailure(Call call, IOException e) {}//請求成功時調用@Overridepublic void onResponse(Call call, Response response) throws IOException {//處于子線程中,能夠進行大文件下載,但是無法更新UIfinal String res = response.body().string();//請求成功時返回的東西//InputStream is=response.body().byteStream();// 執行IO操作時,能夠下載很大的文件,并且不會占用很大內存/*** runOnUiThread方法切換到主線程中,或者用handler機制也可以*/runOnUiThread(new Runnable() {@Overridepublic void run() {// 更新ui}});}});設置響應頭和提取響應頭
典型的HTTP頭像是一個Map<String, String> : 每個字段都有一個或沒有值. 但是一些頭允許多個值, 像Guava的Multimap.
例如: HTTP響應里面提供的Vary響應頭, 就是多值的. OkHttp的api試圖讓這些情況都適用.
當寫請求頭的時候, 使用header(name, value)可以設置唯一的name、value. 如果已經有值, 舊的將被移除, 然后添加新的. 使用addHeader(name, value)可以添加多值(添加, 不移除已有的).
當讀取響應頭時, 使用header(name)返回最后出現的name、value. 通常情況這也是唯一的name、value. 如果沒有值, 那么header(name)將返回null. 如果想讀取字段對應的所有值, 使用headers(name)會返回一個list.
為了獲取所有的Header, Headers類支持按index訪問.
使用Gson來解析JSON響應
Gson是一個在JSON和Java對象之間轉換非常方便的api庫. 這里我們用Gson來解析Github API的JSON響應.
注意: ResponseBody.charStream()使用響應頭Content-Type指定的字符集來解析響應體. 默認是UTF-8.
響應緩存
為了緩存響應, 你需要一個你可以讀寫的緩存目錄, 和緩存大小的限制. 這個緩存目錄應該是私有的, 不信任的程序應不能讀取緩存內容.
一個緩存目錄同時擁有多個緩存訪問是錯誤的. 大多數程序只需要調用一次new OkHttp(), 在第一次調用時配置好緩存, 然后其他地方只需要調用這個實例就可以了. 否則兩個緩存示例互相干擾, 破壞響應緩存, 而且有可能會導致程序崩潰.
響應緩存使用HTTP頭作為配置. 你可以在請求頭中添加Cache-Control: max-stale=3600 , OkHttp緩存會支持. 你的服務通過響應頭確定響應緩存多長時間, 例如使用Cache-Control: max-age=9600.
如果需要阻值response使用緩存, 使用CacheControl.FORCE_NETWORK. 如果需要阻值response使用網絡, 使用CacheControl.FORCE_CACHE.
警告: 如果你使用FORCE_CACHE, 但是response要求使用網絡, OkHttp將會返回一個504 Unsatisfiable Request響應.
Force a Network Response
有些時候, 比如用戶剛剛點擊刷新按鈕, 這時必須跳過緩存, 直接從服務器抓取數據. 為了強制全面刷新, 我們需要添加no-cache指令:
connection.addRequestProperty("Cache-Control", "no-cache");這樣就可以強制每次請求直接發送給源服務器, 而不經過本地緩存版本的校驗, 常用于需要確認認證的應用和嚴格要求使用最新數據的應用.
Force a Cache Response
有時你會想立即顯示資源. 這樣即使在后臺正下載著最新資源, 你的客戶端仍然可以先顯示原有資源, 畢竟有個東西顯示比沒有東西顯示要好.
如果需要限制讓請求優先使用本地緩存資源, 需要增加only-if-cached指令:
取消一個Call
使用Call.cancel()可以立即停止掉一個正在執行的call. 如果一個線程正在寫請求或者讀響應, 將會引發IOException. 當call沒有必要的時候, 使用這個api可以節約網絡資源. 例如當用戶離開一個應用時, 不管同步還是異步的call都可以取消.
你可以通過tags來同時取消多個請求. 當你構建一請求時, 使用RequestBuilder.tag(tag)來分配一個標簽, 之后你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個tag的call.
超時
沒有響應時使用超時結束call. 沒有響應的原因可能是客戶點鏈接問題、服務器可用性問題或者這之間的其他東西. OkHttp支持連接超時, 讀取超時和寫入超時.
private final OkHttpClient client;public ConfigureTimeouts() throws Exception {client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();}public void run() throws Exception {Request request = new Request.Builder().url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay..build();Response response = client.newCall(request).execute();System.out.println("Response completed: " + response);}每個call的配置
使用OkHttpClient, 所有的HTTP Client配置包括代理設置、超時設置、緩存設置. 當你需要為單個call改變配置的時候, 調用OkHttpClient.newBuilder(). 這個api將會返回一個builder, 這個builder和原始的client共享相同的連接池, 分發器和配置.
下面的例子中,我們讓一個請求是500ms的超時、另一個是3000ms的超時。
每一個Call(其實現是RealCall)只能執行一次,否則會報異常,具體參見 RealCall#execute()
處理驗證
這部分和HTTP AUTH有關.
HTTP AUTH
使用HTTP AUTH需要在server端配置http auth信息, 其過程如下:
一次典型的訪問場景是:
OkHttp認證
OkHttp會自動重試未驗證的請求. 當響應是401 Not Authorized時,Authenticator會被要求提供證書. Authenticator的實現中需要建立一個新的包含證書的請求. 如果沒有證書可用, 返回null來跳過嘗試.
使用Response.challenges()來獲得任何authentication challenges的 schemes 和 realms. 當完成一個Basic challenge, 使用Credentials.basic(username, password)來解碼請求頭.
當認證無法工作時, 為了避免多次重試, 你可以返回空來放棄認證. 例如, 當exact credentials已經嘗試過, 你可能會直接想跳過認證, 可以這樣做:
if (credential.equals(response.request().header("Authorization"))) {return null; // If we already failed with these credentials, don't retry.}當重試次數超過定義的次數, 你若想跳過認證, 可以這樣做:
if (responseCount(response) >= 3) {return null; // If we've failed 3 times, give up.}private int responseCount(Response response) {int result = 1;while ((response = response.priorResponse()) != null) {result++;}return result;}攔截器-interceptor
OkHttp的攔截器鏈可謂是其整個框架的精髓,用戶可傳入的 interceptor 分為兩類:
- ①一類是全局的 interceptor,該類 interceptor 在整個攔截器鏈中最早被調用,通過OkHttpClient.Builder#addInterceptor(Interceptor) 傳入;
- ②另外一類是非網頁請求的 interceptor,這類攔截器只會在非網頁請求中被調用,并且是在組裝完請求之后,真正發起網絡請求前被調用,所有的 interceptor 被保存在List interceptors 集合中,按照添加順序來逐個調用,具體可參考RealCall#getResponseWithInterceptorChain() 方法。通過
OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 傳入;
這里舉一個簡單的例子,例如有這樣一個需求,我要監控App通過 OkHttp 發出的所有原始請求,以及整個請求所耗費的時間,針對這樣的需求就可以使用第一類全局的 interceptor 在攔截器鏈頭去做。
OkHttpClient okHttpClient = 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(); okHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.d(TAG, "onFailure: " + e.getMessage());}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody body = response.body();if (body != null) {Log.d(TAG, "onResponse: " + response.body().string());body.close();}} }); public class LoggingInterceptor implements Interceptor {private static final String TAG = "LoggingInterceptor";@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();long startTime = System.nanoTime();Log.d(TAG, String.format("Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers()));Response response = chain.proceed(request);long endTime = System.nanoTime();Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",response.request().url(), (endTime - startTime) / 1e6d, response.headers()));return response;} }針對這個請求,打印出來的結果
Sending request http://www.publicobject.com/helloworld.txt on null User-Agent: OkHttp ExampleReceived response for https://publicobject.com/helloworld.txt in 1265.9ms Server: nginx/1.10.0 (Ubuntu) Date: Wed, 28 Mar 2018 08:19:48 GMT Content-Type: text/plain Content-Length: 1759 Last-Modified: Tue, 27 May 2014 02:35:47 GMT Connection: keep-alive ETag: "5383fa03-6df" Accept-Ranges: bytes注意到一點是這個請求做了重定向,原始的 request url 是 http://www.publicobject.com/helloworld.tx,而響應的 request url 是 https://publicobject.com/helloworld.txt,這說明一定發生了重定向,但是做了幾次重定向其實我們這里是不知道的,要知道這些的話,可以使用 addNetworkInterceptor()去做。更多的關于 interceptor的使用以及它們各自的優缺點,請移步OkHttp Interceptors 攔截器
自定義dns服務
Okhttp默認情況下使用的是系統
其他
推薦讓 OkHttpClient 保持單例,用同一個 OkHttpClient 實例來執行你的所有請求,因為每一個 OkHttpClient 實例都擁有自己的連接池和線程池,重用這些資源可以減少延時和節省資源,如果為每個請求創建一個 OkHttpClient 實例,顯然就是一種資源的浪費。當然,也可以使用如下的方式來創建一個新的 OkHttpClient 實例,它們共享連接池、線程池和配置信息。
OkHttpClient eagerClient = client.newBuilder().readTimeout(500, TimeUnit.MILLISECONDS).build();Response response = eagerClient.newCall(request).execute();資源釋放
OkHttp中被掛起的線程和連接都將會在保持空閑時自動回收。如果我們想要主動釋放資源,可以使用如下方式,之后所有的Call對象執行請求都將被拒絕。
client.dispatcher().executorService().shutdown();清理連接池可以使用如下方式(連接池的守護線程可能不會立即退出):
client.connectionPool().evictAll();如果希望關閉緩存,可以使用如下方式:
client.cache().close();補充一點
MediaType媒體類型:決定瀏覽器將以什么形式、什么編碼對資源進行解析
Content-Type:也屬于MediaType媒體類型,主要用于在請求頭中指定資源的MediaType
一、MediaType類型
類型 描述
text/html HTML格式
text/plain 純文本格式,空格轉換為 “+” 加號,但不對特殊字符編碼
text/xml XML格式
text/x-markdown Markdown格式
image/gif gif圖片格式
image/jpeg jpg圖片格式
image/png png圖片格式
application/xhtml+xml XHTML格式
application/xml XML數據格式
application/json 用來告訴服務端,消息主體是序列化后的JSON字符串
application/pdf pdf格式
application/msword Word文檔格式
application/octet-stream 二進制流數據(如常見的文件下載)
application/x-www-form-urlencoded 參數為鍵值對形式,在發送前編碼所有字符(默認)。瀏覽器的原生 <form encType=”” 表單提交類型,如果不設置 enctype 屬性,那么最終就會以 application/x-www-form-urlencoded 方式提交數據
multipart/form-data 不對字符編碼,發送大量二進制數據或包含non-ASCII字符的文本,application/x-www-form-urlencoded是效率低下的(需要用更多字符表示一個non-ASCII字符)。需要設定“ <form enctype=‘multipart/form-data’”
二、MediaType對象解析
MediaType對象包含了三種信息:type 、subtype、charset,一般將這些信息傳入parse()方法中,這樣就可以解析出MediaType對象
例子1:
text/x-markdown; charset=utf-8type值是text,表示是文本這一大類;
/ 后面的x-markdown是subtype,表示是文本這一大類下的markdown這一小類;
charset=utf-8 則表示采用UTF-8編碼
參考文章
Android OkHttp完全解析 是時候來了解OkHttp了
OkHttp使用教程
okhttp3使用總結
Okhttp3基本使用
OkHttp使用完全教程
總結
以上是生活随笔為你收集整理的OkHttp用法详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qt 二维地图库_Qt轻量级地图解决方案
- 下一篇: Python爬虫 - 00.实现什么值得