OkHttp、rxJava、Retrofit聯合網路請求(一)

筆墨Android發表於2018-09-05

現在基本上所有的網路框架都採用Okhttp、rxjava、retrofit三者一起寫的。因為最近沒有什麼事情,就抽空總結一下這方面的知識:因為這些東西連在一期講的話,很多同學會覺得懵逼,所以這裡我準備先講一下每一個東西的用法,然後在講解一下怎麼聯合使用。

最近看了 《X特遣隊/自殺小隊》 覺得不錯。以一種混子的心態生活,其實挺輕鬆的!所以一張圖片鎮樓!習慣的我可以發給你!

OkHttp、rxJava、Retrofit聯合網路請求(一)

本文知識點

  • OkHttp的使用
  • OkHttp上傳檔案
  • OkHttp的一些高階用法

1. OkHttp的簡單使用

在這裡先來個重要的說明:網路許可權一定要加,一定要加!!!

其實關於OkHttp的使用只要記住一個順序就可以

  • 建立OkHttpClient物件
  • 建立請求Request內容
  • 傳送請求
  • 建立請求的回撥

基本上記住上面的步驟就可以實現簡單的請求了!

1.1 簡單的GET請求

既然上面都提到了相應的步驟,我們就按照上面的步驟寫一下就可以了!!!

1.1.1 建立OkHttpClient物件

OkHttpClient httpClient = new OkHttpClient();
複製程式碼

建立一個物件而已,沒有什麼好說的!!!

1.1.2 建立請求Request內容

 Request request = new Request.Builder()
                .method("GET", null)
                .url("https://www.baidu.com/")
                .build();
複製程式碼

這裡簡單說一下,method是設定相應的請求方式的;url是設定相應的請求地址的!其次Request是一個構建者的構建模式。剩下的沒有什麼好說的。,如果新手,不用管那麼多為什麼,實現效果才是重要的!!!

1.1.3 傳送請求

Call call = httpClient.newCall(request);
複製程式碼

這裡其實就是讓httpClient知道自己要請求什麼而已

1.1.4 建立請求返回的內容

    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.e(TAG, "請求失敗的原因:" + e);
        }

        @Override
        public void onResponse(Call call, final Response response) throws IOException {
            Headers headers = response.headers();
            Set<String> names = headers.names();
            for (String name : names) {
                Log.e(TAG, "請求的header" + name);
                String value = headers.get(name);
                Log.e(TAG, "值為: " + value + "\n----------------------------------");
            }

            final String date = response.body().string();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mTvShow.setText(date);
                }
            });
        }
    });
複製程式碼

這裡要說明的就多了:

  1. 當你的看到FATAL EXCEPTION: OkHttp Dispatcher這個異常的時候,恭喜你,你踩到第一個坑了!這個主要是因為response.body().string()只能呼叫一次,如果你在程式碼中呼叫了兩次,那麼就會出現上面的異常;

  2. 當你非同步請求的時候,是不能在子執行緒修改UI的,所以這裡我用了一個Handler去操作相應的內容

  3. 如果你想看相應的一些內容的話,那麼看那個for迴圈那裡,你列印一下,就能看到如下的內容,如果不怎麼理解的話,找你們後臺人員請教一下!一定要虛心哦。

    Accept-Ranges →bytes
    Cache-Control →no-cache
    Connection →Keep-Alive
    Content-Length →227
    Content-Type →text/html
    Date →Wed, 05 Sep 2018 03:41:58 GMT
    Etag →"5b7b7f40-e3"
    Last-Modified →Tue, 21 Aug 2018 02:56:00 GMT
    Pragma →no-cache
    Server →BWS/1.1
    Set-Cookie →BD_NOT_HTTPS=1; path=/; Max-Age=300
    Strict-Transport-Security →max-age=0
    X-Ua-Compatible →IE=Edge,chrome=1
    複製程式碼
  4. 如果失敗的話,那麼就會在onFailure中把異常反饋給你!!!

