OKHttp3的使用和詳解

前言:不去追逐,永遠不會擁有。不往前走,永遠原地停留。不經歷風雨,怎麼見彩虹。

一、概述

OKHttp是處理網絡請求的開源框架,Andorid當前最火熱的網絡框架,Retrofit的底層也是OKHttp,用於替換HttpUrlConnection和Apache HttpClient(API23 6.0已經移除)。

概況起來說OKHttp是一款優秀HTTP框架,它支持GET和POST請求,支持Http的文件上傳和下載,支持加載圖片,支持下載文件透明的GZIP壓縮,支持響應緩存避免重複的網絡請求,支持使用連接池來降低響應延遲的問題。

OKHttp的優點:

1.支持HTTP2/SPDY,這使得對同一個主機發出的所有請求都可以共享相同的套接字連接。

2.如果HTTP2/SPDY不可用OkHttp,會使用連接池來複用連接以提高效率。

3.提供了對 GZIP 的默認支持來降低傳輸內容的大小

4.提供了對 HTTP 響應的緩存機制,可以避免不必要的網絡請求

5.當網絡出現問題時,OkHttp會自動重試一個主機的多個 IP 地址

二、基本的使用

2.1、配置工程

(1)首先在清單文件AndroidManifest.xml中添加網絡權限

 <uses-permission android:name="android.permission.INTERNET"/>

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2)在build.gradle文件中添加okhttp依賴:

implementation 'com.squareup.okhttp3:okhttp:4.2.0'

2.2、使用步驟:

(1)創建OkHttpClient實例

(2)通過Builder輔助類構建Request請求對象

(3)OkHttpClient實例回調請求,得到Call對象

(4)同步/異步執行請求,獲取Response對象

2.3、方法詳解

(1)創建OkHttpClient實例

//方式一:創建OkHttpClient實例,使用默認構造函數,創建默認配置OkHttpClient(官方建議全局只有一個實例)
OkHttpClient okHttpClient = new OkHttpClient();

//方式二:通過new OkHttpClient.Builder() 一步步配置一個OkHttpClient實例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();

//方式三:如果要求使用現有的實例,可以通過newBuilder().build()方法進行構造
OkHttpClient client = okHttpClient.newBuilder().build();

使用默認構造函數創建OkHttpClient實例,創建默認配置OkHttpClient(官方建議全局只有一個實例)。也可以通過new OkHttpClient.Builder() 一步步配置一個OkHttpClient實例,如果要求使用現有的實例,可以通過newBuilder().build()方法進行構造。

(2)超時設置

我們在創建OkHttpClient實例的時候也會設置相關的屬性,通過.Builder().build()的形式設置,比如超時時間設置:

    //1.構建OkHttpClient實例
    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(2, TimeUnit.SECONDS)//鏈接超時爲2秒,單位爲秒
            .writeTimeout(2, TimeUnit.SECONDS)//寫入超時
            .readTimeout(2, TimeUnit.SECONDS)//讀取超時
            .build();

    //2.通過Builder輔助類構建請求對象
    final Request request = new Request.Builder()
            .url("http://httpbin.org/delay/10")//URL地址
            .build();//構建

    //創建線程,在子線程中運行
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //3.通過mOkHttpClient調用請求得到Call
                final Call call = okHttpClient.newCall(request);
                //4.執行同步請求,獲取響應體Response對象
                Response response = call.execute();
                Log.e(TAG, "請求(超時)==" + response);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "請求(超時)==" + e.toString());
            }
        }
    }).start();

我這裏設置了請求10秒後才請求成功,請求超時時間爲2秒,讀寫超時時間爲2秒,網絡請求是耗時的請求操作,需要另外開子線程運行,拋出了超時異常,測試一下效果:

這裏引出了http請求的幾個重要的角色,Request是OKHttp的訪問請求,Builder是訪問輔助類,Response是OKHttp的請求響應

Request(請求):每一個HTTP請求中,都應該包含一個URL,一個GET或POST方法,以及Header和其他參數,還可以包含特定內容類型的數據流。

