1. 程式人生 > >OKHttp3.0的日常及入門

OKHttp3.0的日常及入門

前言

Okhttp作為現在最火的一個網路請求框架,已經有無數牛人給出了工具類等等。

有別人寫好的工具類固然好,但是不光用要會用,還要止其所以然。

那麼直接取拉官方的程式碼,研究如何使用以及閱讀API說明,對加深理解OkHttp和了解基礎,就更有幫助了。

正文

OkHttp現在已經升級到3.0版本了,API較2.0還是有些不同的。稍後再說。

下面將通過一個一個的測試用例來說明和解釋API的用法,當然這些用力都是我照著example敲的,研究的過程中自然有了些瞭解,便記錄下來。

建立完工程,引入了包,別忘記建立Client例項。

OkHttpClient yOkHttpClient = new
OkHttpClient();

那麼,OkHttpClient是幹嘛用的呢?
簡單來說,通過OkHttpClient可以傳送一個Http請求,並讀取該Http請求的響應,它是一個生產Call的工廠。
此外,受益於一個共享的響應快取/執行緒池/複用的連線等因素,絕大多數應用使用一個OkHttpClient例項,便可以滿足整個應用的Http請求。
建立一個預設配置OkHttpClient,可以使用預設的建構函式。或者通過new OkHttpClient.Builder()方法來一步一步配置一個OkHttpClient例項。另外,如果要求使用現有的例項,可以通過newBuilder()方法來進行構造。

下面就是一個構造OkHttpClient例項的簡單例子。

OkHttpClient client = ...
OkHttpClient clientWith30sTimeout = client.newBuilder()
    .readTimeout(30, TimeUnit.SECONDS)
    .build();
Response response = clientWith30sTimeout.newCall(request).execute();

看一下OkHttpClient的原始碼,會發現快取/代理等等需求,一應俱全的按照類封裝到了Builder中。

Dispatcher dispatcher;          // 分發
Proxy proxy; // 代理 List<Protocol> protocols; List<ConnectionSpec> connectionSpecs; final List<Interceptor> interceptors = new ArrayList<>(); // 攔截器 final List<Interceptor> networkInterceptors = new ArrayList<>(); // 網路攔截器 ProxySelector proxySelector; CookieJar cookieJar; Cache cache; // 快取 InternalCache internalCache; SocketFactory socketFactory; SSLSocketFactory sslSocketFactory; HostnameVerifier hostnameVerifier; CertificatePinner certificatePinner; Authenticator proxyAuthenticator; // 代理證書 Authenticator authenticator; // 證書 ConnectionPool connectionPool; Dns dns; // DNS boolean followSslRedirects; boolean followRedirects; boolean retryOnConnectionFailure; int connectTimeout; int readTimeout; int writeTimeout;

簡單Get請求

程式碼:

@Test
public void testGet() throws IOException {
    Request build = new Request.Builder()
            .url("https://raw.github.com/square/okhttp/master/README.md")
            .build();
    Response response = yOkHttpClient.newCall(build)
            .execute();
    System.out.println(response);
}

結果:
在結果中列印了協議,結果碼,訊息結果,訪問的url地址等等。

