Android開發(fā)——你應(yīng)該要知道 OkHttp

android機(jī)器人.jpg

前言:現(xiàn)在做 Android 開發(fā),應(yīng)該絕大部分都是用 OkHttp 來做網(wǎng)絡(luò)請求的??梢?OkHttp 的強(qiáng)大。今天來總結(jié)下 OkHttp 的使用,OkHttp 中用到了大量的 Builder 模式,建議看此文章前先閱讀 Android開發(fā)---Builder 模式必知必會

OkHttp 學(xué)習(xí)之前,應(yīng)該對 Http 有一定的了解,可以參考下這里:你需要了解的HTTP知識都在這里了!

開頭引用官網(wǎng)的自我介紹:

HTTP是現(xiàn)代應(yīng)用網(wǎng)絡(luò)的方式。這是我們?nèi)绾谓粨Q數(shù)據(jù)和媒體。有效地進(jìn)行HTTP使您的東西加載更快,并節(jié)省帶寬。

OkHttp 有以下特點(diǎn):

  • HTTP / 2支持允許同一主機(jī)的所有請求共享套接字。
  • 連接池減少請求延遲(如果HTTP / 2不可用)。
  • 透明GZIP縮小下載大小。
  • 響應(yīng)緩存可以避免重復(fù)請求的網(wǎng)絡(luò)。

ok,我們開始來探究一下 OkHttp 的使用。

首先添加依賴

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

OkHttp 發(fā)起網(wǎng)絡(luò)請求非常簡單,具體步驟如下:

  • 構(gòu)建 OkHttpClient 客戶端
  • 構(gòu)建 Request 請求
  • 創(chuàng)建 Call 對象,發(fā)起網(wǎng)絡(luò)請求
  • 處理 Response 響應(yīng)結(jié)果

這么看來,只要搞懂這幾個關(guān)鍵類,跟調(diào)用請求的過程。對于 OkHttp 的基本使用就沒問題了。

一、Request

Request 對應(yīng)于 Http 的請求報文,查看源碼可以看到,Request 封裝了請求報文的一些字段。

發(fā)起網(wǎng)絡(luò)請求必然要配置這些請求報文字段,Request 采用 Builder 模式,通過 Builder 內(nèi)部類使我們靈活去配置 Request 對象,默認(rèn)初始化Request request = new Request.Builder().build();是配置了請求頭,請求方法GET,也可以在調(diào)用build()方法之前調(diào)用對應(yīng)方法進(jìn)行配置。如果需要對 其中的某個字段做特殊配置,可以Request.Builder builder = request.newBuilder() 拿到 Builder 對象進(jìn)行 "返廠重建"。

  • Http 請求地址 url
  • 請求方法,例如(GET,POST等等),配置請求方法時調(diào)用get(),post()等,內(nèi)部都是調(diào)用 method()方法傳入請求方法與請求體,并對是否需要請求體進(jìn)行判斷。
  • Headers:維護(hù) Http 消息的頭字段,內(nèi)部也是通過 Builder 模式構(gòu)建字符串鍵值對來維護(hù)消息頭。
  • Request初始化時默認(rèn)構(gòu)造了 Headers的 Builder 實(shí)例,我們初始化Request可以調(diào)用header()方法設(shè)置請求頭的name,value字段。其實(shí)內(nèi)部是調(diào)用了Headers的 Builder 實(shí)例的set()方法設(shè)置的,這種方法設(shè)置的 head表示name是唯一的。因?yàn)樵O(shè)置之前內(nèi)部調(diào)用了removeAll()方法先移除了相同的name對應(yīng)的value。
  • 如果想添加多個name相同的請求頭,可以調(diào)用addHeader()方法進(jìn)行設(shè)置,其實(shí)內(nèi)部是調(diào)用了Headers的 Builder 實(shí)例的add()方法設(shè)置的
  • 也可以調(diào)用 removeHeader()方法移除同一個name的所有請求頭
  • 可以通過 Requesthead()方法獲得指定請求頭,·其實(shí)內(nèi)部也是通過 Headersget()方法通過name獲取的value,headers()方法獲得同一個name的所有value值,以list形式返回
  • 請求體 RequestBody(一般用來向服務(wù)器提交數(shù)據(jù)請求時用到)
  • tag,設(shè)置請求標(biāo)簽,用于取消網(wǎng)絡(luò)請求
  • CacheControl 用于控制緩存使用。