Responses(響應):響應則包含一個回覆代碼(200代表成功,404代表未找到),Header和定製可選的body。
另外可以根據response.code()獲取返回的狀態碼。

OKHttp:簡單來說,通過OkHttpClient可以發送一個http請求,並且可以讀取該請求的響應,它是一個生產Call的工廠。收益於一個共享的響應緩存/線程池/複用的鏈接等因素,絕大多數應用使用一個OKHttpClient實例,便可滿足整個應用的Http請求

(3)HTTP頭部的設置和讀取

HTTP頭部的數據結構是Map<String,List<String>>類型,也就是說,對於每個HTTP的頭,可能有多個值,但是大部分的HTTP頭只有一個值,只有少部分HTTP頭允許多個值,至於name的取值說明請參考:HTTP頭部參數說明

OKHTTP的處理方式是:

  • header(name, value)            來設置HTTP頭的唯一值, 如果請求中已經存在響應的信息那麼直接替換掉;
  • addHeader(name, value)     來補充新值,如果請求頭中已經存在name的name-value, 那麼還會繼續添加,請求頭中便會存在多個name相同value不同的“鍵值對”;
  • header(name, value)            讀取唯一值或多個值的最後一個值;
  • headers(name)                     獲取所有值。
    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")//設置唯一值
            .addHeader("Server", "application/json; q=0.5")//設置新值
            .addHeader("Server", "application/vnd.github.v3+json")//設置新值
            .build();

    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求(HTTP頭)異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            Log.e(TAG, "header:Date==" + response.header("Date"));
            Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
            Log.e(TAG, "headers:Server==" + response.headers("Server"));
            Log.e(TAG, "headers:Vary==" + response.headers("Vary"));

            Log.e(TAG, "Post請求(HTTP頭)異步響應Success==" + response.body().string());
        }
    });

Request請求中通過.Builder().build()的形式設置url(請求地址),也可以設置該請求的頭部信息,response的body有很多種輸出方法,string()只是其中之一,注意是string()不是toString()。如果是下載文件就是response.body().bytes()。我們來看看打印出來的結果:

(4)GET請求(同步)

new Thread(new Runnable() {
        @Override
        public void run() {
            //通過Builder輔助類構建請求對象
            Request request = new Request.Builder()
                    .get()//get請求
                    .url("https://www.baidu.com")//請求地址
                    .build();//構建

            try {
                //通過mOkHttpClient調用請求得到Call
                final Call call = mOkHttpClient.newCall(request);
                //執行同步請求,獲取Response對象
                Response response = call.execute();

                if (response.isSuccessful()) {//如果請求成功
                    String string = response.body().string();
                    Log.e(TAG, "get同步請求success==" + string);
                    //響應體的string()對於小文檔來說十分方便高效,但是如果響應體太大(超過1M),應避免使用string()方法,
                    //因爲它會把整個文檔加載到內存中,對用超多1M的響應body,應該使用流的方式來處理。

                    //response.body().bytes();//字節數組類型
                    //response.body().byteStream();//字節流類型
                    //response.body().charStream();//字符流類型

                    printHeads(response.headers());
                } else {
                    Log.e(TAG, "get同步請求failure==");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();

   /**
     * 打印請求頭信息
     *
     * @param headers 請求頭集合
     */
    private void printHeads(Headers headers) {
        if (headers == null) return;
        for (int i = 0; i < headers.size(); i++) {
            Log.e(TAG, "請求頭==" + headers.name(i) + ":" + headers.value(i));
        }
    }

在請求Request中聲明爲GET請求,設置url請求地址,調用實例mOkHttpClient.new Call(request)調用請求,返回Call,通過同步方法call.execute()同步執行,這裏需要開啓一個子線程運行。需要手動通過response.isSuccessful()判斷請求是否成功,同時通過響應體得到Heander打印了請求頭的相關信息,看看得出的結果:

(5)GET請求(異步)

同步和異步不一樣的地方是Call對象調用的方法,call.enqueue(callback)實現函數回調成功和失敗兩方法,異步方法可以不用開啓子線程執行。

注意:同步是阻塞式的,串聯執行,同步發生在當前線程內。異步是併發式的,會再次創建子線程處理耗時操作。

    //通過Builder輔助類構建Request對象,鏈式編程
    Request request = new Request.Builder()
            .url("https://www.baidu.com")
            .get()
            .build();
    //異步
    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            e.printStackTrace();
            Log.e(TAG, "get異步響應失敗==" + e.toString());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            //主線程中更新UI
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //TODO 在主線程中更新UI的操作
                }
            });

            Log.e(TAG, "get異步當前線程,線程id==" + Thread.currentThread().getId());
            String result = response.body().string();
            Log.e(TAG, "get異步響應成功==" + result);
            printHeads(response.headers());
        }
    });

    Log.e(TAG, "主線程,線程id==" + Thread.currentThread().getId());