Response{protocol=http/1.1, code=200, message=OK, url=https://raw.githubusercontent.com/square/okhttp/master/README.md}

簡單看一下Request類,可以發現它代表一個Http請求,需要注意的是Request一旦build()之後,便不可修改。

Request的例項,主要通過new Request.Builder()來一步一步構造的。看一下Builder的程式碼。

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

預設是Get方法,此外還建立了頭資訊。值得注意的是Headers類中是通過List<String> namesAndValues = new ArrayList<>(20),來存放頭資訊的,一開始我也很納悶,頭資訊都是一對一對的為什麼要用List,看一下原始碼發現,在存取的時候都是將索引+2或者-2。並且頭資訊可以存在多個相同的Key資訊。

繼續看一下如何發起請求的。

yOkHttpClient.newCall(request)

跟到newCall()方法中發現,又使用OkHttpClient例項和Request的例項,一起構造了一個RealCall的例項。在跟進RealCall程式碼中,可以簡單瞭解到RealCall類簡單做了一個託管並通過Dispather類對請求進行分發和執行,實際開啟執行緒發起請求的方法就在這個類中。

隨後又呼叫execute()方法,拿到了一個響應。這個execute()方法,實際上執行的就是RealCall中的execute()方法,那麼最後就呼叫了Dispatcherexecute()方法。

最後,再看一下Response類的說明,Response代表一個Http的響應,這個類的例項不可修改。

致次,一個簡單的Get請求和說明就結束了,簡單跟入原始碼的原因在於,更清楚的瞭解OkHttpClient的Api和執行原理。

簡單Post請求

提起post請求,必然是有請求體的。

程式碼:

String bowlingJson(String player1, String player2) {
    return "{'winCondition':'HIGH_SCORE',"
            + "'name':'Bowling',"
            + "'round':4,"
            + "'lastSaved':1367702411696,"
            + "'dateStarted':1367702378785,"
            + "'players':["
            + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
            + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
            + "]}";
}

@Test
public void testPost() throws IOException {
    MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    RequestBody body = RequestBody.create(JSON, bowlingJson("Jesse", "Jake"));
    Request request = new Request.Builder()
            .url("http://www.roundsapp.com/post")
            .post(body)
            .build();
    Response response = yOkHttpClient.newCall(request).execute();
    System.out.println(response.body().string());
}

此處先給一個Http請求頭大全的地址,非常好的。

MediaType用於描述Http請求和響應體的內容型別。對於Http請求頭不瞭解的人,請看下面的圖(PS:我也不太懂)。

很明瞭吧,MediaType代表的就是請求包體內容的型別。

例如,MediaType.parse("application/json; charset=utf-8");這個就帶表請求體的型別為JSON格式的。

定義好資料型別,還要將其變為請求體,最後通過post()方法,隨請求一併發出。

RequestBody body = RequestBody.create(JSON, bowlingJson("Jesse", "Jake"));

為請求指定頭資訊

程式碼:

 @Test
public void testAccessHeaders() throws Exception {
    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            // User-Agent   User-Agent的內容包含發出請求的使用者資訊    User-Agent: Mozilla/5.0 (Linux; X11)
            .header("User-Agent", "OkHttp Headers.java")
            // Accept   指定客戶端能夠接收的內容型別  Accept: text/plain, text/html
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build();

    Response response = yOkHttpClient.newCall(request).execute();

    if (response.isSuccessful()) {
        System.out.println("Server: " + response.header("Server"));
        System.out.println("Date: " + response.header("Date"));
        System.out.println("Vary: " + response.headers("Vary"));
    }
}

響應結果:

Server: GitHub.com
Date: Thu, 28 Jan 2016 15:41:13 GMT
Vary: [Accept, Accept-Encoding]

這個例子主要是Http請求頭資訊的一個展示。
對於header(String name, String value)方法,使用name和value設定一個頭資訊,如果請求中已經存在響應的資訊那麼直接替換掉。

addHeader(String name, String value),如果請求頭中已經存在name的name-value,那麼還會繼續新增,請求頭中便會存在多個name相同而value不同的“鍵值對”。

至於name的取值說明,可以檢視這個請求頭大全

非同步Get請求

程式碼如下:

@Test
public void testAsyncGet() {
    final Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();
    System.out.println(request);
    yOkHttpClient.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 {
            System.out.println("call = [" + call + "], response = [" + response + "]");
            if (!response.isSuccessful()) {
                throw new IOException("" + response);
            }

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

            System.out.println(response.body().string());
        }
    });
}

與同步方法不同,呼叫的是enqueue()方法,其實際上呼叫的就是RealCall中的enqueue()方法,那麼實際上就是Dispatcherenqueue()方法。

