OkHttp使用介绍 和 使用进阶
Android系統提供了兩種HTTP通信類,HttpURLConnection和HttpClient。
關于HttpURLConnection和HttpClient的選擇>>官方博客
盡管Google在大部分安卓版本中推薦使用HttpURLConnection,但是這個類相比HttpClient實在是太難用,太弱爆了。
OkHttp是一個相對成熟的解決方案,據說Android4.4的源碼中可以看到HttpURLConnection已經替換成OkHttp實現了。所以我們更有理由相信OkHttp的強大。
OkHttp 處理了很多網絡疑難雜癥:會從很多常用的連接問題中自動恢復。如果您的服務器配置了多個IP地址,當第一個IP連接失敗的時候,OkHttp會自動嘗試下一個IP。OkHttp還處理了代理服務器問題和SSL握手失敗問題。
使用 OkHttp 無需重寫您程序中的網絡代碼。OkHttp實現了幾乎和java.net.HttpURLConnection一樣的API。如果你用了?Apache?HttpClient,則OkHttp也提供了一個對應的okhttp-apache 模塊。
注:在國內使用OkHttp會因為這個問題導致部分酷派手機用戶無法聯網,所以對于大眾app來說,需要等待這個bug修復后再使用。或者嘗試使用OkHttp的老版本。
截止到目前,OkHttp一直沒有修復,并把修復計劃延遲到了OkHttp2.3中。不是所有設備都能重現,僅少量設備會出現這個問題。(如果問題這么明顯,OkHttp早就修復了)
入門
官方資料
官方介紹
github源碼
使用范圍
OkHttp支持Android 2.3及其以上版本。
對于Java, JDK1.7以上。
jar包準備
官方介紹頁面有鏈接位置。這里把下載鏈接也寫在下面。
OkHttp
Okio
基本使用
HTTP GET
| 1 2 3 4 5 6 7 8 | OkHttpClient?client?=?new?OkHttpClient(); String?run(String?url)?throws?IOException?{ ????Request?request?=?new?Request.Builder().url(url).build(); ????Response?response?=?client.newCall(request).execute();????if?(response.isSuccessful())?{????????return?response.body().string(); ????}?else?{????????throw?new?IOException("Unexpected?code?"?+?response); ????} } |
Request是OkHttp中訪問的請求,Builder是輔助類。Response即OkHttp中的響應。
Response類:
| 1 2 3 | public?boolean?isSuccessful() Returns?true?if?the?code?is?in?[200..300), ?which?means?the?request?was?successfully?received,?understood,?and?accepted. |
response.body()返回ResponseBody類
可以方便的獲取string
| 1 2 3 4 | public?final?String?string()?throws?IOException Returns?the?response?as?a?string?decoded?with?the?charset?of?the?Content-Type?header.?If?that?header?is?either?absent?or?lacks?a?charset, ?this?will?attempt?to?decode?the?response?body?as?UTF-8.Throws: IOException |
當然也能獲取到流的形式:
| 1 | public?final?InputStream?byteStream() |
HTTP POST
POST提交Json數據
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public?static?final?MediaType?JSON?=?MediaType.parse("application/json;?charset=utf-8"); OkHttpClient?client?=?new?OkHttpClient(); String?post(String?url,?String?json)?throws?IOException?{ ?????RequestBody?body?=?RequestBody.create(JSON,?json); ??????Request?request?=?new?Request.Builder() ??????.url(url) ??????.post(body) ??????.build(); ??????Response?response?=?client.newCall(request).execute(); ????f?(response.isSuccessful())?{ ????????return?response.body().string(); ????}?else?{ ????????throw?new?IOException("Unexpected?code?"?+?response); ????} } |
使用Request的post方法來提交請求體RequestBody
POST提交鍵值對
很多時候我們會需要通過POST方式把鍵值對數據傳送到服務器。 OkHttp提供了很方便的方式來做這件事情。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | OkHttpClient?client?=?new?OkHttpClient(); String?post(String?url,?String?json)?throws?IOException?{ ?????RequestBody?formBody?=?new?FormEncodingBuilder() ????.add("platform",?"android") ????.add("name",?"bug") ????.add("subject",?"XXXXXXXXXXXXXXX") ????.build(); ??????Request?request?=?new?Request.Builder() ??????.url(url) ??????.post(body) ??????.build(); ??????Response?response?=?client.newCall(request).execute(); ????if?(response.isSuccessful())?{ ????????return?response.body().string(); ????}?else?{ ????????throw?new?IOException("Unexpected?code?"?+?response); ????} } |
總結
通過上面的例子我們可以發現,OkHttp在很多時候使用都是很方便的,而且很多代碼也有重復,因此特地整理了下面的工具類。
注意:
-
OkHttp官方文檔并不建議我們創建多個OkHttpClient,因此全局使用一個。 如果有需要,可以使用clone方法,再進行自定義。這點在后面的高級教程里會提到。
-
enqueue為OkHttp提供的異步方法,入門教程中并沒有提到,后面的高級教程里會有解釋。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import?java.io.IOException; import?java.util.List; import?java.util.concurrent.TimeUnit; import?org.apache.http.client.utils.URLEncodedUtils; import?org.apache.http.message.BasicNameValuePair; import?cn.wiz.sdk.constant.WizConstant; import?com.squareup.okhttp.Callback; import?com.squareup.okhttp.OkHttpClient; import?com.squareup.okhttp.Request; import?com.squareup.okhttp.Response;? ?? public?class?OkHttpUtil?{ ????private?static?final?OkHttpClient?mOkHttpClient?=?new?OkHttpClient(); ????static{ ????????mOkHttpClient.setConnectTimeout(30,?TimeUnit.SECONDS); ????} ????/** ?????*?該不會開啟異步線程。 ?????*?@param?request ?????*?@return ?????*?@throws?IOException ?????*/ ????public?static?Response?execute(Request?request)?throws?IOException{ ????????return?mOkHttpClient.newCall(request).execute(); ????} ????/** ?????*?開啟異步線程訪問網絡 ?????*?@param?request ?????*?@param?responseCallback ?????*/ ????public?static?void?enqueue(Request?request,?Callback?responseCallback){ ????????mOkHttpClient.newCall(request).enqueue(responseCallback); ????} ????/** ?????*?開啟異步線程訪問網絡,?且不在意返回結果(實現空callback) ?????*?@param?request ?????*/ ????public?static?void?enqueue(Request?request){ ????????mOkHttpClient.newCall(request).enqueue(new?Callback()?{ ????????????? ????????????@Override ????????????public?void?onResponse(Response?arg0)?throws?IOException?{ ????????????????? ????????????} ????????????? ????????????@Override ????????????public?void?onFailure(Request?arg0,?IOException?arg1)?{ ????????????????? ????????????} ????????}); ????} ????public?static?String?getStringFromServer(String?url)?throws?IOException{ ????????Request?request?=?new?Request.Builder().url(url).build(); ????????Response?response?=?execute(request); ????????if?(response.isSuccessful())?{ ????????????String?responseUrl?=?response.body().string(); ????????????return?responseUrl; ????????}?else?{ ????????????throw?new?IOException("Unexpected?code?"?+?response); ????????} ????} ????private?static?final?String?CHARSET_NAME?=?"UTF-8"; ????/** ?????*?這里使用了HttpClinet的API。只是為了方便 ?????*?@param?params ?????*?@return ?????*/ ????public?static?String?formatParams(List<BasicNameValuePair>?params){ ????????return?URLEncodedUtils.format(params,?CHARSET_NAME); ????} ????/** ?????*?為HttpGet?的?url?方便的添加多個name?value?參數。 ?????*?@param?url ?????*?@param?params ?????*?@return ?????*/ ????public?static?String?attachHttpGetParams(String?url,?List<BasicNameValuePair>?params){ ????????return?url?+?"?"?+?formatParams(params); ????} ????/** ?????*?為HttpGet?的?url?方便的添加1個name?value?參數。 ?????*?@param?url ?????*?@param?name ?????*?@param?value ?????*?@return ?????*/ ????public?static?String?attachHttpGetParam(String?url,?String?name,?String?value){ ????????return?url?+?"?"?+?name?+?"="?+?value; ????} } |
高級
高級屬性其實用的不多,這里主要是對OkHttp github官方教程進行了翻譯。
同步get
下載一個文件,打印他的響應頭,以string形式打印響應體。
響應體的?string()?方法對于小文檔來說十分方便、高效。但是如果響應體太大(超過1MB),應避免適應?string()方法 ,因為他會將把整個文檔加載到內存中。
對于超過1MB的響應body,應使用流的方式來處理body。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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()); } |
異步get
在一個工作線程中下載文件,當響應可讀時回調Callback接口。讀取響應時會阻塞當前線程。OkHttp現階段不提供異步api來接收響應體。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????Request?request?=?new?Request.Builder() ????????.url("http://publicobject.com/helloworld.txt") ????????.build(); ????client.newCall(request).enqueue(new?Callback()?{ ??????@Override?public?void?onFailure(Request?request,?Throwable?throwable)?{ ????????throwable.printStackTrace(); ??????} ??????@Override?public?void?onResponse(Response?response)?throws?IOException?{ ????????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()); ??????} ????}); } |
提取響應頭
典型的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訪問。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????Request?request?=?new?Request.Builder() ????????.url("https://api.github.com/repos/square/okhttp/issues") ????????.header("User-Agent",?"OkHttp?Headers.java") ????????.addHeader("Accept",?"application/json;?q=0.5") ????????.addHeader("Accept",?"application/vnd.github.v3+json") ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println("Server:?"?+?response.header("Server")); ????System.out.println("Date:?"?+?response.header("Date")); ????System.out.println("Vary:?"?+?response.headers("Vary")); } |
Post方式提交String
使用HTTP POST提交請求到服務。這個例子提交了一個markdown文檔到web服務,以HTML方式渲染markdown。因為整個請求體都在內存中,因此避免使用此api提交大文檔(大于1MB)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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?{ ????String?postBody?=?"" ????????+?"Releases\n" ????????+?"--------\n" ????????+?"\n" ????????+?"?*?_1.0_?May?6,?2013\n" ????????+?"?*?_1.1_?June?15,?2013\n" ????????+?"?*?_1.2_?August?11,?2013\n"; ????Request?request?=?new?Request.Builder() ????????.url("https://api.github.com/markdown/raw") ????????.post(RequestBody.create(MEDIA_TYPE_MARKDOWN,?postBody)) ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println(response.body().string()); } |
Post方式提交流
以流的方式POST提交請求體。請求體的內容由流寫入產生。這個例子是流直接寫入Okio的BufferedSink。你的程序可能會使用OutputStream,你可以使用BufferedSink.outputStream()來獲取。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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方式提交文件
以文件作為請求體是十分簡單的。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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?{ ????File?file?=?new?File("README.md"); ????Request?request?=?new?Request.Builder() ????????.url("https://api.github.com/markdown/raw") ????????.post(RequestBody.create(MEDIA_TYPE_MARKDOWN,?file)) ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println(response.body().string()); } |
Post方式提交表單
使用FormEncodingBuilder來構建和HTML<form>標簽相同效果的請求體。鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????RequestBody?formBody?=?new?FormEncodingBuilder() ????????.add("search",?"Jurassic?Park") ????????.build(); ????Request?request?=?new?Request.Builder() ????????.url("https://en.wikipedia.org/w/index.php") ????????.post(formBody) ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println(response.body().string()); } |
Post方式提交分塊請求
MultipartBuilder可以構建復雜的請求體,與HTML文件上傳形式兼容。多塊請求體中每塊請求都是一個請求體,可以定義自己的請求頭。這些請求頭可以用來描述這塊請求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的話,他們會被自動添加到請求頭中。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | private?static?final?String?IMGUR_CLIENT_ID?=?"..."; private?static?final?MediaType?MEDIA_TYPE_PNG?=?MediaType.parse("image/png"); private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????//?Use?the?imgur?image?upload?API?as?documented?at?https://api.imgur.com/endpoints/image ????RequestBody?requestBody?=?new?MultipartBuilder() ????????.type(MultipartBuilder.FORM) ????????.addPart( ????????????Headers.of("Content-Disposition",?"form-data;?name=\"title\""), ????????????RequestBody.create(null,?"Square?Logo")) ????????.addPart( ????????????Headers.of("Content-Disposition",?"form-data;?name=\"image\""), ????????????RequestBody.create(MEDIA_TYPE_PNG,?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(requestBody) ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println(response.body().string()); } |
使用Gson來解析JSON響應
Gson是一個在JSON和Java對象之間轉換非常方便的api。這里我們用Gson來解析Github API的JSON響應。
注意:ResponseBody.charStream()使用響應頭Content-Type指定的字符集來解析響應體。默認是UTF-8。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private?final?OkHttpClient?client?=?new?OkHttpClient(); private?final?Gson?gson?=?new?Gson(); public?void?run()?throws?Exception?{ ????Request?request?=?new?Request.Builder() ????????.url("https://api.github.com/gists/c2a7c39532239ff261be") ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????Gist?gist?=?gson.fromJson(response.body().charStream(),?Gist.class); ????for?(Map.Entry<String,?GistFile>?entry?:?gist.files.entrySet())?{ ??????System.out.println(entry.getKey()); ??????System.out.println(entry.getValue().content); ????} } static?class?Gist?{ ????Map<String,?GistFile>?files; } static?class?GistFile?{ ????String?content; } |
響應緩存
為了緩存響應,你需要一個你可以讀寫的緩存目錄,和緩存大小的限制。這個緩存目錄應該是私有的,不信任的程序應不能讀取緩存內容。
一個緩存目錄同時擁有多個緩存訪問是錯誤的。大多數程序只需要調用一次new OkHttp(),在第一次調用時配置好緩存,然后其他地方只需要調用這個實例就可以了。否則兩個緩存示例互相干擾,破壞響應緩存,而且有可能會導致程序崩潰。
響應緩存使用HTTP頭作為配置。你可以在請求頭中添加Cache-Control: max-stale=3600?,OkHttp緩存會支持。你的服務通過響應頭確定響應緩存多長時間,例如使用Cache-Control: max-age=9600。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | private?final?OkHttpClient?client; public?CacheResponse(File?cacheDirectory)?throws?Exception?{ ????int?cacheSize?=?10?*?1024?*?1024;?//?10?MiB ????Cache?cache?=?new?Cache(cacheDirectory,?cacheSize); ????client?=?new?OkHttpClient(); ????client.setCache(cache); } public?void?run()?throws?Exception?{ ????Request?request?=?new?Request.Builder() ????????.url("http://publicobject.com/helloworld.txt") ????????.build(); ????Response?response1?=?client.newCall(request).execute(); ????if?(!response1.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response1); ????String?response1Body?=?response1.body().string(); ????System.out.println("Response?1?response:??????????"?+?response1); ????System.out.println("Response?1?cache?response:????"?+?response1.cacheResponse()); ????System.out.println("Response?1?network?response:??"?+?response1.networkResponse()); ????Response?response2?=?client.newCall(request).execute(); ????if?(!response2.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response2); ????String?response2Body?=?response2.body().string(); ????System.out.println("Response?2?response:??????????"?+?response2); ????System.out.println("Response?2?cache?response:????"?+?response2.cacheResponse()); ????System.out.println("Response?2?network?response:??"?+?response2.networkResponse()); ????System.out.println("Response?2?equals?Response?1??"?+?response1Body.equals(response2Body)); } |
擴展
在這一節還提到了下面一句:
There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.
我不是很懂cache,平時用到的也不多,所以把Google在Android Developers一段相關的解析放到這里吧。
Force a Network Response
In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:
connection.addRequestProperty("Cache-Control",?"no-cache");If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 instead:
connection.addRequestProperty("Cache-Control",?"max-age=0");Force a Cache Response
Sometimes you'll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:
| 1 2 3 4 5 6 7 8 | try?{ ?????connection.addRequestProperty("Cache-Control",?"only-if-cached"); ?????InputStream?cached?=?connection.getInputStream(); ?????//?the?resource?was?cached!?show?it ??catch?(FileNotFoundException?e)?{ ?????//?the?resource?was?not?cached ?} } |
This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:
| 1 | int?maxStale?=?60?*?60?*?24?*?28;?//?tolerate?4-weeks?staleconnection.addRequestProperty("Cache-Control",?"max-stale="?+?maxStale); |
以上信息來自:HttpResponseCache - Android SDK | Android Developers
取消一個Call
使用Call.cancel()可以立即停止掉一個正在執行的call。如果一個線程正在寫請求或者讀響應,將會引發IOException。當call沒有必要的時候,使用這個api可以節約網絡資源。例如當用戶離開一個應用時。不管同步還是異步的call都可以取消。
你可以通過tags來同時取消多個請求。當你構建一請求時,使用RequestBuilder.tag(tag)來分配一個標簽。之后你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個tag的call。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | private?final?ScheduledExecutorService?executor?=?Executors.newScheduledThreadPool(1); private?final?OkHttpClient?client?=?new?OkHttpClient(); 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(); ????final?long?startNanos?=?System.nanoTime(); ????final?Call?call?=?client.newCall(request); ????//?Schedule?a?job?to?cancel?the?call?in?1?second. ????executor.schedule(new?Runnable()?{ ??????@Override?public?void?run()?{ ????????System.out.printf("%.2f?Canceling?call.%n",?(System.nanoTime()?-?startNanos)?/?1e9f); ????????call.cancel(); ????????System.out.printf("%.2f?Canceled?call.%n",?(System.nanoTime()?-?startNanos)?/?1e9f); ??????} ????},?1,?TimeUnit.SECONDS); ????try?{ ??????System.out.printf("%.2f?Executing?call.%n",?(System.nanoTime()?-?startNanos)?/?1e9f); ??????Response?response?=?call.execute(); ??????System.out.printf("%.2f?Call?was?expected?to?fail,?but?completed:?%s%n", ??????????(System.nanoTime()?-?startNanos)?/?1e9f,?response); ????}?catch?(IOException?e)?{ ??????System.out.printf("%.2f?Call?failed?as?expected:?%s%n", ??????????(System.nanoTime()?-?startNanos)?/?1e9f,?e); ????} } |
超時
沒有響應時使用超時結束call。沒有響應的原因可能是客戶點鏈接問題、服務器可用性問題或者這之間的其他東西。OkHttp支持連接,讀取和寫入超時。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private?final?OkHttpClient?client; public?ConfigureTimeouts()?throws?Exception?{ ????client?=?new?OkHttpClient(); ????client.setConnectTimeout(10,?TimeUnit.SECONDS); ????client.setWriteTimeout(10,?TimeUnit.SECONDS); ????client.setReadTimeout(30,?TimeUnit.SECONDS); } 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改變配置的時候,clone 一個OkHttpClient。這個api將會返回一個淺拷貝(shallow copy),你可以用來單獨自定義。下面的例子中,我們讓一個請求是500ms的超時、另一個是3000ms的超時。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????Request?request?=?new?Request.Builder() ????????.url("http://httpbin.org/delay/1")?//?This?URL?is?served?with?a?1?second?delay. ????????.build(); ????try?{ ??????Response?response?=?client.clone()?//?Clone?to?make?a?customized?OkHttp?for?this?request. ??????????.setReadTimeout(500,?TimeUnit.MILLISECONDS) ??????????.newCall(request) ??????????.execute(); ??????System.out.println("Response?1?succeeded:?"?+?response); ????}?catch?(IOException?e)?{ ??????System.out.println("Response?1?failed:?"?+?e); ????} ????try?{ ??????Response?response?=?client.clone()?//?Clone?to?make?a?customized?OkHttp?for?this?request. ??????????.setReadTimeout(3000,?TimeUnit.MILLISECONDS) ??????????.newCall(request) ??????????.execute(); ??????System.out.println("Response?2?succeeded:?"?+?response); ????}?catch?(IOException?e)?{ ??????System.out.println("Response?2?failed:?"?+?e); ????} } |
處理驗證
這部分和HTTP AUTH有關。
相關資料:HTTP AUTH 那些事 - 王紹全的博客 - 博客頻道 - CSDN.NET
OkHttp會自動重試未驗證的請求。當響應是401 Not Authorized時,Authenticator會被要求提供證書。Authenticator的實現中需要建立一個新的包含證書的請求。如果沒有證書可用,返回null來跳過嘗試。
| 1 2 3 4 5 6 | public?List<Challenge>?challenges() Returns?the?authorization?challenges?appropriate?for?this?response's?code.? If?the?response?code?is?401?unauthorized,? this?returns?the?"WWW-Authenticate"?challenges. If?the?response?code?is?407?proxy?unauthorized,?this?returns?the?"Proxy-Authenticate"?challenges. Otherwise?this?returns?an?empty?list?of?challenges. |
當需要實現一個Basic?challenge, 使用Credentials.basic(username, password)來編碼請求頭。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private?final?OkHttpClient?client?=?new?OkHttpClient(); public?void?run()?throws?Exception?{ ????client.setAuthenticator(new?Authenticator()?{ ??????@Override?public?Request?authenticate(Proxy?proxy,?Response?response)?{ ????????System.out.println("Authenticating?for?response:?"?+?response); ????????System.out.println("Challenges:?"?+?response.challenges()); ????????String?credential?=?Credentials.basic("jesse",?"password1"); ????????return?response.request().newBuilder() ????????????.header("Authorization",?credential) ????????????.build(); ??????} ??????@Override?public?Request?authenticateProxy(Proxy?proxy,?Response?response)?{ ????????return?null;?//?Null?indicates?no?attempt?to?authenticate. ??????} ????}); ????Request?request?=?new?Request.Builder() ????????.url("http://publicobject.com/secrets/hellosecret.txt") ????????.build(); ????Response?response?=?client.newCall(request).execute(); ????if?(!response.isSuccessful())?throw?new?IOException("Unexpected?code?"?+?response); ????System.out.println(response.body().string()); } |
總結
以上是生活随笔為你收集整理的OkHttp使用介绍 和 使用进阶的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于Zipalign的介绍和使用方法
- 下一篇: 用SwipeBackLayout让act