二、構(gòu)建 RequestBody

用于攜帶數(shù)據(jù)請求服務(wù)器,一般就是在特定的請求才會攜帶請求頭,例如 POST,PUT等。查看 RequestBody源碼可以看到有五個 static 方法用于創(chuàng)建 RequestBody實(shí)例,第一個參數(shù)都是 MediaType 表示數(shù)據(jù)的 MIME 類型,第二個參數(shù)分別可選為:String,File,byte[]或者ByteString,也就是提交的數(shù)據(jù)內(nèi)容。

  • MediaType類封裝了三個字段:typesubtype、CharSet 。分別對應(yīng) MIME 類型中的 數(shù)據(jù)類數(shù)據(jù)類型下的子類,編碼類型。例如 text/x-markdown; charset=utf-8 ,text 代表文本大類(type),x-markdown代表文本類下的子類(subtype),charset=utf-8則表示采用UTF-8編碼,內(nèi)部封裝了一個解析方法parse(),只要傳入這些信息就能獲得MediaType 實(shí)例??梢詤⒖兼溄?a target="_blank" rel="nofollow">Media Types和MIME 參考手冊 平時比較常用的 MIME 類型如下:json :application/jsonxml:application/xmlpng:image/png,jpg: image/jpeg,gif:image/gif

除了直接使用靜態(tài)方法create() 構(gòu)建之外, RequestBody 還有兩個直接子類FormBody,MultipartBody,分別用于提交 Form 表單中的鍵值對和構(gòu)建分塊表單請求體(既可以添加表單,又可以也可以添加文件等二進(jìn)制數(shù)據(jù))

FormBody表單創(chuàng)建

通過內(nèi)部類 Builder 構(gòu)建 FormBody 實(shí)例,同時調(diào)用 add() 傳入需要傳的鍵值對,例如:

RequestBody formBody = new FormBody.Builder()
        .add("username", "張少林")
        .build();

構(gòu)建分塊表單請求體 MultipartBody

MultipartBody 也是通過內(nèi)部類 MultipartBody.Builder 動態(tài)構(gòu)建實(shí)例,構(gòu)建實(shí)例之前有以下幾個方法:

  • setType():設(shè)置MultipartBodyMediaType類型,一般會設(shè)置為MultipartBody.FORM,也就是multipart/form-data.
  • addFormDataPart(String name, String value):添加字符串鍵值對
  • addFormDataPart(String name, @Nullable String filename, RequestBody body):添加表單文件
  • 其中三個重載 addPart() 方法,分別添加請求體,其中的addPart(@Nullable Headers headers, RequestBody body)可以在添加RequestBody的時候,同時為其單獨(dú)設(shè)置請求頭

源碼淺析:查看MultipartBody.Builder源碼發(fā)現(xiàn),addFormDataPart(String name, String value)內(nèi)部其實(shí)都是調(diào)用了addPart(Part part),而addPart(Part part)內(nèi)部是往全局實(shí)例化好的List<Part>中 add 實(shí)例化好的part。而MultipartBody.Part其實(shí)就是包含了RequestBody,所以可以說 MultipartBody是一個RequestBody的集合。

三、Response

Response對應(yīng)于 Http 的響應(yīng)報文,查看源碼可以看到,Request 封裝了響應(yīng)報文的一些字段。

封裝的字段有 對應(yīng)的請求報文Request ,響應(yīng)碼 code,響應(yīng)消息 message,響應(yīng)體ResponseBody,網(wǎng)絡(luò)請求響應(yīng)結(jié)果以及緩存響應(yīng)結(jié)果等字段。同樣的,Response 的構(gòu)建也是通過內(nèi)部類 Response.Builder 進(jìn)行動態(tài)靈活配置,對其中的一些常用方法做簡單介紹:

  • isSuccessful():用來判斷請求是否成功
  • body() :返回響應(yīng)體ResponseBody
  • headers():獲取響應(yīng)頭 Headers 對象

