Android 主流開源框架(二)OkHttp 使用詳解

文章首發於個人我的博客:wildma的博客,這裏有更好的閱讀體驗,歡迎關注。css

前言

最近有個想法——就是把 Android 主流開源框架進行深刻分析,而後寫成一系列文章,包括該框架的詳細使用與源碼解析。目的是經過鑑賞大神的源碼來了解框架底層的原理,也就是作到不只要知其然,還要知其因此然。html

這裏我說下本身閱讀源碼的經驗,我通常都是按照平時使用某個框架或者某個系統源碼的使用流程入手的,首先要知道怎麼使用,而後再去深究每一步底層作了什麼,用了哪些好的設計模式,爲何要這麼設計。java

系列文章:android

更多幹貨請關注 AndroidNotesgit

1、OkHttp 介紹

上一篇介紹了 HttpClient 與 HttpURLConnection,咱們知道 Google 在 Android 6.0 版本已經刪除了 HttpClient 的相關代碼,HttpURLConnection 用起來也比較麻煩,因此網絡框架 OkHttp 也就誕生了。github

OkHttp 是 Square 公司開源的網絡框架,能夠說是當前 Android 界最好用的網絡框架了,它有以下特色:json

  1. 封裝簡單易用,支持鏈式調用。
  2. 同時支持同步和異步請求。
  3. 支持 HTTP/2 協議,容許對同一主機的全部請求共用同一個 socket 鏈接。
  4. 若是 HTTP/2 不可用, 鏈接池複用技術能夠減小請求延遲。
  5. 支持 GZIP,減少了下載大小。
  6. 支持緩存處理,能夠避免重複請求。
  7. 若是你的服務有多個 IP 地址,當第一次鏈接失敗,OkHttp 會嘗試備用地址。
  8. OkHttp 還處理了代理服務器問題和SSL握手失敗問題。

2、OkHttp 的使用

2.1 使用前準備

  1. 加入網絡權限 在 AndroidManifest.xml 文件中加入以下:
<uses-permission android:name="android.permission.INTERNET"/>
複製代碼
  1. 添加 OkHttp 庫的依賴 在當前使用的 module 下的 build.gradle 中加入以下:
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
複製代碼

2.2 同步 GET 請求

同步 GET 請求的步驟:設計模式

  1. 建立 OkHttpClient 對象。
  2. 建立 Request 對象,而後經過 Builder() 鏈式調用能夠設置請求 url、header、method 等。
  3. 調用 OkHttpClient 對象的 newCall() 方法建立一個 Call 對象。
  4. 調用 Call 對象的 execute() 方法發起一個請求,並獲取服務器返回的數據。
  5. Response 就是返回的數據,能夠根據需求獲得相應的數據格式,例如: 但願返回 String,則調用 response.body().string(),適用於不超過 1 MB 的數據。 但願返回輸入流,則調用 response.body().byteStream(),適用於超過 1 MB 的數據,例以下載文件。 但願返回二進制字節數組,則調用 response.body().bytes()

須要注意的是:api

  1. 同步 GET 請求須要在子線程中調用。
  2. string() 方法只能調用一次,緣由是該方法在第一次調用完就關閉了流。

具體代碼以下:數組

private void syncGetRequestByOkHttp() throws Exception {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        Response response = call.execute();
        if (response.isSuccessful()) {
            Log.i(TAG, "syncGetRequestByOkHttp data-->" + response.body().string());
        } else {
            throw new IOException("Unexpected code " + response);
        }
    }
複製代碼

打印結果:

OkHttpActivity: syncGetRequestByOkHttp data-->
<!DOCTYPE html>
    <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css>
<title>百度一下,你就知道</title>
</head> <body link=#0000cc> 省略部分日誌...</body> 
</html>
複製代碼

2.3 異步 GET 請求

異步 GET 請求與同步 GET 請求的代碼差很少,區別是:

  1. 將同步方法 execute() 換成異步方法 enqueue()。
  2. 異步方法不須要在子線程中執行,由於 enqueue() 方法內部已經有一個線程池去執行。
  3. 返回的數據在 onResponse() 方法中,因爲內部是經過線程池去執行的,因此該方法也在子線程。若是須要操做 UI,須要使用 handler 等切換到主線程。

具體代碼以下:

private void asyncGetRequestByOkHttp() {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
    }
複製代碼

打印結果:與同步 GET 請求同樣。

2.4 異步 POST 請求

POST 請求與 GET 請求的區別是 POST 請求須要構建一個 RequestBody 來存放請求參數,而後在 Request.Builder 中調用 post 方法,並傳入 RequestBody 對象。具體代碼以下:

private void asyncPostRequestByOkHttp() {
        OkHttpClient client = new OkHttpClient();
        RequestBody formBody = new FormBody.Builder()
                .add("username", "wildma")
                .add("password", "123456")
                .build();
        Request request = new Request.Builder()
                .url("https://postman-echo.com/post")
                .post(formBody)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
    }
複製代碼

打印結果:

OkHttpActivity: onResponse data-->
{
 "args": {},
 "data": "",
 "files": {},
 "form": {
  "username": "wildma",
  "password": "123456"
 },
 "headers": {
  "x-forwarded-proto": "https",
  "host": "postman-echo.com",
  "content-length": "31",
  "content-type": "application/x-www-form-urlencoded",
  "user-agent": "Apache-HttpClient/UNAVAILABLE (java 1.4)",
  "x-forwarded-port": "443"
 },
 "json": {
  "username": "wildma",
  "password": "123456"
 },
 "url": "https://postman-echo.com/post"
}
複製代碼

2.5 異步 POST 方式上傳文件

上傳文件首先須要定義上傳文件的類型 MediaType,而後構建一個 File 的 RequestBody,其餘與普通 POST 請求相似。
其中 test.txt 爲 SD 卡跟目錄下的文件,內容爲「test post file」,須要提早放好。具體代碼以下:

private void asyncPostFileByOkHttp() {
        final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
        //test.txt 爲 SD 卡跟目錄下的文件,須要提早放好
        File file = new File(Environment.getExternalStorageDirectory(), "test.txt");
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, file);
        Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(requestBody)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure IOException-->" + e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
    }
複製代碼

注意,因爲須要操做 SD 卡數據,因此須要在 AndroidManifest.xml 文件添加讀寫權限,以下:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
複製代碼

若是是 6.0 以上還須要動態申請權限。

打印結果:

OkHttpActivity: onResponse data-->test post file
複製代碼

2.6 異步 POST multipart 請求

MultipartBody.Builder 能夠構建與 HTML 文件上傳表單兼容的複雜請求體。multipart 請求體的每一部分自己就是一個請求體,能夠定義本身的請求頭。也就是說一個接口可能須要同時上傳文件和其餘參數,這時候就可使用 MultipartBody.Builder 來構建複雜的請求體。具體代碼以下:

private void asyncPostMultipartRequestByOkHttp() {
        final String IMGUR_CLIENT_ID = "...";
        final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
        //test.png 爲 SD 卡跟目錄下的文件,須要提早放好
        File file = new File(Environment.getExternalStorageDirectory(), "test.png");
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("title", "test")
                .addFormDataPart("image", "test.png",
                        RequestBody.create(MEDIA_TYPE_PNG, file))
                .build();
        Request request = new Request.Builder()
                .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
                .url("https://api.imgur.com/3/image")
                .post(requestBody)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure IOException-->" + e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
    }
複製代碼

打印結果: 因爲這裏沒有合適的測試服務器,因此請求會走到 onFailure(),如需測試請換成本身的服務器。

2.7 設置超時時間

發送一個請求若是沒有響應則會使用超時結束 call,沒有響應多是客戶端或服務器問題,例如網絡慢致使請求失敗,OkHttp 能夠設置鏈接、讀取和寫入的超時時間。 以下請求 url 的延遲時間爲 2 秒,這時候我設置讀取的超時時間爲 1 秒,最終則會請求失敗走到 onFailure() 方法。具體代碼以下:

private void asyncGetRequestByOkHttpAndSetTimeout() {
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(1, TimeUnit.SECONDS)
                .build();
        Request request = new Request.Builder()
                .url("http://httpbin.org/delay/2") //該 url 的延遲時間爲 2 秒
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure IOException-->" + e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
    }
複製代碼

打印結果:

OkHttpActivity: onFailure IOException-->java.net.SocketTimeoutException: timeout
複製代碼

說明 socket 超時了。

2.8 取消請求

經過 Call.cancel() 能夠當即中止正在進行的 Call。若是一個線程正在寫請求或讀響應,它還將收到一個 IOException 異常。當一個 Call 不須要時,可使用 Call.cancel() 節約網絡資源,例如用戶離開一個界面時。同步和異步調用均可以被取消。 以下請求 url 的延遲時間爲 2 秒,這時候我在請求的同時立刻取消請求。具體代碼以下:

private void asyncGetRequestByOkHttpAndCancelRequest() {
        OkHttpClient client = 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") //該 url 的延遲時間爲 2 秒
                .build();
        mCall = client.newCall(request);
        mCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure IOException-->" + e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse data-->" + response.body().string());
                } else {
                    throw new IOException("Unexpected code " + response);
                }
            }
        });
        //請求的同時立刻取消請求
        mCall.cancel();
        Log.i(TAG, "asyncGetRequestByOkHttpAndCancelRequest isCanceled-->" + mCall.isCanceled());
    }
複製代碼

打印結果:

OkHttpActivity: asyncGetRequestByOkHttpAndCancelRequest isCanceled-->true
OkHttpActivity: onFailure IOException-->java.io.IOException: Canceled
複製代碼

第一行說明取消成功了,第二行說明一個線程正在寫請求或讀響應,這時候會走到 onFailure() 方法並收到一個 IOException 異常。

3、源碼

OkHttp 的使用 demo

相關文章
相關標籤/搜索