Android網絡請求庫 - Say hello to OkHttp

rwkz2260 8年前發布 | 30K 次閱讀 OkHttp 安卓開發 Android開發 移動開發

引言

網絡請求是Android開發中必不可少的一塊,通常我們可以用原生的HttpUrlConnection或者Apache封裝的HttpClient來完成網絡請求的實現。

如今來說,HttpClient在API23問世后也被廢棄了。也就是說Google現在更推薦我們使用HttpUrlConnection來實現Http網絡請求。

但以Http請求來說,大多數時候都是遵循一定的套路的,所以重復的寫關于HttpUrlConnection的相關代碼是一件很無趣并且浪費精力的事情。

于是,很多時候我們都會根據自己的實際需求去封裝自己的網絡請求框架。如果我們自己足夠牛逼,那自己動手寫框架會是一件很酷的事情。

但如果能力暫時還不夠或者不想浪費精力去造輪子,那么當然也有很多現成的關于網絡請求的第三方庫供我們選擇。

現在Android開發中,最常見的和出名的網絡請求庫大致有三種,分別是:Volley,OkHttp以及Retrofit。那么,我們做何選擇呢?

關于這點我們可以參考這篇文章: Android開源項目推薦之「網絡請求哪家強」 ,里面介紹了關于以上一種網絡請求庫的優劣對比。

初次見面

好的,接下來我們進入正題。今天我們要走進的是Android網絡請求開源庫這個系列里的第一個庫: OkHttp

首先,我們可以進入關于 OkHttp 該庫的官方介紹的網站: An HTTP & HTTP/2 client for Android and Java applications

好的,在開始了解如何使用這個庫之前。我們打算先對這個庫的概念有一個大致性的了解。我們先看到官方介紹中的Overview:

HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth. HTTP是現代應用進行網絡通信的途徑。這關乎于我們如何去交換數據&多媒體信息;如何通過HTTP更加有效的提升加載數據的速度以及節省帶寬。

OkHttp is an HTTP client that’s efficient by default: OkHttp是一種HTTP的客戶端,它默認為我們提供了以下有效的默認行為:

  • HTTP/2 support allows all requests to the same host to share a socket.
    HTTP 2.0的情況下,支持所有指向相同主機的請求共享同一個套接字
  • Connection pooling reduces request latency (if HTTP/2 isn’t available).
    通過連接池減少請求的延遲(如果是HTTP 2.0則是無效的)
  • Transparent GZIP shrinks download sizes.
    通過GZIP對數據進行壓縮,減少數據體積(從而可以節省流量/帶寬)
  • Response caching avoids the network completely for repeat requests.
    請求響應進行了緩存,從而避免重復請求(同樣是節省流量)

OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails. bala bala….核心意思就是:如果你的服務器配置了多個地址,OkHttp會在第一個地址連接失敗的時候,輪流的嘗試連接其他備用地址。

Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks. OkHttp的使用很簡單,因為其API被設計的很流暢和遵循一定套路。與此同時,同步阻塞和異步回調的方式哥都支持。

OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7. OkHttp支持Android 2.3以上版本;Java則是1.7以上版本。

準備工作

因為是第三方庫,所以很顯然的假設我們想要在自己的項目中使用它。自然第一步的準備工作就是將其搗鼓進我們自己的項目。我們有兩種方式:

  • 如果在AndroidStudio中進行開發(即gradle作為構建工具),那么因為OkHttp已經被托管在代碼倉庫jcenter當中,

    我們要做的就很簡單了:僅僅只需要在app目錄下的build.gralle文件中進行相關的依賴配置即可:

compile ‘com.squareup.okhttp3:okhttp:3.4.1’

compile ‘com.squareup.okio:okio:1.9.0’

  • 當然還有另一種熟悉的方式,那就是通過倒入JAR包的方式。這個就沒什么值得說的了,只需要下載對應的JAR包倒入就行了。(JAR為okhttp以及其依賴的okio)

基本使用

HTTP GET