四、ResponseBody

通過 Responsebody()方法可以得到響應(yīng)體ResponseBody,響應(yīng)體必須最終要被關(guān)閉,否則會導(dǎo)致資源泄露、App運(yùn)行變慢甚至崩潰。

響應(yīng)體中的數(shù)據(jù)有可能很大,應(yīng)該只讀取一次響應(yīng)體的數(shù)據(jù)。調(diào)用ResponseBodybytes()string()方法會將整個響應(yīng)體數(shù)據(jù)寫入到內(nèi)存中,可以通過source()、byteStream()charStream()進(jìn)行流式處理。

五、OkHttpClient

OkHttp 網(wǎng)絡(luò)請求客戶端,封裝了 一些客戶端請求的常用配置,例如請求超時時間、連接超時時間、讀取超時時間、緩存目錄、代理等等。

同樣,OkHttpClient 也是通過內(nèi)部類 Builder 模式,動態(tài)靈活的配置參數(shù),從而構(gòu)建 OkHttpClient 實(shí)例。默認(rèn)初始化 OkHttpClient client = new OkHttpClient() 內(nèi)部已經(jīng)幫我們配置了一系列參數(shù),我們需要自己配置參數(shù)的話,只需要通過內(nèi)部類 Builder 實(shí)例,在調(diào)用 build()方法之前,調(diào)用對應(yīng)方法動態(tài)配置即可。OkHttpClient client = new OkHttpClient.Builder().build();例如我們動態(tài)配置讀取時間為30秒(默認(rèn)配置為10秒):

OkHttpClient client = new OkHttpClient.Builder()
        .readTimeout(30, TimeUnit.SECONDS)
        .build();

當(dāng)配置完成之后,我們需要對特定的請求配置不同的參數(shù),也可以OkHttpClient.Builder builder = okHttpClient.newBuilder();
拿到 Builder 類實(shí)例進(jìn)行"返廠重建"

當(dāng)然了,還有很多參數(shù)可以配置,具體可以查閱文檔 OkHttpClient參數(shù)配置

注意:在創(chuàng)建 OkHttpClient 時,應(yīng)當(dāng)保持全局單例, 每一個 OkHttpClient 實(shí)例都保持著自己的連接池和線程池 ,重用連接池和線程池會節(jié)省一些內(nèi)部資源,假如每次請求都去創(chuàng)建新的客戶端實(shí)例,就會造成一定的資源浪費(fèi)

六、創(chuàng)建 Call 對象,發(fā)起網(wǎng)絡(luò)請求

查閱 OkHttpClient 源碼,我們看到有個 newCall()方法,傳入 Request 實(shí)例,返回一個 Call 對象。我們暫且不用考慮它的原理,只知道可以用它來發(fā)起網(wǎng)絡(luò)請求,返回 Response 響應(yīng)報文。點(diǎn)擊進(jìn)去查看 Call 接口,可以看到有兩個方法是用來請求網(wǎng)絡(luò)的。

  • execute():同步的網(wǎng)絡(luò)請求,返回 Response 響應(yīng)結(jié)果,由于同步請求會阻塞當(dāng)前線程,我們使用過程還需要開啟子線程,所以一般較少使用。
  • enqueue(Callback responseCallback):加入隊(duì)列,也就是異步網(wǎng)絡(luò)請求,傳入回調(diào)接口,發(fā)起網(wǎng)絡(luò)請求時,請求成功會回掉Callback 對象的 onResponse 方法,同時返回 Response 響應(yīng)報文,對結(jié)果進(jìn)行處理。請求失敗則回調(diào)Callback對象的onFailure方法,對失敗進(jìn)行處理。由于異步請求不會阻塞當(dāng)前線程,所以一般使用異步請求,注意:不管是成功還是失敗的回調(diào)方法都是在子線程,不能直接進(jìn)行 ui 操作。
  • isCanceled() 方法用于判斷網(wǎng)絡(luò)請求是否取消
  • isExecuted()方法用于判斷網(wǎng)絡(luò)請求是否已經(jīng)執(zhí)行
  • request()返回發(fā)起該請求的 Request 請求報文
  • clone()方法克隆一個 Call 對象

