OkHttp3-Android網絡請求框架常用用法介紹與實例(mob請求天氣預報)

前言: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

關于我

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容