編輯:關於Android編程
在Android客戶端開發中,使用網絡請求是非常常見的事情,一般我們使用HttpURLConnection是可以滿足需求的,不過隨著業務邏輯復雜,依然還是有很多不便,一個神奇的公司square開源了一個網絡請求庫——Okhttp。隨著Okhttp越來越火,越來越多的人使用Okhttp+retrofit+Rxjava,我們還是很有必要了解一下。本文的實力代碼來自官方wiki。
現在最新的版本是3.X,android支持2.3+,java應用程序中使用,java最低版本是1.7。
可以通過下載jar包獲取,也可以通過Maven獲取:
com.squareup.okhttp3 okhttp3.4.1
或者Gradle
compile 'com.squareup.okhttp3:okhttp:3.4.1'
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 創建一個請求對象,我們可以通過這個配置緩存、超時目錄、代理、攔截器等,大多數時候我們只應該創建一個對象,所有的請求可以共用緩存、連接池、 `攔截器等,如下所示:
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(8000, TimeUnit.SECONDS)
.connectTimeout(8000, TimeUnit.SECONDS)
.writeTimeout(8000, TimeUnit.SECONDS)
.addInterceptor(new Interceptor())
.cache(cache)
.build();
Requests 請求,每個HTTP請求包含一個URL、一個方法(如GET或POST),同時包含頭信息的列表。請求也可能包含一個實體:具體類型內容的數據流。
Responses 響應,根據請求返回的響應碼(成功的200或沒有找到內容的404),頭信息,和可選的實體。
Calls 代表一個實際的http請求,一般調用會執行兩種方式中的一種:
- 同步:執行execute()方法,你的線程被鎖住直到響應返回.
- 異步:執行enqueue()方法,你在任何線程安排請求,在另一個線程獲得響應回調,不會阻塞當前線程,通過Callback 對象的成功和失敗方法獲取響應。

以上代碼運行返回如圖所示,上面幾行是響應頭信息,我們可以通過Resbonse的response.headers() 的到Headers對象,我們可以通過索引遍歷獲取響應頭的信息和值,我們不通過遍歷也可以通過responseHeaders.get("Cache-Control"); 獲取,如果響應頭一樣,有多個值返回,我們可以通過`public List values(String name) {} 返回一個集合,如果通過Header對象的get方法獲取只會返回最後一個數據。
下面返回的就是獲得響應體對象response.body() ,調用string() 方法將其轉成string對象,對小文件來說string()方法響應實體是方便和高效的.但如果響應實體很大(大於1 MiB),避免使用string(),因為它會將整個文檔加載到內存中.在這種情況下,更傾向於用流處理實體。 `<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCkFzeW5jaHJvbm91cyjS7LK9KSBHZXQ8YnIgLz4NCrT6wuvI58/Cy/nKvqO6IDwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+DQo8cHJlIGNsYXNzPQ=="brush:java;"> 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(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); Headers responseHeaders = response.headers(); for (int i = 0, size = responseHeaders.size(); i < size; i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } System.out.println(response.body().string()); } }); }
以上代碼異步執行,不會阻塞當前線程,該方法接受一個okhttp3.callback對象,當請求成功後執行callback對象的onResponse方法,我們通過調用isSuccessful() 可以判斷返回的響應碼是否在200和300之間。當請求失敗或取消的會調用callback對象的onFailure 。回調的方法都是執行在工作線程的,不可以直接更新UI。
Accessing Headers 訪問頭信息
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();
Posting a String 上傳字符串
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方發接收一個RequestBody 對象,使用一個HTTP POST發送請求實體到服務.這個例子提交一個markdown文檔發送給web服務,將markdown呈現為HTML.因為整個請求實體同時在內存中,避免使用這個API發布大文檔(大於1 MiB).
Post 提交鍵值對
private final OkHttpClient client = new OkHttpClient();
public void run(Map params) throws Exception{
FormBody.Builder builder = new FormBody.Builder();
for (Map.Entry entry : params.entrySet()){
builder.add(entry.getKey(),entry.getValue().toString());
}
RequestBody formBody = builder.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 提交json
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();
return response.body().string();
}
Post Streaming 上傳流
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());
}
Posting a 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 = 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());
}
Posting form parameters 上傳表格參數
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());
}
Posting a multipart request 上傳多部分的請求
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 MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
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());
}
設置setType 我們一般將該值設置為MultipartBody.FORM ,addFormDataPart(String name, String value) 我們可以向裡面添加鍵值對,addFormDataPart(String name, String filename, RequestBody body) 我們可以向裡面添加文件,addPart 提供了三個重載方法,我們可以通過addPart(Headers headers, RequestBody body)可以在添加RequestBody的時候,同時為其單獨設置請求頭。如下所示:
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(Headers.of("Content-Disposition",
"form-data; name=\"username\""),
RequestBody.create(null, "test"))
.build();
Parse a JSON Response With Gson 用Gson解析一個JSON響應
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 entry : gist.files.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue().content);
}
}
static class Gist {
Map files;
}
static class GistFile {
String content;
}
上面就是通過Gson解析返回的數據,跟平時用法差不多。
Response Caching 響應緩存
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.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);
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));
}
為了緩存響應,你需要一個緩存目錄,你可以讀和寫,並限制緩存的大小。緩存目錄應該是私有的,不受信任的應用程序不應該能夠閱讀其內容,所以我們一般這樣配置,緩存的數據存放在context.getCacheDir()的子目錄中::
final @Nullable File baseDir = context.getCacheDir();
if (baseDir != null) {
final File cacheDir = new File(baseDir, "HttpResponseCache");
okHttpClient.setCache(new Cache(cacheDir, 10 * 10 * 1024));
}
第一次請求完成後,Okhttp將請求結果寫入了緩存當中,第一次response1.networkResponse()為請求的值,response1.cacheResponse() 打印的值為null;第二次response2.cacheResponse() 打印的是第一次網絡請求的值,response2.networkResponse() 打印的值是null,說明你第一次走的網絡請求,第二次請求來自於緩存,兩次的值response1Body.equals(response2Body) 返回也是true,很好的驗證上面的說法。我們還可以配置響應頭信息Cache-Control:max-stale=3600 Cache-Control: max-age=9600 ,okhttp也會對配置進行緩存處理,超過時間走網絡請求。禁止一個響應使用緩存,只獲取網絡響應,使用CacheControl.FORCE_NETWORK。禁止一個響應使用網絡,只使用緩存,使用CacheControl.FORCE_CACHE。注意:如果你使用FORCE_CACHE請求緩存,緩存不存在,,OkHttp將返回一個504不可滿足的請求響應。
Canceling a Call 取消一個調用
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);
}
}
Timeouts 超時
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);
}
Per-call Configuration 每個調用的設置
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 {
// Copy to customize OkHttp for this request.
OkHttpClient copy = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Response response = copy.newCall(request).execute();
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
}
try {
// Copy to customize OkHttp for this request.
OkHttpClient copy = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build();
Response response = copy.newCall(request).execute();
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}
Handling authentication 處理身份驗證
private final OkHttpClient client;
public Authenticate() {
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override public Request authenticate(Route route, Response response) throws IOException {
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();
}
})
.build();
}
public void run() throws Exception {
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會一直使用這個錯誤的信息嘗試,那麼我們應該加一個判斷,如果之前用該用戶名和密碼登陸失敗了,就不應該再次登錄:
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.
}
這上面的代碼依賴於 responseCount() 方法:
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
Okhttp很強大,我們看一下wiki基本上就可以上手,下一篇我會講解攔截器和Okhttp封裝,讓更簡潔的調用。
android 彈出提示框的使用(圖文實例)
復制代碼 代碼如下://刪除全部else if(id==R.id.btnDelet){new AlertDialog.Builder(this).setTitle(刪除提
Android動畫之視圖動畫
前言動畫在Android中是一個相當於重要的知識點,使用場景也很多,炫酷的界面效果少不了動畫來提升,這裡我們就先來說說Android中的動畫,在說Android的動畫之前
Android中用RxJava和ViewPager實現輪播圖
前言很多人要實現輪播圖都會想到使用ViewPager + Handler來完成輪播圖的效果。但是在RxJava快速發展的情況下,已經可以使用RxJava來代替Handle
從源碼角度分析嵌套滑動機制NestedScrolling
現在講到android的機制,就是事件分發,事件攔截。但我不知道大家聽沒聽說過嵌套的滑動機制,准確的可以理解成把事件分發,事件攔截綜合在一起。如果聽說過這個的,你們第一個