給你貼下整體程式碼吧!

        /*1.建立OkHttpClient物件*/
        OkHttpClient httpClient = new OkHttpClient();
        /*2.建立請求Request內容*/
        Request request = new Request.Builder()
                .method("GET", null)
                .url("https://www.baidu.com/")
                .build();
        /*3.傳送請求*/
        Call call = httpClient.newCall(request);
        /*4.建立請求的回撥*/
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e(TAG, "請求失敗的原因:" + e);
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                Headers headers = response.headers();
                Set<String> names = headers.names();
                for (String name : names) {
                    Log.e(TAG, "請求的header" + name);
                    String value = headers.get(name);
                    Log.e(TAG, "值為: " + value + "\n----------------------------------");
                }


                final String date = response.body().string();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mTvShow.setText(date);
                    }
                });
            }
        });
複製程式碼

以上步驟就能正常請求相應的資料了,如果還沒有資料的話,好好看看程式碼!

1.2 簡單的POST請求

關於POST請求的話,基本上就是比GET請求多一步設定表單的方法,也就是一個FormBody物件的設定,以key、value的方式設定表單而已,所以這裡教你怎麼寫,然後我貼一下程式碼就那麼滴了,誰讓我那麼懶呢!!!

表單的寫法是這樣的:

FormBody formBody = new FormBody.Builder()
                .add("key", "value")
                .build();
複製程式碼

其實add方法可以被呼叫多次,新增相應的key和value;

整體的程式碼是這樣的!!!

        /*1.建立OkHttpClient物件*/
        OkHttpClient httpClient = new OkHttpClient();
        /*2.建立相應的表單內容*/
        FormBody formBody = new FormBody.Builder()
                .add("key", "value")
                .build();
        /*3.建立請求Request內容*/
        Request request = new Request.Builder()
                .url("https://www.baidu.com/")
                .post(formBody)
                .build();
        /*4.傳送請求*/
        Call call = httpClient.newCall(request);
        /*5.建立請求的回撥*/
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e(TAG, "請求失敗的原因:" + e);
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                Headers headers = response.headers();
                Set<String> names = headers.names();
                for (String name : names) {
                    Log.e(TAG, "請求的header" + name);
                    String value = headers.get(name);
                    Log.e(TAG, "值為: " + value + "\n----------------------------------");
                }


                final String date = response.body().string();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mTvShow.setText(date);
                    }
                });
            }
        });
    }
複製程式碼

POST和GET請求只是請求的方式不同,POST比較安全,所有內容都依靠表單傳遞!

2. OkHttp3進行檔案上傳

在這裡先來個重要的說明:去寫SD卡的許可權一定要加,一定要加!!!

說到檔案上傳,一般的網路請求都帶有檔案上傳的功能,其實OkHttp3也可以上傳檔案,具體操作步驟如下:

  • 建立OkHttpClient物件
  • 建立請求Request內容和所有所需引數(這裡和其他的請求不同的地方)
    • 獲取檔案
    • 設定上傳檔案的型別
    • 獲取請求體
  • 建立請求
  • 建立請求的回撥

因為其他的內容都差不多,只有關於表單的內容不通,所以這裡著重講一下關於這個表單的問題。

 RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("title", "張三")
                .addFormDataPart("image", "zhangsan.jpg", RequestBody.create(MediaType.parse("application/octet-stream"), new File(Environment.getExternalStorageDirectory().getParent() + "/0/123.png")))
                .build();
複製程式碼

一般這種上傳檔案,基本上都是傳遞相應的使用者圖片,修改圖片什麼的!因為伺服器要根據你上傳的這張圖片進行相應圖片的替換。回來說上面那個配置:

  1. 上面第一個"title"那個引數的話,應該是一組key、value形式,基本上是根據伺服器定的引數為準,可以有多組!
  2. 第二個"image"那個引數的話,基本上就是這個形式的key、圖片名稱、圖片位置。這樣就能鎖定一張圖片了,這樣就構建出一個相應的RequestBody物件了。