打印log如下:

onFailure()onResponse()分別是在請求失敗和成功時會調用的方法。這裏有個要注意的地方,onFailure()onResponse()是在異步線程裏執行的,所以如果你在Android把更新UI的操作寫在這兩個方法裏面是會報錯的,這個時候可以用runOnUiThread這個方法進行更新UI操作。

(6)POST提交String請求(同步)

post請求創建request和get是一樣的,只是post請求需要提交一個表單,就是RequestBody。表單的格式有好多種,post請求提交參數需要構建RequestBody對象,post提交的過程需要將提交的內容封裝到一個RequestBody中,同時需要設置類型MediaTypeMediaType用於描述Http請求和響應體的內容類型,也就是Content-Type,通過.Builder().build()的形式設置URL(請求地址),RequestBody(參數容器)。

    //構建RequestBody對象,post提交的過程需要將提交的內容封裝到一個RequestBody中
    //MediaType用於描述Http請求和響應體的內容類型,也就是Content-Type
    MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
    RequestBody requestBody = RequestBody.create("提交的內容", mediaType);
    final Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.e(TAG, "Post請求String同步響應success==" + response.body().string());
                } else {
                    Log.e(TAG, "Post請求String同步響應failure==" + response.body().string());
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "Post請求String同步響應failure==" + e.getMessage());
            }
        }
    }).start();

請求結果如下:

RequestBody的數據格式都要指定Content-Type,常見的有三種:

  • application/x-www-form-urlencoded 數據是個普通表單;
  • multipart/form-data                           數據裏有文件;
  • application/json                                 數據是個json。
 MediaType mediaType = MediaType.parse("image/png");
 RequestBody requestBody = RequestBody.create(xxx.png, mediaType);

改變MediaType 中的內容即可設置不同的內容類型,比如image/png表示便攜式網絡圖形(Portable Network Graphics,PNG)是一種無損壓縮的位圖圖形格式,支持索引、灰度、RGB三種顏色方案以及Alpha通道等特性。

(7)POST提交String請求(異步)

    RequestBody requestBody = RequestBody.create("提交內容", MediaType.parse("text/plain; charset=utf-8"));
    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求String異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String string = response.body().string();
            Log.e(TAG, "Post請求String異步響應success==" + string);
        }
    });

異步請求主要是有回調方法,和其他部分和同步差不多,我們來看看效果:

(8)POST提交鍵值對請求(異步)

請求參數提交鍵值對需要用到FormBody,FormBody繼承自RequestBody,通過.add("key", "value")形式添加:

    //提交鍵值對需要用到FormBody,FormBody繼承自RequestBody
    FormBody formBody = new FormBody.Builder()
            //添加鍵值對(通多Key-value的形式添加鍵值對參數)
            .add("key", "value")
            .build();
    final Request request = new Request.Builder()
            .post(formBody)
            .url("url")
            .build();

        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求(鍵值對)異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post請求(鍵值對)異步響應Success==" + result);
        }
    });

(9)POST提交文件請求(異步)

post提交文件,將文件傳入RequestBody中即可:

    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
    Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求(文件)異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post請求(文件)異步響應Success==" + result);
        }

