前言:OkHttp是Square開發的第三方庫,用于發送和接收基于HTTP的網絡請求。它建立在Okio庫之上,通過創建共享內存池,它嘗試通過標準Java I / O庫更高效地讀取和寫入數據。它還是Retrofit庫的底層庫,為使用基于REST的API提供類型安全性。Square公司是不是看著很 眼紅 (眼熟),是的沒錯,這家公司在開源的道路上做足了貢獻,造福了無數程序員。除了OkHttp外,還有Picasso、Retrofit、otto等著名的開源項目。目前OkHttp最新版本是3.x,支持Android 2.3+,所以以下所講內容都是基于OkHttp3.x。
OkHttp項目開源地址 :https://github.com/square/okhttp
基本使用
●配置與導入
在AndroidManifest.xml文件中打開了聯網的權限:
<uses-permission android:name="android.permission.INTERNET"/>
在Android Studio 中配置gradle:
compile 'com.squareup.okhttp3:okhttp:3.5.0'
●發送和接收網絡請求
實例化一個OkHttpClient并創建一個Request對象。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
如果有任何需要添加的查詢參數,OkHttp提供的HttpUrl類可以用來構造URL:
HttpUrl.Builder urlBuilder = HttpUrl.parse("http://blog.csdn.net/donkor_").newBuilder();
//addQueryParameter 添加查詢參數
urlBuilder.addQueryParameter("name", "donkor");
urlBuilder.addQueryParameter("blog", "okhttp3");
urlBuilder.addQueryParameter("number", "8");
String url = urlBuilder.build().toString();
Request request = new Request.Builder()
.url(url)
.build();
●同步Get
因為Android不允許主線程上的網絡調用,所以只能在單獨的線程或后臺服務上進行同步調用。
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
//newCall方法會得到一個Call對象,表示一個新的網絡請求
//execute方法是同步方法,會阻塞當前線程,并返回Response對象
okhttp3.Response response = client.newCall(request).execute();
String data=response.body().string();
if (response.isSuccessful()) {
Log.e("asd","okHttp is request success");
} else {
Log.e("asd", "okHttp is request error");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
※ 注
:通過Response對象的body()方法可以得到響應體ResponseBody對象,調用其string()方法可以很方便地將響應體中的數據轉換為字符串,該方法會將所有的數據放入到內存之中,所以如果數據超過1M,最好不要調用string()方法以避免占用過多內存,這種情況下可以考慮將數據當做Stream流處理。
●異步Get
//enqueue方法調用異步請求網絡,該方法接收一個okhttp3.Callback對象,
// 且不會阻塞當前線程,會新開一個工作線程,讓實際的網絡請求在工作線程中執行。
//當異步請求成功后,會回調Callback對象的onResponse方法,在該方法中可以獲取Response對象。
// 當異步請求失敗或者調用了Call對象的cancel方法時,會回調Callback對象的onFailure方法。
// onResponse和onFailure這兩個方法都是在工作線程中執行的。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("asd", "okHttp is request erro");
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("asd", "okHttp is request success");
String data=response.body().string();
//在主線程中進行UI修改操作
runOnUiThread(new Runnable() {
@Override
public void run() {
//do something
}
});
}
});
●Post方式發送String
使用HTTP POST提交請求到服務。這個例子提交了一個markdown文檔到web服務,以HTML方式渲染markdown。因為請求體會放置在內存中,所以應該避免用該API發送超過1M的數據。
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";
//post方法接收一個RequestBody對象
//create方法第一個參數都是MediaType類型,create方法的第二個參數可以是String、File、byte[]或okio.ByteString
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方式發送Stream流
這里我們將請求主體作為流。 請求體的內容由流寫入產生。 此示例直接流入Okio緩沖接收器。 您的程序可能更喜歡OutputStream,您可以從BufferedSink.outputStream()獲取。
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() {
//重寫contentType()方法,返回markdown類型的MediaType
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
//重寫writeTo()方法,該方法會傳入一個Okia的BufferedSink類型的對象,
//可以通過BufferedSink的各種write方法向其寫入各種類型的數據,
//此例中用其writeUtf8方法向其中寫入UTF-8的文本數據。
//也可以通過它的outputStream()方法,得到輸出流OutputStream,
//從而通過OutputSteram向BufferedSink寫入數據。
@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
上傳文件在實際開發中也經常用到,這里比較簡單,直接看代碼:
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 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方式Form表單中的鍵值對
使用FormBody.Builder來構建類似于HTML <form>標簽的請求體。鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody 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();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
●響應緩存
要想緩存響應,就需要配置一個可以讀寫的緩存目錄,以及緩存大小的限制。 并且緩存目錄應該是私有的,不受信任的應用程序不應該能夠讀取其內容!
一個緩存目錄同時被多個緩存訪問是錯誤的。 大多數應用程序應該只需調用一次OkHttpClient(),并配置它的緩存,之后只需要調用這個實例即可。 否則,兩個緩存實例會互相干擾,破壞響應緩存,并可能導致應用程序崩潰。
private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
//設置緩存上限為10M
int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(cacheDirectory, cacheSize);
//new OkHttpClient只實例化一次,避免多個緩存實例互相干擾
client = new OkHttpClient.Builder()
.cache(cache)
.build();
}
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);
//對于同一個url地址,第一次獲得緩存數據 為null
System.out.println("Response 1 cache response: " + response1.cacheResponse());
//對于同一個url地址,第一次獲得請求數據 不為null
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);
//對于同一個url地址,第二次獲得緩存數據 不為null
System.out.println("Response 2 cache response: " + response2.cacheResponse());
//對于同一個url地址,第二次獲得請求數據 為null
System.out.println("Response 2 network response: " + response2.networkResponse());
System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
}
※ 注
:如果想讓某次請求禁用緩存,可以調用request.cacheControl(CacheControl.FORCE_NETWORK)方法,這樣即便緩存目錄有對應的緩存,也會忽略緩存,強制發送網絡請求,這對于獲取最新的響應結果很有用。如果想強制某次請求使用緩存的結果,可以調用request.cacheControl(CacheControl.FORCE_CACHE),這樣不會發送實際的網絡請求,而是讀取緩存,即便緩存數據過期了,也會強制使用該緩存作為響應數據,如果緩存不存在,那么就返回504 Unsatisfiable Request錯誤。
●取消請求Call
當請求不再需要的時候,我們應該中止請求,比如退出當前的Activity了,那么在Activity中發出的請求應該被中止。可以通過調用Call的cancel方法立即中止請求,如果線程正在寫入Request或讀取Response,那么會拋出IOException異常。使用這個api可以節約網絡資源。同步請求和異步請求都可以被取消。
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.
//客戶端發出請求1秒之后,請求還未完成,這時候通過cancel方法中止了Call,請求中斷,并觸發IOException異常
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支持連接,讀取和寫入超時。
private final OkHttpClient client;
public ConfigureTimeouts() throws Exception {
//connectTimeout方法設置客戶端和服務器建立連接的超時時間
//writeTimeout方法設置客戶端上傳數據到服務器的超時時間
//readTimeout方法設置客戶端從服務器下載響應數據的超時時間
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);
}
實例:Gson處理復雜JSON字符串,并獲取天氣情況:
1 . 在使用免費的天氣預報api,這里用的是mob平臺提供的,有需要的直接在下面鏈接注冊使用,這里不再過多贅述。
http://api.mob.com/#/apiwiki/weather
2 . mob平臺獲取天氣狀況請求方式是get,這里我們使用同步與異步get分別進行請求
3 . 請求成功,返回的數據是json格式,所以我們使用Gson進行解析,并顯示在文本上
4 . 配置gradle與請求網絡權限
compile files('libs/gson-2.8.0.jar')
compile files('libs/okhttp-3.5.0.jar')
compile files('libs/okio-1.11.0.jar')
<uses-permission android:name="android.permission.INTERNET"/>
5 . 添加請求天氣預報的url地址,這里我們以深圳為例
//url為請求地址 key=19d6b7c760314 key為注冊應用成功后獲得
private final String url = "http://apicloud.mob.com/v1/weather/query?key=19d6b7c760314&city=深圳";
6 . 初始化Gson與OkHttpClient
private Gson gson = new Gson();
private OkHttpClient client= new OkHttpClient();
7 . 根據json字符串的復雜程序,定義需要序列化的bean。下圖是我獲取得到天氣預報并打印成字符串的截圖
8 . 根據上圖獲取的json字符串,要得到future最近四天的天氣,這里我們定義三個類。
CommWeather.java
public class CommWeather {
private String retCode;
private List<Results> result;
public String getRetCode() {
return retCode;
}
public void setRetCode(String retCode) {
this.retCode = retCode;
}
public List<Results> getResult() {
return result;
}
public void setResult(List<Results> result) {
this.result = result;
}
}
Results.java
public class Results {
private String city;
private String sunrise;
private String sunset;
private List<Future> future;
public List<Future> getFuture() {
return future;
}
public void setFuture(List<Future> future) {
this.future = future;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getSunrise() {
return sunrise;
}
public void setSunrise(String sunrise) {
this.sunrise = sunrise;
}
public String getSunset() {
return sunset;
}
public void setSunset(String sunset) {
this.sunset = sunset;
}
}
Future.java
public class Future {
private String date;
private String dayTime;
private String night;
private String temperature;
private String wind;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getDayTime() {
return dayTime;
}
public void setDayTime(String dayTime) {
this.dayTime = dayTime;
}
public String getNight() {
return night;
}
public void setNight(String night) {
this.night = night;
}
public String getTemperature() {
return temperature;
}
public void setTemperature(String temperature) {
this.temperature = temperature;
}
public String getWind() {
return wind;
}
public void setWind(String wind) {
this.wind = wind;
}
}
9.使用okhttp同步/異步get獲取得到天氣預報的json數據,并進行解析,并顯示在UI上。這里我們看下MainActivity中異步操作并解析的主要代碼:
private void getWeatherAsync() {
//CacheControl.FORCE_NETWORK 不進行緩存
Request request = new Request.Builder().url(url).cacheControl(CacheControl.FORCE_NETWORK).build();
//enqueue方法調用異步請求網絡,該方法接收一個okhttp3.Callback對象,
// 且不會阻塞當前線程,會新開一個工作線程,讓實際的網絡請求在工作線程中執行。
//當異步請求成功后,會回調Callback對象的onResponse方法,在該方法中可以獲取Response對象。
// 當異步請求失敗或者調用了Call對象的cancel方法時,會回調Callback對象的onFailure方法。
// onResponse和onFailure這兩個方法都是在工作線程中執行的。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("asd", "okHttp is request error");
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("asd", "okHttp is request success");
//獲取服務器返回的json字符串
String responseString = response.body().string();
Log.e("asd", "responseString: " + responseString);
//使用Gson解析json字符串
CommWeather commWeather = gson.fromJson(responseString, CommWeather.class);
//retCode==200 請求成功
if (commWeather.getRetCode().equals("200")) {
List<Results> listResult = commWeather.getResult();
//根據mob的api文檔可以確定,result始終size為1
for (int i = 0; i < listResult.size(); i++) {
final String city = listResult.get(i).getCity();
final String sunrise = listResult.get(i).getSunrise();
final String sunset = listResult.get(i).getSunset();
//在主線程中修改UI
runOnUiThread(new Runnable() {
@Override
public void run() {
tvCity.setTextColor(android.graphics.Color.RED);
tvCity.setText("Aysnc異步獲得的"+city + " 的日出時間: " + sunrise + " 和日落時間" + sunset);
}
});
//這里我們只需要獲取最近四天的天氣
final List<Future> listFuture = listResult.get(i).getFuture();
runOnUiThread(new Runnable() {
@Override
public void run() {
//修改UI操作
}
});
}
}
}
});
}
最后看下效果圖
結尾:
參考:
https://github.com/square/okhttp/wiki/Recipes
CSDN下載(內含最新jar包): http://download.csdn.net/detail/donkor_/9710663
關于我:
- Android開發交流QQ群:537891203
- 郵箱:donkor@yeah.net