整體程式碼是這樣的:

        /*1.建立OkHttpClient物件*/
        OkHttpClient httpClient = new OkHttpClient();
        /*2.建立相應的表單內容*/
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("title", "張三")
                .addFormDataPart("image", "zhangsan.jpg", RequestBody.create(MediaType.parse("application/octet-stream"), new File(Environment.getExternalStorageDirectory().getParent() + "/0/123.png")))
                .build();

        /*3.建立請求Request內容*/
        Request request = new Request.Builder()
                .header("key", "value")
                .url("https://www.baidu.com/")
                .post(requestBody)
                .build();

        /*4.傳送請求*/
        Call call = httpClient.newCall(request);
        /*5.建立請求的回撥*/
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e(TAG, "onFailure: " + e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.e(TAG, "onResponse: " + response.body().string());
            }
        });
複製程式碼

對了忘說了一點,圖片是以流的形式進行傳遞的。所以上面"application/octet-stream"配置的是這種格式,如果是其他的格式呢?給大家一份對照表:參照一下就OK了。

引數 說明
text/html HTML格式
text/plain 純文字格式
text/xml XML格式
image/gif gif圖片格式
image/jpeg jpg圖片格式
image/png png圖片格式
application/xhtml+xml XHTML格式
application/xml XML資料格式
application/atom+xml Atom XML聚合格式
application/json JSON資料格式
application/pdf pdf格式
application/msword Word文件格式
application/octet-stream 二進位制流資料

基本上你把上面的程式碼改吧改吧就能上傳檔案了!!!就醬紫簡單。。。

3. OkHttp的高階配置

3.1 OkHttp一些基本引數的配置

配置請求時間和連線超時的時間等等

    OkHttpClient httpClient = new OkHttpClient.Builder()
            //設定相應的連線池
            .connectionPool(new ConnectionPool())
            //連線超時
            .connectTimeout(15, TimeUnit.SECONDS)
            //寫入超時
            .writeTimeout(15, TimeUnit.SECONDS)
            //讀取超時
            .readTimeout(20, TimeUnit.SECONDS)
            .build();
複製程式碼

3.2 OkHttp攔截器的一些簡單理解

往往在專案中,都會有一些關於公共請求引數的一些問題,這裡就會用到相應的OkHttp攔截器!什麼是攔截器呢?簡單點說就和埋點差不多。在請求的時候,會走每一個攔截器!想新增什麼就新增什麼,這裡我們通過幾個例項講解一下你就能大概理解了!

3.2.1 日誌攔截器

先看下程式碼,然後我在做一下相應的解釋:

public class LogInterceptor implements Interceptor {

    private static final String TAG = LogInterceptor.class.getSimpleName();

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        
        /*這樣就能在請求之前列印相應的內容了*/
        Log.e("url", String.format("Sending request %s on %s %n %s", request.url(), chain.connection(), request.headers()));

        /*其實下面這個chain.proceed(request)這個方法,代表請求前和請求後*/
        return chain.proceed(request);
    }
}
複製程式碼

這裡就是直接列印了一個相應的LOG,可以獲取到一些請求的引數,這裡說明一下:

  1. 當你的請求為GET請求的時候只能列印一個Url地址,也就是request.url()的值了,飲後後面的headers獲取到的內容為空,因為GET請求沒有相應的表單資訊;
  2. chain.connection()當你使用除了日誌攔截器的時候,就會返回空
  3. chain.proceed(request)代表請求響應的結果,所以說明你也是可以修改返回結果的!!!

3.2.2 重定向一個網址連結的攔截器

這個說來就有意思了,當你請求攔截器的時候,正常應該返回百度返回的內容,但是如果你修改了連結的地址會怎麼樣呢?當然就會返回你修改之後的返回地址了。。。我們看看怎麼實現的

public class ResetInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {

        Request newRequest = new Request.Builder()
                .method("GET", null)
                .url("https://fanyi.baidu.com/translate?aldtype=16047&query=%E8%BF%9B%E5%BA%A6%0D%0A&keyfrom=baidu&smartresult=dict&lang=auto2zh#zh/en/%E9%87%8D%E7%BD%AE")
                .build();

        return chain.proceed(newRequest);
    }
}
複製程式碼