synchronized void enqueue(AsyncCall call) {
 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
   runningAsyncCalls.add(call);
   executorService().execute(call);
 } else {
   readyAsyncCalls.add(call);
 }
}

新增授權證書

程式碼如下:

@Test
public void testAuthenticate() throws IOException {
    yOkHttpClient = 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");
                    // HTTP授權的授權證書  Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
                    return response
                            .request()
                            .newBuilder()
                            .header("Authorization", credential)
                            .build();
                }
            })
            .build();

    Request request = new Request.Builder().url("http://publicobject.com/secrets/hellosecret.txt").build();
    Response response = yOkHttpClient.newCall(request).execute();

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

結果如下:

 Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets"]

通過Authenticator類,可以響應來自遠端或者代理伺服器的授權驗證,通常情況會返回一個授權頭以做驗證;亦或是返回空表示拒絕驗證。簡單來說,你要訪問一個服務,但是你要對方的驗證。通過Authenticator類來代理一個認證請求,並使用Credentials.basic()來構造一個證書。

請求快取

程式碼如下:

@Test
    public void testCacheResponse() throws IOException {

        int cacheSize = 10 * 1024 * 1024;

        Cache cache = new Cache(new File("bzh.tmp"), cacheSize);

        yOkHttpClient = new OkHttpClient.Builder()
                .cache(cache)
                .build();

        Request request = new Request.Builder()
                .url("http://publicobject.com/helloworld.txt").build();

        Response response1 = yOkHttpClient.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 = yOkHttpClient.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

        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));
    }

說起這個快取那就更有趣了,okhttp自帶快取,是不是很厲害。

對於OkHttpClient來說,只要為其設定瞭如下程式碼,那麼便有了快取功能。

Cache cache = new Cache(new File("bzh.tmp"), cacheSize);
yOkHttpClient = new OkHttpClient.Builder()
        .cache(cache)
        .build();

其中最為關鍵的就是Cache類,他的主要作用在於快取HTTP和HTTPS響應檔案,所以他們可以重複使用,節省時間和頻寬。

強制使用網路請求

Request request = new Request.Builder()
     // or  .cacheControl(CacheControl.FORCE_NETWORK)
     .cacheControl(new CacheControl.Builder().noCache().build())
     .url("http://publicobject.com/helloworld.txt")
     .build();

其中CacheControl.FORCE_NETWORKnew CacheControl.Builder().noCache().build()是等效的。

測試結果如下:

Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response:    null
Response 1 network response:  Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 response:           Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response:     null
Response 2 network response:   Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 equals Response 1 ? true

可以看到,響應中只有網路響應,而快取響應不存在。

另外,如果需要強制伺服器驗證一下快取,可以使用如下程式碼:

Request request = new Request.Builder()
     .cacheControl(new CacheControl.Builder()
         .maxAge(0, TimeUnit.SECONDS)
         .build())
     .url("http://publicobject.com/helloworld.txt")
     .build();

測試執行結果如下:

Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response:    Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 network response:  Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
Response 2 response:           Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response:     Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response:   Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
Response 2 equals Response 1 ? true

可以看到,響應中快取和網路響應都存在。

強制使用快取

 Request request = new Request.Builder()
     .cacheControl(new CacheControl.Builder()
         .onlyIfCached()
         .build())
     .url("http://publicobject.com/helloworld.txt")
     .build();
 Response forceCacheResponse = client.newCall(request).execute();
 if (forceCacheResponse.code() != 504) {
   // The resource was cached! Show it.
 } else {
   // The resource was not cached.
 }

其中,new CacheControl.Builder().onlyIfCached().build()CacheControl.FORCE_CACHE是一致的。

Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response:    Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 network response:  null
Response 2 response:           Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response:     Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response:   null
Response 2 equals Response 1 ? true

根據結果可以發現,響應中只有快取響應而無網路響應。

另外還有一種情況,如果快取過期了,而又無法請求到網路怎麼辦?
可以通過如下程式碼,繼續使用過期的快取。

Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .maxStale(365, TimeUnit.DAYS)
        .build())
    .url("http://publicobject.com/helloworld.txt")
    .build();

