Android Retrofit 實(shí)現(xiàn)(圖文上傳)文字(參數(shù))和多張圖片一起上傳

背景

在有心課堂《自己動(dòng)手寫HTTP框架》課程中有下列課程:

自拍要發(fā)朋友圈如何實(shí)現(xiàn) http://stay4it.com/course/4/learn#lesson/208

通過(guò)自己寫的HTTP框架實(shí)現(xiàn)將圖片和文字等內(nèi)容在一個(gè)接口中提交到服務(wù)器。無(wú)論哪種網(wǎng)絡(luò)框架,都要遵守HTTP協(xié)議。下面我們簡(jiǎn)單了解下HTTP協(xié)議。

HTTP協(xié)議

其中HTTP協(xié)議版本有兩種:HTTP1.0/HTTP1.1 可以這樣區(qū)別:

  • HTTP1.0對(duì)于每個(gè)連接都的建立一次連接一次只能傳送一個(gè)請(qǐng)求和響應(yīng),請(qǐng)求就會(huì)關(guān)閉,HTTP1.0沒(méi)有Host字段;
  • HTTP1.1在同一個(gè)連接中可以傳送多個(gè)請(qǐng)求和響應(yīng),多個(gè)請(qǐng)求可以重疊和同時(shí)進(jìn)行,HTTP1.1必須有Host字段。

HTTP請(qǐng)求類型

根據(jù)HTTP標(biāo)準(zhǔn),HTTP請(qǐng)求可以使用多種請(qǐng)求方法。例如:HTTP1.1支持7種請(qǐng)求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。在Internet應(yīng)用中,最常用的方法是GET和POST。

GET: 請(qǐng)求指定的頁(yè)面信息,并返回實(shí)體主體。
POST: 請(qǐng)求服務(wù)器接受所指定的文檔作為對(duì)所標(biāo)識(shí)的URI的新的從屬實(shí)體。

HTTP請(qǐng)求格式

當(dāng)瀏覽器向Web服務(wù)器發(fā)出請(qǐng)求時(shí),它向服務(wù)器傳遞了一個(gè)數(shù)據(jù)塊,也就是請(qǐng)求信息,HTTP請(qǐng)求信息由3部分組成:

① 請(qǐng)求方法 URI 協(xié)議/版本
② 請(qǐng)求頭(Request Header)
③ 請(qǐng)求正文

下面是一個(gè)HTTP請(qǐng)求的例子:

  • 請(qǐng)求方法URI協(xié)議/版本

請(qǐng)求的第一行是“方法URL協(xié)議版本”:GET/sample.jsp HTTP/1.1

如上面圖片所示,“GET”代表請(qǐng)求方法,“/sample.jsp”表示URI,“HTTP/1.1代表協(xié)議和協(xié)議的版本。

URL完整地指定了要訪問(wèn)的網(wǎng)絡(luò)資源,通常只要給出相對(duì)于服務(wù)器的根目錄的相對(duì)目錄即可,因此總是以“/”開(kāi)頭,最后,協(xié)議版本聲明了通信過(guò)程中使用HTTP的版本。

  • 請(qǐng)求頭(Request Header)

請(qǐng)求頭包含許多有關(guān)的客戶端環(huán)境和請(qǐng)求正文的有用信息。例如,請(qǐng)求頭可以聲明瀏覽器所用的語(yǔ)言,請(qǐng)求正文的長(zhǎng)度等。

image
  • 請(qǐng)求正文

請(qǐng)求頭和請(qǐng)求正文之間是一個(gè)空行,這個(gè)行非常重要,它表示請(qǐng)求頭已經(jīng)結(jié)束,接下來(lái)的是請(qǐng)求正文。請(qǐng)求正文中可以包含客戶提交的查詢字符串信息:

username=jinqiao&password=1234

在以上的例子的HTTP請(qǐng)求中,請(qǐng)求的正文只有一行內(nèi)容。當(dāng)然,在實(shí)際應(yīng)用中,HTTP請(qǐng)求正文可以包含更多的內(nèi)容。

HTTP Post請(qǐng)求解析

一個(gè)稍微完整的HTTP請(qǐng)求報(bào)文:

這里寫圖片描述

①請(qǐng)求方法
②為請(qǐng)求對(duì)應(yīng)的URL地址,它和報(bào)文頭的Host屬性組成完整的請(qǐng)求URL,③是協(xié)議名稱及版本號(hào)。
④是HTTP的報(bào)文頭,報(bào)文頭包含若干個(gè)屬性,格式為“屬性名:屬性值”,服務(wù)端據(jù)此獲取客戶端的信息。
⑤是報(bào)文體,它將一個(gè)頁(yè)面表單中的組件值通過(guò)param1=value1&param2=value2的鍵值對(duì)形式編碼成一個(gè)格式化串,它承載多個(gè)請(qǐng)求參數(shù)的數(shù)據(jù)。不但報(bào)文體可以傳遞請(qǐng)求參數(shù),請(qǐng)求URL也可以通過(guò)類似于“/aremiyi/wonter.html? param1=value1&param2=value2”的方式傳遞請(qǐng)求參數(shù)。