對,你沒有看錯,就這麼赤裸裸的換了一個url地址,其實Request request = chain.request();這個方法,返回的Request就是在建立的時候,建立的Request,所以,這裡你直接,通過攔截器,直接建立一個新的,直接返回就可以了,就沒有之前的Request什麼事情了!!!其實就相當於你把之前的內容重新寫了一遍!就醬紫了。。。

3.2.3 新增相應的公共請求引數

其實這個的實現和上面的差不多,也就是替換相應的Request的內容!但是這裡你要考慮一個問題,就是GET請求和POST請求的處理方式應該是不同的,多以這裡要分情況去處理。否則不能達到你想要的效果的!所以這裡我們分開說。先說明一下,GET請求是在Url後面拼接相應的引數,而POST請求是在form表單中新增相應的引數,所以方式一定是不一樣的!!!

1. GET請求新增公共請求引數

先來一段程式碼體驗一下:

    HttpUrl build = originalRequest.url().newBuilder()
            .addQueryParameter("key1", "value1")
            .addQueryParameter("key2", "value2")
            .addQueryParameter("key3", "value3")
            .addQueryParameter("key4", "value4")
            .addQueryParameter("key5", "value5")
            .build();

    Request request = originalRequest.newBuilder().url(build).build();
複製程式碼

這樣就可以新增相應的公共請求引數了,其實開始的時候,我以為newBuilder()是建立一個新的內容呢?其實它是拿到之前的內容,然後把下面的內容新增進去。所以這裡其他的內容是不會收到影響的!!!

其實GET請求就是在URL後面追加上相應的引數。

2. POST請求新增公共請求引數

還是先來一點程式碼體驗一下:

Request requestBuilder = originalRequest.newBuilder()
        .addHeader("key1", "value1")
        .addHeader("key2", "value2")
        .addHeader("key3", "value3")
        .addHeader("key4", "value4")
        .addHeader("key5", "value5")
        .build();
複製程式碼

和上面的類似,只是寫法不同而已!因為POST請求新增的是相應的header。

整體的程式碼如下:

public class PublicInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        if ("GET".equals(request.method())) {
            //GET請求的處理
            HttpUrl build = request.url().newBuilder()
                    .addQueryParameter("key1", "value1")
                    .addQueryParameter("key2", "value2")
                    .addQueryParameter("key3", "value3")
                    .addQueryParameter("key4", "value4")
                    .addQueryParameter("key5", "value5")
                    .build();

            request = request.newBuilder().url(build).build();
        } else if ("POST".equals(request.method())) {
            request = request.newBuilder()
                    .addHeader("key1", "value1")
                    .addHeader("key2", "value2")
                    .addHeader("key3", "value3")
                    .addHeader("key4", "value4")
                    .addHeader("key5", "value5")
                    .build();
        }

        return chain.proceed(request);
    }
}
複製程式碼

最後在把相應的Interceptor新增到OkHttp就好了。


2018年10月15日補充:

在POST請求中,請求引數應該新增到body中,所以上面程式碼是有問題的!

替換成下面這樣:

        if (originalRequest.body() instanceof FormBody) {
            // 構造新的請求表單
            FormBody.Builder builder = new FormBody.Builder();

            FormBody body = (FormBody) originalRequest.body();
            //將以前的引數新增
            for (int i = 0; i < body.size(); i++) {
                builder.add(body.encodedName(i), body.encodedValue(i));
            }
            //追加新的引數
            builder.add("key1", "value1");
            builder.add("key2", "value2");
            builder.add("key3", "value3");
            builder.add("key4", "value4");
            builder.add("key5", "value5");
            //構造新的請求體
            originalRequest = originalRequest.newBuilder().post(builder.build()).build();
        }
複製程式碼

對於以上的錯誤深表歉意,因為沒有弄清楚http中的一下內容,還請見諒!!!


基本上使用的時候就這麼多問題,可能有些講解不到的,如果有什麼不到位的,及時補充!!!有問題留言,我看到了一定會回覆你的!!!

github地址奉上

相關文章