從 http協議角度解析okhttp

Okhttp 介紹

OkHttp 是 Square 公司開源的一款網絡框架,封裝了一個高性能的 http 請求庫。java

聲明

  • 支持 spdy、http2.0、websocket 等協議
  • 支持同步、異步請求
  • 封裝了線程池,封裝了數據轉換,提升性能。
  • 在 Android 6.0 中自帶的網絡請求 API 的底層就是使用了 okhttp 來進行的
  • 使用 okhttp 比較接近真正的 HTTP 協議的框架

其餘優勢見:Android 網絡框架比較(後面更新)
提及 okhttp 的介紹,介紹完這幾個關鍵類就能夠了!web

Okhttp 中幾個重要類的介紹

OkHttpClient

這個類主要是用來配置 okhttp 這個框架的,通俗一點講就是這個類是管理這個框架的各類設置的。json

Call 類的工廠,經過 OkHttpClient 才能獲得 Call 對象。api

OkHttpClient 使用注意

OkHttpClient 應該被共享,使用 okhttp 這個框架的時候,最好要將 OkHttpClient 設置成單例模式,全部的 HTTP 在進行請求的時候都要使用這一個 Client 。由於每一個 OkHttpClient 都對應了本身的鏈接池和線程池。減小使用鏈接池和線程池能夠減小延遲和內存的使用。相反的若是每一個請求都建立一個 OkHttpClient 的話會很浪費內存資源。緩存

OkHttpClient的建立

OkHttpClient 有三個建立方法bash

第一個方法:直接使用 new OkHttpClient() 來建立一個實例對象就能夠了,這個實例對象有默認的配置。默認請求鏈接超時時間 10 s ,讀寫超時時間 10 s,鏈接不成功會自動再次鏈接。服務器

第二個方法:就是經過 Builder的方式來本身定義一個 OkHttpclient 。固然若是你直接 build 沒有本身配置參數的話,效果和第一個方法是同樣的。websocket

public final OkHttpClient = new OkHttpClient.Builder()
  .addInterceptor(new HttpLoggingInterceptor())
  .cache(new Cache(cacheDir,cacheSize))
  .等等配置
  .build();
複製代碼

第三個方法:就是經過已有的 OkHttpClient 對象來複制一份共享線程池和其餘資源的 OkHttpClient 對象。markdown

OkHttpClient agerClient = client.newBuilder()
  .readTimeout(500,TimeUnit.MILLSECONS)
  .build();
複製代碼

這種方法的好處就是,當咱們有一個特殊的請求,有的配置有點不同,好比要求鏈接超過 1 s 就算超時,這個時候咱們就可使用這個方法來生成一個新的實例對象,不過他們共用不少其餘的資源,不會對資源形成浪費。網絡

關於 OkHttpClient 的配置改變都在 Builder 中進行

不須要了能夠關閉

其實持有的線程池和鏈接池將會被自定釋放若是他們保持閒置的話。

你也能夠自動釋放,釋放後未來再調用 call 的時候會被拒接。

client.dispatcher().excurorService().shutdown()

清除鏈接池,注意清除後,鏈接池的守護線程可能會馬上退出。

client.connectionPool().evictAll()

若是 Client 有緩存,能夠關閉。注意:再次調用一個被關閉的 cache 會發生錯誤。也會形成 crash。

client.cache().close();

OkHttp 在 HTTP/2 鏈接的時候也會使用守護線程。他們閒置的時候將自動退出。

知道有這麼一回事就行,通常不會主動調用。

Call 類

Call 這個類就是用來發送 HTTP 請求和讀取 HTTP 響應的一個類

Call類方法.png

這個類的方法不多,從上到下依次是:放棄請求、異步執行請求、同步執行請求。

Request 類

這個類就是至關於 http 請求中的請求報文,是用來表達請求報文的,因此這裏能夠設置請求的 url、請求頭、請求體等等和請求報文有關的內容。

主要方法羅列:

// 獲取請求 url
public HttpUrl url();
// 獲取請求方法類型
public String method();
// 獲取請求頭
public Headers headers();
//獲取請求體
public RequestBody body();
// 獲取 tag
public Object tag();
// 返回緩存控制指令,永遠不會是 null ,即便響應不包含 Cache-Control 響應頭
public CacheControl cacheControl();
// 是不是 https 請求
public boolean isHttps();
// Resquest{method=" ",url=" ",tag = " "}
public String toString();
複製代碼

request_builder.png

這是它的 Builder 中提供的方法,只設置 .url() 的時候默認是 post 請求。

RequestBody

介紹完請求報文就要介紹請求體了,這都是和 http協議緊密聯繫的。

RequestBody 就是用來設置請求體的,它的主要方法就是下面這個幾個靜態方法,用來生成對應的請求體:

request_body.png

就是經過這幾個方法來產生對應的不一樣的請求體。MediaType 是用來描述請求體或者響應體類型的。好比請求體類型是 json 串格式的,那對應的 MediaType 就是MediaType.parse("application/json; charset=utf-8"); ,若是上傳的是文件那麼對應的就是 application/octet-stream,還有幾個經常使用的類型 text/plain imge/png text/x-markdown 等等。

它還有兩個子類:

request_body.png

FormBody 這個請求體是咱們平時最經常使用的,就是咱們平時使用 post 請求的時候,參數是鍵值對的形式。就是使用這個請求體最簡單了。

說深一點,對應的請求報文是:

POST /test HTTP/1.1   請求行
Host: 32.106.24.148:8080  下面都是請求頭
Content-Type: application/x-www-form-urlencoded 用於指明請求體的類型。
User-Agent: PostmanRuntime/7.15.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 954bda0d-dbc2-4193-addf-a7631cab2cfa,5ba2ebed-90b4-4f35-bcf5-80c4777de471
Host: 39.106.24.148:8080
accept-encoding: gzip, deflate
content-length: 133
Connection: keep-alive
cache-control: no-cache

key0=value0&key1=value1  請求體(也是咱們的參數)
複製代碼

這是發送的原始的報文格式,用代碼實現的話就是

// 建立客戶端
OkHttpClient client = new OkHttpclient();
// 創建請求體 
FormBody formBody = new FormBody.Builder()
                    .add("key0", "value0")
  					.add("key1","value1")
                    .build();
// 創建請求報文
Request request = new Request.Builder
								.post(formBody)
  								.url("請求url")
  								.addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("User-Agent", "PostmanRuntime/7.15.0")
  .addHeader("Accept", "*/*")
  .addHeader("Cache-Control", "no-cache")
  .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
  .addHeader("Host", "39.106.24.148:8080")
  .addHeader("accept-encoding", "gzip, deflate")
  .addHeader("content-length", "133")
  .addHeader("Connection", "keep-alive")
  .addHeader("cache-control", "no-cache")
  .build();
// 發起請求
client.newCall(request).excute();
複製代碼

上面是使用了 FormBody 的形式,若是使用 RequestBody 的話就要更麻煩一些。

OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "key0=value0&key1=value1");
Request request = new Request.Builder()
  .url("http://39.106.24.148:8080/test")
  .post(body)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("User-Agent", "PostmanRuntime/7.15.0")
  .addHeader("Accept", "*/*")
  .addHeader("Cache-Control", "no-cache")
  .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
  .addHeader("Host", "39.106.24.148:8080")
  .addHeader("accept-encoding", "gzip, deflate")
  .addHeader("content-length", "133")
  .addHeader("Connection", "keep-alive")
  .addHeader("cache-control", "no-cache")
  .build();
Response response = client.newCall(request).execute();
複製代碼

固然平時咱們使用的時候,不用拼上這麼多的請求頭,我這樣寫的目的就是爲了更加還原請求報文。

還有一個子類 MultipartBody這個能夠用來構建比較複雜的請求體。