Accept、Cookie 、Referer等屬于HTTP請(qǐng)求報(bào)文報(bào)文頭,了解其含義或者更多報(bào)文頭參考:http://blog.csdn.net/jdsjlzx/article/details/52259312

HTTP multipart/form-data請(qǐng)求分析

說(shuō)完了Get、Post請(qǐng)求,我們來(lái)說(shuō)說(shuō)multipart/form-data請(qǐng)求,這也是這篇博客的核心。

根據(jù)http/1.1 rfc 2616的協(xié)議規(guī)定,我們的請(qǐng)求方式只有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE等,那為為何我們還會(huì)有multipart/form-data請(qǐng)求之說(shuō)呢?這里簡(jiǎn)要說(shuō)明下。

http協(xié)議大家都知道是規(guī)定了以ASCII碼傳輸,建立在tcp、ip協(xié)議之上的應(yīng)用層規(guī)范,規(guī)范內(nèi)容把http請(qǐng)求分為3個(gè)部門:請(qǐng)求方法 URI 協(xié)議/版本,請(qǐng)求頭,請(qǐng)求正文。所有的方法、實(shí)現(xiàn)都是圍繞如何運(yùn)用和組織這三部分來(lái)完成的。

也就是說(shuō)http協(xié)議原始方法不支持multipart/form-data請(qǐng)求,那這個(gè)請(qǐng)求自然就是由這些原始的方法演變而來(lái)的,具體如何演變?nèi)缦拢?/p>

1、multipart/form-data的基礎(chǔ)方法是post,也就是說(shuō)是由post方法來(lái)組合實(shí)現(xiàn)的
2、multipart/form-data與post方法的不同之處:請(qǐng)求頭,請(qǐng)求體。
3、multipart/form-data的請(qǐng)求頭必須包含一個(gè)特殊的頭信息:Content-Type,且其值也必須規(guī)定為multipart/form-data,同時(shí)還需要規(guī)定一個(gè)內(nèi)容分割符用于分割請(qǐng)求體中的多個(gè)post的內(nèi)容,如文件內(nèi)容和文本內(nèi)容自然需要分割開(kāi)來(lái),不然接收方就無(wú)法正常解析和還原這個(gè)文件了。具體的頭信息如下:

Content-Type: multipart/form-data; boundary=${bound}  

//其中${bound} 是一個(gè)占位符,代表我們規(guī)定的分割符,可以自己任意規(guī)定,但為了避免和正常文本重復(fù)了,盡量要使用復(fù)雜一點(diǎn)的內(nèi)容。如:——————–56423498738365

4、multipart/form-data的請(qǐng)求體也是一個(gè)字符串,不過(guò)和post的請(qǐng)求體不同的是它的構(gòu)造方式,post是簡(jiǎn)單的name=value值連接,而multipart/form-data則是添加了分隔符等內(nèi)容的構(gòu)造體。具體格式如下:

這里寫圖片描述

其中${bound}為之前頭信息中的分割符,如果頭信息中規(guī)定為123,那么這里也要為123,;可以很容易看出,這個(gè)請(qǐng)求體是多個(gè)相同的部分組成的:每一個(gè)部分都是以–加分隔符開(kāi)始的,然后是該部分內(nèi)容的描述信息,然后一個(gè)回車,然后是描述信息的具體內(nèi)容;如果傳送的內(nèi)容是一個(gè)文件的話,那么還會(huì)包含文件名信息,以及文件內(nèi)容的類型。上面的第二個(gè)小部分其實(shí)是一個(gè)文件體的結(jié)構(gòu),最后會(huì)以–分割符–結(jié)尾,表示請(qǐng)求體結(jié)束。

通過(guò)上面分析,可以知道要發(fā)送一個(gè)multipart/form-data的請(qǐng)求,其實(shí)任何支持post請(qǐng)求的工具或語(yǔ)言都可以支持,只是自己要稍微包裝一下便可。同樣,《自己動(dòng)手寫HTTP框架》里面的HTTP框架也是這么實(shí)現(xiàn)的,具體可以看看代碼。

下面我們結(jié)合具體的接口來(lái)分析multipart/form-data的請(qǐng)求。

抓包分析

課程中上傳圖片相關(guān)代碼如下圖所示:

這里寫圖片描述

從上面的代碼中可以看出,把圖片放在了列表中,圖片描述放在了request.content中。

通過(guò)對(duì)該方法運(yùn)行時(shí)的網(wǎng)絡(luò)請(qǐng)求抓包分析如下:

這里寫圖片描述

返回結(jié)果抓包分析如下:

這里寫圖片描述

從上圖Contents項(xiàng)中可以看到有兩個(gè)關(guān)鍵字段,分別是data和file0字段。

這兩個(gè)字段是怎么產(chǎn)生的呢?

通過(guò)查看《自己動(dòng)手寫HTTP框架》相關(guān)代碼,有如下方法:

這里寫圖片描述

這個(gè)是單張圖片上傳,緊接著看多張圖片上傳,代碼如下:

這里寫圖片描述

通過(guò)對(duì)上面兩段代碼的比較,發(fā)現(xiàn)主區(qū)別在這個(gè)地方:

這里寫圖片描述

這個(gè)地方也是我們使用Retrofit上傳的關(guān)鍵點(diǎn)所在,后面我們會(huì)再提到。

上面分析了這么多,我們來(lái)看看怎么使用retrofit來(lái)實(shí)現(xiàn)。

Retrofit實(shí)現(xiàn)文件和圖片一起上傳

如果對(duì)retrofit不是很了解,參考:初識(shí)Retrofit

定義接口

在碼小白的博客 Retrofit 2.0 超能實(shí)踐,輕松實(shí)現(xiàn)多文件/圖片上傳 中有下面內(nèi)容:

圖片和字符串同時(shí)上報(bào)

這里寫圖片描述

這種接口應(yīng)該也是可以的,具體要怎么實(shí)現(xiàn),都要與服務(wù)器接口保持一致,所以不能照搬照抄了。

根據(jù)對(duì)有心課堂提供的上傳圖片接口的大量抓包和測(cè)試總結(jié),接口定義如下:

這里寫圖片描述

這里用到了@Partmap注解,將圖片文件信息放入map中。

準(zhǔn)備圖片

在sdcard根目錄存放兩張圖片,分別為test.png和test.jpg(不要是gif圖片啊,服務(wù)器不支持)

代碼實(shí)現(xiàn)

這里就不貼代碼了,截圖如下(如果看不清,鼠標(biāo)右鍵在新窗口打開(kāi)就可以看到原圖了):

這里寫圖片描述

關(guān)鍵代碼在于:

這里寫圖片描述

看到這個(gè)是不是想起了上面我們提到的關(guān)鍵代碼呢?下面再貼出來(lái)我們對(duì)比下。

這里寫圖片描述

只要將對(duì)應(yīng)的http請(qǐng)求頭信息填寫正確,就能上傳成功。

那么問(wèn)題又來(lái)了,怎么分析和正確拼寫這個(gè)請(qǐng)求頭呢?

在文章開(kāi)頭的時(shí)候有個(gè)抓包信息:

Content-Disposition: form-data; name="file0"; filename="test.png"

實(shí)質(zhì)上上傳文件Requestbody對(duì)應(yīng)的請(qǐng)求頭就是 name=”file0”; filename=”test.png”,只要拼對(duì)了就沒(méi)有問(wèn)題了。

注意:

  1. name=”file0”; filename=”test.png”這個(gè)請(qǐng)求頭是根據(jù)有心課堂提供的上傳接口寫的,不適用其他上傳接口,但原理是類似的;
  2. 單張圖片上傳通用的請(qǐng)求頭是:name=”file”; filename=”test.png”
  3. filename=”test.png”這個(gè)一般是指(你希望)保存在服務(wù)器的文件名字。

舉例說(shuō)明

比如我們這樣寫請(qǐng)求頭信息,如下代碼所示:

這里寫圖片描述

運(yùn)行請(qǐng)求抓包請(qǐng)求頭信息如下圖所示:

這里寫圖片描述

出現(xiàn)了name=”name=”file1”這樣的字段,拼接錯(cuò)誤(不用加name字段),服務(wù)器也毫不留情的返回了錯(cuò)誤:

這里寫圖片描述

這個(gè)問(wèn)題我當(dāng)初沒(méi)有發(fā)現(xiàn),后來(lái)還是請(qǐng)教了Stay才搞明白了。

好了,不知道我講的大家明白了沒(méi)有,最后來(lái)個(gè)成功運(yùn)行的請(qǐng)求抓包截圖吧:

這里寫圖片描述

關(guān)于文字類參數(shù)上傳

寫到最后忘了說(shuō)文字參數(shù)了,文字參數(shù)相對(duì)文件來(lái)說(shuō)容易些。

在接口中,我們有一個(gè)文字參數(shù) @Part("data") String des,如果你需要多個(gè),增加就行了。需要注意的是這個(gè)參數(shù)的名字比如”data”,不是前端自定義,而是后臺(tái)定義的。

代碼托管地址:https://github.com/stay4it/RetrofitTutorial

版權(quán)聲明:本文原創(chuàng)作者:一葉飄舟 作者博客地址:http://blog.csdn.net/jdsjlzx https://blog.csdn.net/jdsjlzx/article/details/52246114

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

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

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