重識OkHttp——更深入了解如何使用

本文的分析基于OkHttp3.4祭椰,不展示完整的代碼示例,具體可以查看這個官方例子或者項目中的samples乓旗。

OkHttp作為square公司出品的一個網(wǎng)絡(luò)請求框架禽最,應(yīng)該算是目前Android端最火爆的網(wǎng)絡(luò)框架了嗤攻。我公司目前的項目中采用的都是Rxjava結(jié)合Retrofit進行網(wǎng)絡(luò)請求的處理毛嫉,對于底層真正實現(xiàn)網(wǎng)絡(luò)請求的OkHttp關(guān)注的不是很多。最近探究了一下OkHttp的源碼妇菱,對OkHttp的使用有了一些新的認(rèn)識承粤,在此做一下總結(jié)。

1 OkHttp的優(yōu)點

OkHttp作為當(dāng)前Android端最火熱的網(wǎng)絡(luò)請求框架闯团,必然有很多的優(yōu)點辛臊。

  • 支持HTTP/2 協(xié)議,允許連接到同一個主機地址的所有請求共享Socket房交。這必然會提高請求效率彻舰。
  • 在HTTP/2協(xié)議不可用的情況下,通過連接池減少請求的延遲候味。
  • GZip透明壓縮減少傳輸?shù)臄?shù)據(jù)包大小刃唤。
  • 響應(yīng)緩存,避免同一個重復(fù)的網(wǎng)絡(luò)請求白群。

2 網(wǎng)絡(luò)處理3要素

對于客戶端來講尚胞,我們關(guān)注的就是把正確的請求發(fā)送到服務(wù)端并拿到結(jié)果來進行處理。在OkHttp中帜慢,我認(rèn)為可以分為3個部分:

  • Request類封裝客戶端發(fā)送的請求笼裳,包括請求的url,請求方法method(主要是GET和POST方法)粱玲、請求頭header以及請求體requestBody躬柬;
  • Response類封裝了服務(wù)器響應(yīng)的數(shù)據(jù),包括code抽减、message允青、body、header等胯甩。
  • OkHttpClient負責(zé)發(fā)送請求Request并通過同步或者異步的方式返回服務(wù)器的響應(yīng)Response昧廷,就好比是一個瀏覽器。

OkHttp中通過建造者模式來構(gòu)建OkHttpClient偎箫、Request和Response木柬。對于客戶端來講,我們不需要過多關(guān)注Response是如何構(gòu)建的淹办,因為這個是OkHttp對響應(yīng)結(jié)果進行了封裝處理眉枕。我們只關(guān)注請求Request和客戶端OkHttpClient如何構(gòu)建即可。

2.1 請求Request

Request采用建造者模式來配置url怜森,請求方法method速挑、header、tag和cacheControl副硅。

  • 設(shè)置url姥宝。可以是String類型恐疲、URL類型和HttpUrl類型腊满。最終都是用到HttpUrl類型。
  • 設(shè)置method培己,包含get碳蛋、post方法等。默認(rèn)的是get方法省咨。post方法要傳RequestBody肃弟,類似的還有delete、put零蓉、patch笤受。
  • 設(shè)置header,方法有addHeader(String name, String value)壁公、 removeHeader(String name)感论、header(String name, String value)、headers(Headers headers)紊册。headers(Headers headers)調(diào)用之后其它的header都會被移除比肄,只添加這一個header。而header(String name, String value)方法調(diào)用之后囊陡,其它與這個name同名的header都會被移除芳绩,只保留這一個header。
  • 設(shè)置tag撞反,設(shè)置tag可以用來取消這一請求妥色。如果未指定tag或者tag為null,那么這個request本身就會當(dāng)做是一個tag用來被取消請求遏片。
  • 設(shè)置cacheControl嘹害,這個是設(shè)置到請求頭中撮竿。用來替換其它name是"Cache-Control"的header。如果cacheControl是空的話就會移除請求頭中name是"Cache-Control"的header笔呀。
Request.png