七、實(shí)際使用

至此,已經(jīng)理清了 OkHttp 的關(guān)鍵類以及網(wǎng)絡(luò)請求步驟,現(xiàn)在對于 OkHttp 的基本使用已經(jīng)沒多大問題了,下面舉例幾個實(shí)際場景使用,滴滴滴,開車~~~

別忘了添加網(wǎng)絡(luò)權(quán)限:

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

同步 GET 請求

  private void sync_get() throws IOException {
        //構(gòu)建okHttp客戶端
        OkHttpClient okHttpClient = new OkHttpClient();
        //構(gòu)建請求報文
        Request request = new Request.Builder()
                .url("http://www.baidu.com")//配置請求百度首頁地址
                .build();

        //發(fā)起同步請求,返回響應(yīng)報文
        Response response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            //獲取Headers 請求頭對象
            Headers headers = response.headers();
            for (int i = 0; i < headers.size(); i++) {
                //分別獲取請求頭的name,value值
                String headName = headers.name(i);
                String headValue = headers.value(i);
            }
            //將響應(yīng)體中的數(shù)據(jù)轉(zhuǎn)為 String ,讀取到內(nèi)存
            final String string = response.body().string();
        } else {
            //失敗處理
        }
    }

注意: 同步請求必須開啟子線程,我這里省略部分代碼

異步 GET 請求

  private void async_get() {
        //構(gòu)建okHttp客戶端
        OkHttpClient okHttpClient = new OkHttpClient();
        //構(gòu)建請求報文
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .build();
        //發(fā)起異步請求,返回響應(yīng)報文
        okHttpClient.newCall(request)
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        //失敗處理
                        Log.e(TAG, "onFailure: " + Thread.currentThread().getName());
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        Log.e(TAG, "onResponse: " + Thread.currentThread().getName());
                        //獲取Headers 請求頭對象
                        Headers headers = response.headers();
                        for (int i = 0; i < headers.size(); i++) {
                            //分別獲取請求頭的name,value值
                            String headName = headers.name(i);
                            String headValue = headers.value(i);
                        }
                        //將響應(yīng)體中的數(shù)據(jù)轉(zhuǎn)為 String ,讀取到內(nèi)存
                        final String string = response.body().string();
                    }
                });
    }

異步請求不需要自己開啟子線程,打印回調(diào)方法的線程名發(fā)現(xiàn),成功或者失敗回調(diào)默認(rèn)都是在子線程

  //06-21 22:52:01.567 30162-30277/com.sunnada.okhttpdemo E/MainActivity: onResponse: OkHttp http://www.baidu.com/...

POST 上傳 json 字符串

public void async_post() {
        //解析數(shù)據(jù)類型為json
        MediaType MEDIA_TYPE_JSON
                = MediaType.parse("application/json; charset=utf-8");
        //構(gòu)建OkHttp 客戶端
        OkHttpClient okHttpClient = new OkHttpClient();
        //構(gòu)建json數(shù)據(jù)源
        User user = new User("張少林", "123456");
        String json = new Gson().toJson(user);
        //初始化請求體
        RequestBody requestBody = RequestBody.create(MEDIA_TYPE_JSON, json);
        //初始化請求報文
        Request request = new Request.Builder()
                .url("http://192.168.1.104:8080/users/uploadJson")//配置服務(wù)端請求地址
                .post(requestBody)//配置請求方法,傳入請求體
                .build();
        //發(fā)起異步請求,回調(diào)處理結(jié)果
        okHttpClient.newCall(request)
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        //失敗回調(diào)
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                           if (!response.isSuccessful())
                            throw new IOException("Unexpected code " + response);
                        Log.e(TAG, "onResponse: " + response.body().string());
                    }
                });
    }