請求效果如下:源碼中有例子

(10)POST提交表單請求(異步)

 使用FormEncodingBuilder來構建和HTML標籤相同效果的請求體,鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼

   //使用FormEncodingBuilder來構建和HTML標籤相同效果的請求體,鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼
    FormBody 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();

        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求(表單)異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post請求(表單)異步響應Success==" + result);
        }
    });

(11)POST提交流請求(同步)

以流的方式Post提交請求體,請求體的內容由流寫入產生,這裏是流直接寫入OKIO的BufferedSink。你的程序可能會使用OutputStream, 你可以使用BufferedSink.outputStream()來獲取,這裏需要重寫RequestBody中的幾個方法,將本地數據放入到Http協議的請求體中,然後發送到服務器。

    //以流的方式Post提交請求體,請求體的內容由流寫入產生,這裏是流直接寫入OKIO的BufferedSink。
    // 你的程序可能會使用OutputStream, 你可以使用BufferedSink.outputStream()來獲取
    final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
    //重寫RequestBody中的幾個方法,將本地數據放入到Http協議的請求體中,然後發送到服務器
    final RequestBody requestBody = new RequestBody() {
        @Nullable
        @Override
        public MediaType contentType() {
            //返回內容類型
            return mediaType;
        }

        @Override
        public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
            //輸入數據頭
            bufferedSink.writeUtf8("Numbers\\n");
            bufferedSink.writeUtf8("-------\\n");

            //構造數據
            for (int i = 2; i < 997; i++) {
                bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
        }
    };

    //構建請求
    final Request request = new Request.Builder().
            url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        //開啓線程
        new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    String result = response.body().toString();
                    Log.e(TAG, "Post請求(流)異步響應Success==" + result);
                } else {
                    Log.e(TAG, "Post請求(流)異步響應failure==" + response);
                    throw new IOException("Unexpected code " + response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();

    private String factor(int n) {
        for (int i = 2; i < n; i++) {
            int x = n / i;
            if (x * i == n) {
                return factor(x) + "x" + i;
            }
        }
        return Integer.toString(n);
    }

請求結果如下:

(12)POST提交分塊請求(異步)

MultipartBuilder可以構建複雜的請求體,與HTML文件上傳形式兼容,多塊請求頭中的每個請求體都是一個親求體,可以定義自己的請求體,這些請求體可以用來描述這塊請求,例如他的Content-Disposition,如果Content-Length和Content-Type可用的話,他們會被自動添加到請求頭中。

    MediaType mediaType = MediaType.parse("image/png");
    String IMGUR_CLIENT_ID = "...";
    //構建body
    MultipartBody multipartBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
                    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(multipartBody)
            .build();
        
    //執行請求
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post請求(分塊)異步響應failure==" + e.getMessage());
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post請求(分塊)異步響應Success==" + result);
        }
    });

請求結果如下:

如果你上傳一個文件不是一張圖片,但是MediaType.parse("image/png")裏的"image/png"不知道該填什麼,可以參考下這個頁面

至此,本文結束!

源碼地址:https://github.com/FollowExcellence/Rxjava_Retrofit

轉載請聲明出處:https://mp.csdn.net/postedit/101029208  謝謝!

相關文章:

Retrofit2詳解和使用(一)

  • Retrofit2的介紹和簡單使用

OKHttp3的使用和詳解

  • OKHttp3的用法介紹和解析

OKHttp3源碼詳解

  • 從源碼角度解釋OKHttp3的關鍵流程和重要操作

RxJava2詳解(一)

  • 詳細介紹了RxJava的使用(基本創建、快速創建、延遲創建等操作符)

RxJava2詳解(二)

  • RxJava轉換、組合、合併等操作符的使用

RxJava2詳解(三)

  • RxJava延遲、do相關、錯誤處理等操作符的使用

RxJava2詳解(四)

  • RxJava過濾、其他操作符的使用

上述幾篇都是android開發必須掌握的,後續會完善其他部分!

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