OkHttp3網絡協議的使用

OkHttp3網絡協議的使用


一、引入包

在項目module下的build.gradle添加okhttp3依賴

compile 'com.squareup.okhttp3:okhttp:3.3.1'

二、基本使用

1、okhttp3 Get 方法

1.1 、okhttp3 同步 Get方法

/**
 * 同步Get方法
 */
private void okHttp_synchronousGet() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String url = "https://api.github.com/";
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url(url).build();
                okhttp3.Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.i(TAG, response.body().string());
                } else {
                    Log.i(TAG, "okHttp is request error");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

Request是okhttp3 的請求對象,Response是okhttp3中的響應。通過response.isSuccessful()判斷請求是否成功。

@Contract(pure=true) 
public boolean isSuccessful()
Returns true if the code is in [200..300), which means the request was successfully received, understood, and accepted.


獲取返回的數據,可通過response.body().string()獲取,默認返回的是utf-8格式;string()適用於獲取小數據信息,如果返回的數據超過1M,建議使用stream()獲取返回的數據,因爲string() 方法會將整個文檔加載到內存中。

@NotNull 
public final java.lang.String string()
                             throws java.io.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:
java.io.IOException


當然也可以獲取流輸出形式;

public final java.io.InputStream byteStream()

1.2 、okhttp3 異步 Get方法

有時候需要下載一份文件(比如網絡圖片),如果文件比較大,整個下載會比較耗時,通常我們會將耗時任務放到工作線程中,而使用okhttp3異步方法,不需要我們開啓工作線程執行網絡請求,返回的結果也在工作線程中;

/**
 * 異步 Get方法
 */
private void okHttp_asynchronousGet(){
    try {
        Log.i(TAG,"main thread id is "+Thread.currentThread().getId());
        String url = "https://api.github.com/";
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                // 注:該回調是子線程,非主線程
                Log.i(TAG,"callback thread id is "+Thread.currentThread().getId());
                Log.i(TAG,response.body().string());
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}



注:Android 4.0 之後,要求網絡請求必須在工作線程中運行,不再允許在主線程中運行。因此,如果使用 okhttp3 的同步Get方法,需要新起工作線程調用。

1.3 、添加請求頭

okhttp3添加請求頭,需要在Request.Builder()使用.header(String key,String value)或者.addHeader(String key,String value);
使用.header(String key,String value),如果key已經存在,將會移除該key對應的value,然後將新value添加進來,即替換掉原來的value;
使用.addHeader(String key,String value),即使當前的可以已經存在值了,只會添加新value的值,並不會移除/替換原來的值。

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



2、okhttp3 Post 方法

2.1 、Post 提交鍵值對

很多時候,我們需要通過Post方式把鍵值對數據傳送到服務器,okhttp3使用FormBody.Builder創建請求的參數鍵值對;

private void okHttp_postFromParameters() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 請求完整url:http://api.k780.com:88/?app=weather.future&weaid=1&&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json
                String url = "http://api.k780.com:88/";
                OkHttpClient okHttpClient = new OkHttpClient();
                String json = "{'app':'weather.future','weaid':'1','appkey':'10003'," +
                        "'sign':'b59bc3ef6191eb9f747dd4e83c99f2a4','format':'json'}";
                RequestBody formBody = new FormBody.Builder().add("app", "weather.future")
                        .add("weaid", "1").add("appkey", "10003").add("sign",
                                "b59bc3ef6191eb9f747dd4e83c99f2a4").add("format", "json")
                        .build();
                Request request = new Request.Builder().url(url).post(formBody).build();
                okhttp3.Response response = okHttpClient.newCall(request).execute();
                Log.i(TAG, response.body().string());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}



2.2 、Post a String

可以使用Post方法發送一串字符串,但不建議發送超過1M的文本信息,如下示例展示了,發送一個markdown文本

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




2.3 、Post Streaming

post可以將stream對象作爲請求體,依賴以Okio 將數據寫成輸出流形式;

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




2.4 、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 = 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());
  }



3、其他配置

3.1 、Gson解析Response的Gson對象

如果Response對象的內容是個json字符串,可以使用Gson將字符串格式化爲對象。ResponseBody.charStream()使用響應頭中的Content-Type 作爲Response返回數據的編碼方式,默認是UTF-8

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



3.2 、okhttp3 本地緩存

okhttp框架全局必須只有一個OkHttpClient實例(new OkHttpClient()),並在第一次創建實例的時候,配置好緩存路徑和大小。只要設置的緩存,okhttp默認就會自動使用緩存功能。

package com.jackchan.test.okhttptest;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;

import com.squareup.okhttp.Cache;
import com.squareup.okhttp.CacheControl;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.File;
import java.io.IOException;


public class TestActivity extends ActionBarActivity {

    private final static String TAG = "TestActivity";

    private final OkHttpClient client = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        File sdcache = getExternalCacheDir();
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        client.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    execute();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void execute() 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));

    }
}




okhttpclient有點像Application的概念,統籌着整個okhttp的大功能,通過它設置緩存目錄,我們執行上面的代碼,得到的結果如下

這裏寫圖片描述

response1 的結果在networkresponse,代表是從網絡請求加載過來的,而response2的networkresponse 就爲null,而cacheresponse有數據,因爲我設置了緩存因此第二次請求時發現緩存裏有就不再去走網絡請求了。
但有時候即使在有緩存的情況下我們依然需要去後臺請求最新的資源(比如資源更新了)這個時候可以使用強制走網絡來要求必須請求網絡數據

public void execute() 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());

        request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
        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));

    }


上面的代碼中
response2對應的request變成

request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();

我們看看運行結果

這裏寫圖片描述

response2的cache response爲null,network response依然有數據。

同樣的我們可以使用 FORCE_CACHE 強制只要使用緩存的數據,但如果請求必須從網絡獲取纔有數據,但又使用了FORCE_CACHE 策略就會返回504錯誤,代碼如下,我們去okhttpclient的緩存,並設置request爲FORCE_CACHE

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        File sdcache = getExternalCacheDir();
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        //client.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    execute();
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println(e.getMessage().toString());
                }
            }
        }).start();
    }

    public void execute() 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());

        request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
        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));

    }



這裏寫圖片描述

3.3 、okhttp3 取消請求

如果一個okhttp3網絡請求已經不再需要,可以使用Call.cancel()來終止正在準備的同步/異步請求。如果一個線程正在寫一個請求或是讀取返回的response,它將會接收到一個IOException

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


3.4 、okhttp3 超時設置

okhttp3 支持設置連接超時,讀寫超時。

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


3.5 、okhttp3 複用okhttpclient配置

所有HTTP請求的代理設置,超時,緩存設置等都需要在OkHttpClient中設置。如果需要更改一個請求的配置,可以使用 OkHttpClient.newBuilder()獲取一個builder對象,該builder對象與原來OkHttpClient共享相同的連接池,配置等。

如下示例,拷貝2個'OkHttpClient的配置,然後分別設置不同的超時時間;

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


3.6 、okhttp3 處理驗證

okhttp3 會自動重試未驗證的請求。當一個請求返回401 Not Authorized時,需要提供Anthenticator

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




參考文獻:

https://github.com/square/okhttp/wiki/Recipes http://blog.csdn.net/chenzujie/article/details/46994073 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章