值得一提的

如果在構造OkHttpClient時,沒有指定cache,那麼便不會有快取功能。

取消任務

@Test
public void testCancelCall() {
    Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2")
            .build();

    final long startNanos = System.nanoTime();
    final Call call = yOkHttpClient.newCall(request);

    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);
    }
}

測試結果:

0.02 Executing call.
1.01 Canceling call.
1.01 Canceled call.
1.01 Call failed as expected: java.net.SocketException: Socket closed

證書

 @Test
public void testCertificatePinning() throws IOException {
    yOkHttpClient = new OkHttpClient.Builder()
            .certificatePinner(new CertificatePinner.Builder()
                    .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
                    .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
                    .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
                    .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
                    .build())
            .build();

    Request request = new Request.Builder().url("https://publicobject.com/robots.txt").build();

    Response response = yOkHttpClient.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    for (Certificate certificate : response.handshake().peerCertificates()) {
        System.out.println(CertificatePinner.pin(certificate));
    }
}

測試結果:

Connected to the target VM, address: '127.0.0.1:49312', transport: 'socket'
sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=
sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=
sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=
sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=
Disconnected from the target VM, address: '127.0.0.1:49312', transport: 'socket'

超時設定

程式碼如下

@Test
public void testTimeout() throws IOException {
    yOkHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();

    Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();

    Response response = yOkHttpClient.newCall(request).execute();
    System.out.println("Response completed: " + response);
}

結果如下:

Response completed: Response{protocol=http/1.1, code=200, message=OK, url=http://httpbin.org/delay/2}

新增日誌攔截器

private static class LoggingInerceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {

        long t1 = System.nanoTime();
        Request request = chain.request();

        System.out.println(String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();

        System.out.println(String.format("Received response for %s in %.1fms%n%s",
                request.url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

  @Test
public void testLoggingInterceptor() throws IOException {
    yOkHttpClient = new OkHttpClient.Builder()
            .addInterceptor(new LoggingInerceptor())
            .build();

    Request request = new Request.Builder()
            .url("https://publicobject.com/helloworld.txt")
            .build();

    Response execute = yOkHttpClient.newCall(request).execute();

    execute.body().close();
}

測試結果:

Sending request https://publicobject.com/helloworld.txt on null

Received response for https://publicobject.com/helloworld.txt in 2555.9ms
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 30 Jan 2016 03:49:20 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
OkHttp-Sent-Millis: 1454126228626
OkHttp-Received-Millis: 1454126229516

可以瞭解,在攔截器的intercept()方法中,通過chain.request()可以拿到請求物件;通過chain.proceed(request)可以拿到響應結果;

Gson解析

 @Test
public void testParseResponseGson() throws IOException {

    Gson gson = new Gson();

    Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
    Response response = yOkHttpClient.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    response.body().close();

    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue().content);
    }
}

這裡面最主要的是呼叫了response.body().charStream()。大致看一下ResponseBody類。

  1. ResponseBody儲存了服務端發往客戶端的原始位元組流。
  2. ResponseBody必須被關閉
  3. 每一個響應體都是一個有限的資源支援。如果沒有關閉的響應體將洩漏這些資源,並可能最終導致應用程式的速度慢下來或崩潰。通過close()bytestream()關閉響應體.reader().close()。()。其中bytes()string()方法會自動關閉響應體。
  4. 響應主體只能被消耗一次。
  5. 這個類可以用於非常大的響應流。例如,常見的視訊流應用的要求。
  6. 因為這個ResponseBody不緩衝記憶體中的全部響應,應用程式不能重新讀取響應的位元組數。可以利用 bytes() orstring()source(), byteStream(), or charStream()等方法,將流內容讀入到記憶體中。

一個請求多個Client配置

 @Test
public void testPerCallSettings() {
    Request request = new Request.Builder()
            .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
            .build();

    try {

        OkHttpClient copy = yOkHttpClient.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 = yOkHttpClient.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);
    }
}

執行結果:

Response 1 failed: java.net.SocketTimeoutException: timeout
Response 2 succeeded: Response{protocol=http/1.1, code=200, message=OK, url=http://httpbin.org/delay/1}

上傳小檔案

  @Test
public void testPostFile() throws IOException {
    MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

    File file = new File("記錄.md");

    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
            .build();

    Response response = yOkHttpClient.newCall(request).execute();

    if (!response.isSuccessful()) {
        throw new IOException("Unexpected code " + response);
    }

    System.out.println(response.body().string());
}

上傳表單

@Test
public void testPostForm() throws IOException {
    FormBody formBody = new FormBody.Builder()
            .add("search", "biezhihua")
            .build();

    Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();

    Response response = yOkHttpClient.newCall(request)
            .execute();

    if (!response.isSuccessful()) {
        throw new IOException("Unexcepted code " + response);
    }

    System.out.println(response.body().string());
}

主要利用了FormBody類,不用猜我們也能知道,其繼承了RequestBody類,並內建了MediaType型別,用且用集合儲存鍵值對資料。

上傳表單

@Test
public void testPostMultipart() throws IOException {
    String IMGUR_CLIENT_ID = "9199fdef135c122";
    MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

    MultipartBody 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 = yOkHttpClient.newCall(request)
            .execute();

    if (!response.isSuccessful()) {
        throw new IOException("Unexpected code" + request);
    }

    System.out.println(response.body().string());
}

上傳流

 @Test
public void testPostStreaming() throws IOException {
    final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

    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 = yOkHttpClient.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());

}

其實以上這幾個測試,實質上都是一個東西,就是對RequestBody做的封裝或者重寫,都是將本地資料放入到Http協議的請求體中,然後傳送到服務端。其中唯一不同,就是別人幫你寫好的;或是你自己重寫的幾個方法。

上傳字串

@Test
public void testPostString() throws IOException {
    MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
    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 = yOkHttpClient.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

載入進度

程式碼如下

// 監聽進度的介面
interface ProgressListener {
    void update(long bytesRead, long contentLength, boolean done);
// 處理進度的自定義響應體
static class ProgressResponseBody extends ResponseBody {

      private final ResponseBody responseBody;
      private final ProgressListener progressListener;
      private BufferedSource bufferedSource;

      public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
          this.responseBody = responseBody;
          this.progressListener = progressListener;
      }


      @Override
      public MediaType contentType() {
          return responseBody.contentType();
      }

      @Override
      public long contentLength() {
          return responseBody.contentLength();
      }

      @Override
      public BufferedSource source() {

          if (bufferedSource == null) {
              bufferedSource = Okio.buffer(source(responseBody.source()));
          }

          return bufferedSource;
      }

      private Source source(Source source) {

          return new ForwardingSource(source) {

              long totalBytesRead = 0L;

              @Override
              public long read(Buffer sink, long byteCount) throws IOException {
                  long bytesRead = super.read(sink, byteCount);
                  totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                  progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                  return bytesRead;
              }
          };
      }
  }
}

// 為客戶端例項新增網路攔截器,並相應回撥。
@Test
public void testProgress() throws IOException {
    Request request = new Request.Builder()
            .url("https://publicobject.com/helloworld.txt")
            .build();

    final ProgressListener progressListener = new ProgressListener() {

        @Override
        public void update(long bytesRead, long contentLength, boolean done) {
            System.out.println("bytesRead = [" + bytesRead + "], contentLength = [" + contentLength + "], done = [" + done + "]");
            System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
        }
    };

    yOkHttpClient = new OkHttpClient.Builder()
            .addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Response originalResponse = chain.proceed(chain.request());
                    return originalResponse.newBuilder()
                            .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                            .build();
                }
            })
            .build();

    Response response = yOkHttpClient.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}