POST 上傳文件

    public void postFile() {
        //獲取手機(jī)中的圖片文件
        List<File> files = FileUtils.listFilesInDir(SDCardUtils.getSDCardPath() + "/Pictures/Screenshots", false);
        File file = files.get(1);
        String fileName = file.getName();
        //構(gòu)建OkHttp 客戶端
        OkHttpClient okHttpClient = new OkHttpClient();
        if (!file.exists()) {
            ToastUtils.showLong("文件不存在");
        } else {
            //構(gòu)建文件請求頭
            RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
            Request request = new Request.Builder()
                    .url("http://192.168.1.104:8080/upload")
                    .post(requestBody)
                    .build();
            okHttpClient.newCall(request)
                    .enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {

                        }

                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                              if (!response.isSuccessful())
                            throw new IOException("Unexpected code " + response);
                        Log.e(TAG, "onResponse: " + response.body().string());
                        }
                    });
        }
    }

POST 提交表單

  public void post_form() {
        //構(gòu)建okHttp客戶端
        OkHttpClient okHttpClient = new OkHttpClient();
        RequestBody formBody = new FormBody.Builder()
                .add("username", "張少林")
                .add("password", "123456")
                .build();
        Request request = new Request.Builder()
                .url("http://192.168.1.104:8080/users/register")
                .post(formBody)
                .build();
        okHttpClient.newCall(request)
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {

                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                      if (!response.isSuccessful())
                            throw new IOException("Unexpected code " + response);
                        Log.e(TAG, "onResponse: " + response.body().string());
                    }
                });
    }

POST 圖文上傳

public void post_img_title() {
        //獲取手機(jī)中的圖片文件
        List<File> files = FileUtils.listFilesInDir(SDCardUtils.getSDCardPath() + "/Pictures/Screenshots", false);
        File file = files.get(1);
        MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)//設(shè)置表單類型
                .addFormDataPart("title", "Logo")//添加表單鍵值對
                .addFormDataPart("image", "logo.png", RequestBody.create(MEDIA_TYPE_PNG, file))//添加表單文件
                .build();
        Request request = new Request.Builder()
                .url("xxxxxxx")
                .post(requestBody)
                .build();
        client.newCall(request)
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                      //請求失敗處理
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (!response.isSuccessful())
                            throw new IOException("Unexpected code " + response);
                        Log.e(TAG, "onResponse: " + response.body().string());
                    }
                });
    }

文件下載,這里下載一張圖片

 public void downloadImg(){
        OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder()
                .get()
                .url("https://www.baidu.com/img/bd_logo1.png")
                .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 {
                //拿到字節(jié)流
                InputStream is = response.body().byteStream();

                int len = 0;
                File file  = new File(Environment.getExternalStorageDirectory(), "image.png");
                FileOutputStream fos = new FileOutputStream(file);
                byte[] buf = new byte[128];

                while ((len = is.read(buf)) != -1){
                    fos.write(buf, 0, len);
                }

                fos.flush();
                //關(guān)閉流
                fos.close();
                is.close();
            }
        });
    }

滴滴,到站了,下車~~~

最后

結(jié)語:雖說正式開發(fā)中,一般會使用基于 OkHttp 封裝一層的網(wǎng)絡(luò)庫,例如 Retrofit 2,OkGo,底層還是使用OkHttp 做網(wǎng)絡(luò)請求的,個人覺得絕對有必要理清 OkHttp 的基本使用,有時間甚至可以自己封裝一個網(wǎng)絡(luò)框架出來也是個好的磨練。通過這次的總結(jié),加上看 OkHttp 的部分源碼,收獲不小,最大的感悟就是不應(yīng)該只會調(diào)用 api ,而應(yīng)該經(jīng)常去看看流行框架的源碼,好的框架源碼是值得一看的。計(jì)劃下次對 OkHttp 的緩存,以及攔截器做一下總結(jié)~

一點(diǎn)點(diǎn)學(xué)習(xí)總結(jié),一點(diǎn)點(diǎn)思考,如有不足還請指出,如果喜歡,小手一點(diǎn)給個贊~~

如需轉(zhuǎn)載請注明出處,謝謝~
http://m.itdecent.cn/u/52ccd7428abe

最后的最后,感謝樂于分享的大神,參考鏈接:

更多原創(chuàng)文章會在公眾號第一時間推送,歡迎掃碼關(guān)注 張少林同學(xué)

張少林同學(xué).jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容