1995 年 Content-Type 的類型擴充了 multipart/form-data 用來支持向服務器發送二進制數據。若是一次提交多種類型的數據,好比:一張圖片和一個文字,這個時候引入了 boundaryboundary使得 POST 能夠知足這種提交多種不一樣的數據類型。經過 boundary 能夠實現多個不一樣類型的數據同時存在在一個 Request 中。兩個 boundary 之間就是一個類型的數據,而且能夠從新設置 Content-Type

與 HTML 文件上傳形式兼容。每塊請求體都是一個請求體,能夠定義本身的請求頭。這些請求頭能夠用來描述這塊請求。例如,他們的 Content-Disposition。若是 Content-Length 和 Content-Type 可用的話,他們會被自動添加到請求頭中。

來看一下這種類型的請求報文是什麼樣的:

POST /web/UploadServlet HTTP/1.1
Content-Type: multipart/form-data; boundary=e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Length: 66089
Host: localhost.tt.com:8080
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.5.0

–e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Disposition: form-data; name=」file」; filename=」**.png」
Content-Type: image/png
Content-Length: 65744

fdPNG
IHDR�0B7M�iM�M�CCPIM�CC ProfileH��……………………IEND�B`�
–e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Disposition: form-data; name=」comment」
Content-Length: 30

上傳一個圖
–e1b05ca4-fc4e-4944-837d-cc32c43c853a–
複製代碼

第一個數據是一張 png 的圖,從新設置了 Content-Type:image/png 中間的亂碼就是圖片的數據。這一堆數據前有一個空行,表示上下分別是請求頭、請求體。

第二個數據,就是一個文本數據。

這樣它們一塊兒構成了請求體。

講起來可能比較複雜,就記住,當既須要上傳參數,又須要上傳文件的時候用這種請求體。

MediaType mediaType = MediaType.parse("image/png");
        RequestBody requestBody = new MultipartBody.Builder()
          			// 須要設置成表單形式不然沒法上傳鍵值對參數
                .setType(MultipartBody.FORM)
                .addPart(Headers.of("Content-Disposition", "form-data;name=\"title\""),
                        RequestBody.create(null, "Square Logo"))
                .addPart(
                        Headers.of("Content-Disposition", "form-data;name=\"imge\""),
                        RequestBody.create(mediaType, new File("路徑/logo.png"))
                ).
                        build();
        Request request = new Request.Builder()
                .post(requestBody)
                .url("https://api.imgur.com/3/image")
                .build();
        try {
            mOkHttpClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
複製代碼

簡化寫法:

MediaType mediaType = MediaType.parse("image/png");
        RequestBody requestBody = new MultipartBody.Builder()
          		 .setType(MultipartBody.FORM)
               .addFormDataPart("title","logo")
                .addFormDataPart("img","logo.png",RequestBody.create(mediaType,new File("路徑/logo.png")))
                .build();
複製代碼

Content-Disposition 能夠用在消息體的子部分中,用來給出其對應字段的相關信息。做爲 multipart body 中的消息頭,第一個參數老是固定不變的 form-data; 附加的參數不區分大小寫,而且擁有參數值,參數名與參數值用等號鏈接,參數之間用分號分隔。參數值用雙引號括起來

// 好比這樣,就是這種固定的格式
"Content-Disposition","form-data;name=\"mFile\";filename=\"xxx.mp4\""

複製代碼

到這裏關於請求的幾個重要的類就講完了。

總結一下

只要掌握 http 請求的原理,使用起 okhttp 來也就不是什麼問題了。

首先 OkHttpClient 是用來設置關於請求工具的一些參數的,好比超時時間、是否緩存等等。

Call 對象是發起 Http 請求的對象,經過 Call 對象來發起請求。

發起請求的時候,須要有請求報文,Request 對象就是對應的請求報文,能夠添加對應的請求行、請求頭、請求體。

提及請求體就是對應了 RequestBody 了。而後這個網絡請求過程就完成了!

相關文章
相關標籤/搜索