顯然,Http分為GET和POST兩種請求方式,我們先來看比較簡單的GET請求在OkHttp中怎么實現。在本地寫一段簡單的測試代碼:

public class Test {
    static String url = "http://localhost:8080/LocalServer/hello.api";

public static void main(String[] args) {
    try {
        testHttpGet();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

static void testHttpGet() throws IOException {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();

    if(response.isSuccessful()){
        String content = response.body().string();
        System.out.println(content);
    }
}

}</code></pre>

我們通過tomcat在本地搭建了一個簡單的servlet服務器,Get請求成功后,返回一段請求成功的文本信息。運行上面的代碼會發現的確請求成功了。

可以看到對于OkHttp來說,基本的使用確實還是比較簡單的。在上面的測試代碼中,我們可以注意到三個比較關鍵的類:

  • OkHttpClient 封裝好的OkHttp客戶端
  • Request 對應于Http的請求體
  • Response 對應于Http的響應體

然后我們來解析一下這段代碼發起請求的過程:

  • 首先,我們看到如果我們想發起一個請求,首先需要構建一個Request對象,而該對象是通過其輔助類Builder來構建的。
    同時因為其本身已經做了一定的封裝,所以我們這里只需要通過url()方法傳入我們自己的服務器url,并且調用build()方法完成構建就行了。
  • 接著通過OkHttpClient的newCall方法將之前的Request對象傳入,真正完成此次請求(Call)的封裝。接著調用execute發出請求。
  • execute方法將返回一個Response對象,如它的命名一樣,這個對象封裝了此次請求響應的各種信息。

HTTP POST

看完了GET的基本使用,我們接著就來看POST的基本使用是如何的。官方介紹中的Example是這樣的:

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();
}</code></pre> 

我們可以看到,實際上上面的代碼與之前使用GET時的代碼是很相似的。不同點在于:

這里在通過Builder構建Request對象時,多調用了一個方法post()。而該方法接受的參數類型是一個叫做RequestBody的東西。

從命名就可以看出來這是對于請求體的一個封裝。因為對于POST請求來說,通常我們都會在請求體中傳入一些我們提交給服務器的信息。

public static final MediaType JSON=MediaType.parse(“application/json; charset=utf-8”);這行代碼十分重要。

它做的工作實際是在設置請求的 Content-Type 。(更多的ContentType信息可以參考: 常用Http Content-Type對照表

接著,我們通過源碼看一看為什么post方法能夠聲明此次請求方法是POST。它在底層是怎么樣與GET進行區分的呢?打開post的方法的源碼:

public Request.Builder post(RequestBody body) {
            return this.method("POST", body);
        }

        public Request.Builder method(String method, RequestBody body) {
            // ......
            } else {
                this.method = method;
                this.body = body;
                return this;
            }
        }

看上去十分簡潔易懂,那么為什么我們沒有調用post方法的的時候,就代表是GET請求呢?打開builder默認的無參構造器就得到了答案:

public Builder() {
            this.method = "GET";
            this.headers = new okhttp3.Headers.Builder();
        }

其它招式

到了這里我們已經知道了對于OkHttp這個開源庫來說,最基本的GET與POST請求的使用方式。但顯然這還是不夠,我們接著來看一些其它的常用的方式。

不同的響應數據類型

我們之前在HTTP GET的用例當中,通過response.body().string();的方式很容易的獲取到了響應信息。但顯然這里是指純文本信息。

一個成熟的框架的封裝度肯定遠不如此,OkHttp還提供了另外幾個常見的方法分別用于以byte數組、字節流與字符流的方式讀取響應信息。它們分別是:

  • bytes() // 返回類型為byte[]
  • byteStream() //返回類型為InputStream
  • charStream() //返回類型為Reader

現在我們以一個簡單的例子來加深這種印象。假設我們現在的需求是通過url去下載一張網絡圖片到本地,那么顯然這個時候就需要以字節流的形式去讀取數據:

if (response.isSuccessful()) {
            InputStream in = response.body().byteStream();
            FileOutputStream fos = new FileOutputStream(new File(
                    "android.jpg"));

            int length;
            byte[] buf = new byte[1024];

            while ((length = in.read(buf)) != -1) {
                System.out.println(length);
                fos.write(buf, 0, length);
            }
        }

添加請求頭

顯然我們在實際開發時,很多時候還需要在請求頭中去設置一些相關的信息。在OkHttp當中這個操作也容易實現:

Request request = new Request.Builder()
                         .url(url)
                         .header("headerKey1", "headerValue1")    // 設置請求頭
                         .addHeader("headerKey2", "headerValue2") // 追加請求頭
                         .build();

獲取響應頭

同理來說,當然我們也可以從響應體中提取頭信息。實現的方式同樣很簡單:

System.out.println(response.header("responseHeader"));
            System.out.println(response.header("responseHeader", "默認值"));

            Headers headers = response.headers();
            for (int i = 0 ;i < headers.size();i++) {
                String name = headers.name(i);
                System.out.println(response.header(name));
            }

以其它形式提交數據

我們之前在說到POST的示例代碼中,它示范了怎么樣提交JSON形式的數據。但實際開發中,顯然有很多其它類型的數據讓我們POST。

舉例來說,很多時候我們都會以表單形式去提交鍵值對上傳到服務器,OkHttp也考慮到了這點,并做好了封裝。

之前我們可以通過FormEncodingBuilder來實現,而OkHttp3之后這個類則已經被FormBody所代替了。

RequestBody formBody = new FormBody.Builder()
        .add("platform", "android")
        .add("name", "bug")
        .add("subject", "XXXXXXXXXXXXXXX")
        .build();

        Request request = new Request.Builder().url(url).post(formBody).build();

我們注意到這與之前上傳JSON的示例當中,改變了的就僅僅是RequestBody的構建形式。抱著一探究竟的心態打開FormBody的源碼,首先就會看到:

private static final MediaType CONTENT_TYPE = MediaType.parse("application/x-www-form-urlencoded");

我們恍然大悟,原來所謂的FormBody其實也就僅僅是OkHttp為我們做的一層封裝而已。關鍵還是在于對于Content-Type的設置。

而一通則百通,了解了這個原理,對于其他更多數據類型的POST的使用,我們也就有底了。

響應緩存

在有些情況下,我們沒有必要每次都去發起一次全新的請求去獲取數據。可以通過設置緩存的方式來達到節約開銷的目的。OkHttp當然也支持這樣的操作。

// 設置緩存目錄與緩存大小上限
        Cache cache = new Cache(new File("D:\\cache"), 1024 * 1000);
        // okhttp3不再以setCache設置緩存而是通過OkHttpClient.Builder來設置了
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        clientBuilder.cache(cache);
        // 構建客戶端對象
        OkHttpClient client = clientBuilder.build();

由此我們發現在OkHttp中使用緩存還是很簡單的,關鍵需要注意一點:那就是我們發現這里的cache是與OkHttpClient對象關聯的。也就是說:

我們最好在第一次調用時配置好緩存,然后其他地方只需要調用這個實例就可以了。否則兩個緩存示例互相干擾,破壞響應緩存,且可能導致程序崩潰。

同時,響應緩存使用HTTP頭作為配置。對于客戶端來說,我們可以在請求頭中添加“Cache-Control: max-stale=3600”,OkHttp緩存會支持。而對于服務來說,則是通過響應頭確定響應緩存多長時間,例如使用Cache-Control: max-age=3600。

我們在這里可以簡單的總結一下這兩個東西,因為起初我一直對它們都感到有點迷惑:

  • 服務器在響應頭中設置max-age的意義在于,將此次請求的響應進行緩存指定的秒數。當超出這個指定時間之后,這個緩存就會失效,那么再次獲取數據就需要發起新的網絡請求了。
  • 客戶端在請求頭中設置max-stale,這個東西更有趣。起初一直沒搞明白它究竟有什么用,直到看到Google在Android Developers一段相關的解析。

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:

這段解釋的關鍵在于: 一個陳舊的響應總好過沒有響應 。 一下就恍然大悟了,我們之前說緩存一旦超過max-age就會失效,需要獲取新的響應。

但是也可能出現雖然緩存失效了,卻新的請求因為某種原因失敗獲取不到響應,那么這時我們仍然從失效的緩存中獲取響應總是要好過沒有響應的。

而max-stale的意義就在于指定允許從失效多久的緩存中讀取響應。例如我們將其設置為3600秒,即代表是否讀取緩存取決于緩存是否已經失效超過1小時。

(另外,從代碼注釋中也可以看到,對于緩存的設置已經放在了OkHttpClient.Builder中。同理,對于其它的一些請求屬性設置(如超時等)都已經轉移到了這個類當中。)

做好了準備工作。我們就可以通過代碼來判斷是從緩存還是網絡中讀取響應了:

System.out.println("response:" + response);
            System.out.println("cache response:"+ response.cacheResponse());
            System.out.println("network response:"+ response.networkResponse());

取消請求

有的時候,因為一些原因,我們也會想要去取消或者說中斷一個請求。OkHttp當然也支持這種功能。

Call call = client.newCall(request);
        call.enqueue(new Callback() {

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("請求成功");
                call.cancel(); //可以在這里取消
            }

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("請求失敗");

            }
        });
        call.cancel(); // 可以在這里取消
        client.dispatcher().cancelAll(); // 可以取消全部請求

并且,同樣的,如果取消一個正處于連接狀態下的請求,是會拋出IO異常的。

異步請求

之前我們測試的代碼當中,通過execute方法來執行請求,其內部都是以同步的形式實現的。而如果我們希望異步執行請求的話,則應該通過enqueue來實現:

client.newCall(request).enqueue(null);

我們發現由同步執行改為異步執行很簡單,只需要由execute方法改為調用enqueue就行了。但需要明白的一點是:

這里所謂的同步與異步并不是指多個請求的串行或者并行執行的區別。而是指線程是否因為此次請求而堵塞。

通過一段簡單的代碼,我們能夠更加詳細的理解這個概念:

Response response = client.newCall(request).execute();
        System.out.println("lalalala");

當我們采用同步的方式執行請求時,線程會進入堵塞,也就是說當此次請求完全執行完畢之前,之后的”lalalala”的打印語句都是無法執行并輸出的。

而假設我們先將其該為用異步的方式執行請求,那么我們會發現之后的打印語句是不會被阻塞而無法執行的。

與此同時,我們看到enqueue方法接收一個Callback類型的參數,這個其實沒什么好說的,一旦涉及異步,通常都離不開回調。這同樣也是一個回調接口:

client.newCall(request).enqueue(new Callback() {

            @Override
            public void onResponse(Call arg0, Response arg1) throws IOException {
                System.out.println("請求成功");
            }

            @Override
            public void onFailure(Call arg0, IOException arg1) {
                System.out.println("請求失敗");

            }
        });

總結

OK,到了這里,實際上當我們掌握了以上說到的點,對于日常的使用基本上是已經足夠了。

更多的一些功能或者進階的使用技巧,我們可以查看官方的資料或者在自己的使用工作逐漸摸索和總結。

總的來說,OkHttp是一個十分強大的網絡請求庫。但與此同時我們可以發現如果直接使用的話,在實際使用中還是會寫到很多重復的代碼的。

這也就是為什么很多人都建議能力足夠的話,可以根據自己的需求來對OkHttp進行一次二次封裝,從而讓使用更加簡潔。

總的來說就總結到這里吧,畢竟實踐才能出真知。掌握了基本的知識點后,只有在實際的使用中不斷碰坑,才能越發熟練的掌握一個東西。

 

來自:http://blog.csdn.net/ghost_programmer/article/details/52253359

 

 本文由用戶 rwkz2260 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!