OkHttp采用POST方法向服務(wù)器發(fā)送一個請求體幢踏,在OkHttp中這個請求體是RequestBody。這個請求體可以是:

  • String類型
  • Stream流類型
  • File文件類型
  • Form表單形式的key-value類型
  • 類似Html文件上傳表單的復(fù)雜請求體類型(多塊請求)许师。

RequestBody有幾個靜態(tài)方法用于創(chuàng)建不同類型的請求體:

//創(chuàng)建String類型的請求體
public static RequestBody create(MediaType contentType, String content)

//創(chuàng)建文件類型的請求體
 public static RequestBody create(final MediaType contentType, final File file) 

最終都是相當(dāng)于重寫了RequestBody的兩個抽象方法來寫入流房蝉,如果傳遞流類型的參數(shù),只要重寫這兩個抽象方法即可微渠。

  //對應(yīng)的是name為Content-Type的header
  public abstract MediaType contentType();

  //這個BufferedSink位于Okio包下搭幻,提供高效的寫入。
  public abstract void writeTo(BufferedSink sink) throws IOException;

  //在寫入的時候可以傳遞內(nèi)容的大小逞盆,如果不知道就返回-1即可檀蹋。
  public long contentLength() throws IOException {  return -1;}

例如,我們提交一個String:

String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

提交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();

提交流:

RequestBody requestBody = new RequestBody() {
       @Override
        public MediaType contentType() {
             return MEDIA_TYPE_MARKDOWN;
        }

        @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();

對于提交表單和分塊請求云芦,OkHttp提供了兩個RequestBody的子類续扔,FormBodyMultipartBody

2.1.1 表單FormBody

FormBody也是采用建造者模式, 這個很簡單焕数,添加key-value形式的鍵值對即可纱昧。
添加鍵值對有兩個方法:

//采用OkHttp默認(rèn)的編碼
public Builder add(String name, String value) 

//采用用戶要求的編碼
public Builder addEncoded(String name, String value)

例如:

  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();

2.1.2 分塊MultipartBody

MultipartBody也是采用建造者模式,MultipartBody.Builder可以構(gòu)建兼容Html文件上傳表單的復(fù)雜請求體堡赔。每一部分的多塊請求體都是它自身的請求體识脆,并且可以定義它自己的請求頭。如果存在的話善已,這些請求頭用來描述這部分的請求體灼捂。例如Content-Disposition、Content-Length 和 Content-Type如果可用就會被自動添加到頭换团。

MIME類型有:

  public static final MediaType MIXED = MediaType.parse("multipart/mixed");

  public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");

  public static final MediaType DIGEST = MediaType.parse("multipart/digest");

  public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");

有幾個主要的方法:


  //設(shè)置MIME類型悉稠,如MIXED(默認(rèn)的)
    public Builder setType(MediaType type) {}

  //添加請求體
    public Builder addPart(RequestBody body) {
      return addPart(Part.create(body));
    }

  //添加包含header的請求體
    public Builder addPart(Headers headers, RequestBody body) {
      return addPart(Part.create(headers, body));
    }

    //請求體添加表單
    public Builder addFormDataPart(String name, String value) {
      return addPart(Part.createFormData(name, value));
    }

    //請求體中包含文件
    public Builder addFormDataPart(String name, String filename, RequestBody body) {
      return addPart(Part.createFormData(name, filename, body));
    }

    //添加自己定義的part
    public Builder addPart(Part part) {
      if (part == null) throw new NullPointerException("part == null");
      parts.add(part);
      return this;
    }

例如提交一個圖片文件:

RequestBody requestBody = new MultipartBody.Builder()
         .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
         RequestBody.create(MEDIA_TYPE_PNG, 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(requestBody)
         .build();

2.2 客戶端OkHttpClient

OkHttpClient采用建造者模式,通過Builder可以配置連接超時時間艘包、讀寫時間的猛,是否緩存、是否重連想虎,還可以設(shè)置各種攔截器interceptor等卦尊。
建議在一個App中,OkHttpClient保持一個實例舌厨。一個OkHttpClient支持一定數(shù)量的并發(fā)岂却,請求同一個主機最大并發(fā)是5,所有的并發(fā)最大是64。這個與OkHttp中的調(diào)度器Dispatcher有關(guān)躏哩,可以設(shè)置并發(fā)數(shù)署浩。本文不對Dispatcher進行討論。

OkHttpClient okHttpClient=new OkHttpClient.Builder().build();

//如果不需要我們額外配置扫尺,可以使用默認(rèn)的配置
OkHttpClient okHttpClient1 = new OkHttpClient();

一個例子:

int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheDirectory = new File(getCacheDir(), "OkHttpCache");
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)//連接超時時間
        .readTimeout(60, TimeUnit.SECONDS)//讀的時間
        .writeTimeout(60, TimeUnit.SECONDS)//寫的時間
        .cache(cache)//配置緩存
        .build();

OkHttpClient支持單獨配置瑰抵,例如原來設(shè)置不同的請求時間,可以通過OkHttpClient的newBuilder()方法來重新構(gòu)造一個OkHttpClient器联。例如:

OkHttpClient client = new OkHttpClient();

//讀的時間設(shè)置為500ms
OkHttpClient copy = client.newBuilder()
                          .readTimeout(500, TimeUnit.MILLISECONDS)
                          .build();

//讀的時間設(shè)置為3000ms
OkHttpClient copy = client.newBuilder()
                          .readTimeout(3000, TimeUnit.MILLISECONDS)
                          .build();

3 同步請求和異步請求

上面已經(jīng)講了如何創(chuàng)建Request和OkHttpClient,剩下的就是發(fā)送請求并得到服務(wù)器的響應(yīng)了婿崭。OkHttp發(fā)送請求可分為同步和異步拨拓。OkHttpClient首先通過Request構(gòu)建一個Call,通過這個Call去執(zhí)行同步或者異步請求氓栈。

#OkHttpClient
public Call newCall(Request request)

同步方式渣磷,調(diào)用Call的execute()方法,返回Response授瘦,會阻塞當(dāng)前線程:

response = client.newCall(request).execute();

異步方式醋界,調(diào)用Call的enqueue(CallBack callBack)方法,會在另一個線程中返回結(jié)果提完。

client.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 {
              //處理正確的回調(diào)
            }
        });

4 其他

4.1 配置響應(yīng)緩存

為了緩存響應(yīng)形纺,需要一個可讀寫并且設(shè)置大小Size的緩存目錄。緩存目錄需要私有徒欣,其它不信任的應(yīng)用不能訪問這個文件逐样。
如果同時有多個緩存訪問同一個緩存目錄會報錯。所以最好只在App中初始化一次OkHttpClient打肝,給這個實例配置緩存脂新,在整個App生命周期內(nèi)都用這一個緩存。否則幾個緩存會相互影響粗梭,導(dǎo)致緩存出錯争便,引起程序崩潰。
響應(yīng)緩存采用Http頭來配置断医,你可以添加這樣的請求頭Cache-Control: max-stale=3600滞乙。 max-age指的是客戶端可以接收生存期不大于指定時間(以為單位)的響應(yīng)。
為了防止響應(yīng)使用緩存鉴嗤,可以用CacheControl.FORCE_NETWORK酷宵。為了防止使用網(wǎng)絡(luò),采用 CacheControl.FORCE_CACHE躬窜。

注意:如果使用FORCE_CACHE禁止使用網(wǎng)絡(luò)浇垦,而響應(yīng)又沒有緩存存在,OkHttp會報**504 Unsatisfiable Request **響應(yīng)錯誤荣挨。

4.2 取消請求

調(diào)用Call.cancel()方法可以立即取消一個網(wǎng)絡(luò)請求男韧。如果當(dāng)前線程正在寫request或者讀response會報IO異常朴摊。如果不再需要網(wǎng)絡(luò)請求,采用這種方法是比較方便的此虑。例如在App中返回了上一頁甚纲。無論是同步還是異步的請求都可以被取消。

4.3 Response讀取響應(yīng)結(jié)果

可以通過Response的code來判斷請求是否成功朦前,如果服務(wù)器返回的有數(shù)據(jù)介杆,可以通過Response的body得到一個ResponseBody讀取。
如果采用ResponseBody的string()方法會一次性把數(shù)據(jù)讀取到內(nèi)存中韭寸,如果數(shù)據(jù)超過1MB可能會報內(nèi)存溢出春哨,所以對于超過1MB的數(shù)據(jù),建議采用流的方式去讀取恩伺,如ResponseBody的byteStream()方法赴背。

需要說明的是:

  • 如果ResponseBody的內(nèi)容不讀取的話,不會觸發(fā)IO流的讀取操作
  • 內(nèi)容讀取之后晶渠,這個body需要關(guān)閉凰荚。

5 總結(jié)

OkHttp中的很多類都用到了建造者模式,可以根據(jù)需要靈活配置褒脯。采用建造者模式的有:

  • OkHttpClient.Builder
  • Request.Builder
  • FormBody.Builder
  • MultipartBody.Builder
  • Response.Builder

如果單獨使用OkHttp進行網(wǎng)絡(luò)請求便瑟,通常需要開發(fā)者自己再封裝一下,如果不想重復(fù)造輪子番川,Github上面的有一些優(yōu)秀開源庫可以拿來使用(本文只列出star較多的幾個):

參考

OkHttp官方Wiki文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胳徽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子爽彤,更是在濱河造成了極大的恐慌养盗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适篙,死亡現(xiàn)場離奇詭異往核,居然都是意外死亡,警方通過查閱死者的電腦和手機嚷节,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門聂儒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硫痰,你說我怎么就攤上這事衩婚。” “怎么了效斑?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵非春,是天一觀的道長。 經(jīng)常有香客問我,道長奇昙,這世上最難降的妖魔是什么护侮? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮储耐,結(jié)果婚禮上羊初,老公的妹妹穿的比我還像新娘。我一直安慰自己什湘,他們只是感情好长赞,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闽撤,像睡著了一般得哆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腹尖,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音伐脖,去河邊找鬼热幔。 笑死,一個胖子當(dāng)著我的面吹牛讼庇,可吹牛的內(nèi)容都是我干的绎巨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蠕啄,長吁一口氣:“原來是場噩夢啊……” “哼场勤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起歼跟,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤和媳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哈街,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體留瞳,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年骚秦,在試婚紗的時候發(fā)現(xiàn)自己被綠了她倘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡作箍,死狀恐怖硬梁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胞得,我是刑警寧澤荧止,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響罩息,放射性物質(zhì)發(fā)生泄漏嗤详。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一瓷炮、第九天 我趴在偏房一處隱蔽的房頂上張望葱色。 院中可真熱鬧,春花似錦娘香、人聲如沸苍狰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淋昭。三九已至,卻和暖如春安接,著一層夾襖步出監(jiān)牢的瞬間翔忽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工盏檐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歇式,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓胡野,卻偏偏與公主長得像材失,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子硫豆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理龙巨,服務(wù)發(fā)現(xiàn),斷路器熊响,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 參考Android網(wǎng)絡(luò)請求心路歷程Android Http接地氣網(wǎng)絡(luò)請求(HttpURLConnection) 一...
    合肥黑閱讀 21,234評論 7 63
  • OkHttp使用完全教程 標(biāo)簽 : Http請求, 類庫blog : http://blog.csdn.net/o...
    oncealong閱讀 170,092評論 27 459
  • 1.okhttp介紹: 目前最新android 網(wǎng)絡(luò)請求底部封裝就是okhttp旨别,github鏈接 :https:...
    小夢想家北冥有魚閱讀 1,529評論 0 3
  • MVP+okHttp+Retrofit+RxJava+Glide+Dagger 是現(xiàn)在最流行的一套技術(shù)框架, MV...
    EmanLu閱讀 1,786評論 0 3