Retrofit上傳文件的參數(shù)設(shè)置疑問(wèn)以及URL的坑

一些題外話...

很久沒(méi)寫過(guò)什么東西了,上一篇文章還要追溯到去年八月份。那時(shí)我還是單身,而現(xiàn)在,我他娘的還是單身。自從去年9月份以來(lái),各種事情纏身算是徹底了我17年上半年無(wú)所事事的無(wú)聊遺憾。經(jīng)過(guò)9月份的煎熬和無(wú)休止的爭(zhēng)吵,終于在國(guó)慶回來(lái)后下定決心離職。隨后就是漫長(zhǎng)的工作交接和求職之路,出去正兒八經(jīng)面試過(guò)了才知道鍋兒是鐵打的,其中的心酸可能也只有自己經(jīng)歷過(guò)了才能體會(huì)。好在在17年年末一切都終于塵埃落定,搞定了新工作。初來(lái)入職,任務(wù)不算緊,所以終于有時(shí)間和精力,更重要的是有心情來(lái)寫一點(diǎn)東西。本來(lái)在去年計(jì)劃來(lái)點(diǎn)貨真價(jià)實(shí)的干貨,寫點(diǎn)視頻錄制的解決方案,不過(guò)也隨著已成歷史的17年無(wú)疾而終。自己也已經(jīng)算是將近3個(gè)月沒(méi)有正經(jīng)經(jīng)過(guò)代碼的錘煉了,有時(shí)自己都感覺(jué)手指已經(jīng)開(kāi)始生疏。不過(guò)作為沒(méi)什么人看的技術(shù)菜雞,寫什么年終總結(jié)、心路歷程和雞湯段子也顯然不太合適。所以還是隨便先寫點(diǎn)東西練練手,不能就這么荒廢下去。畢竟生活還要繼續(xù),用我去年領(lǐng)悟的一句話來(lái)說(shuō):假如生活欺騙了你,fuck it!

言歸正傳

最近剛好在準(zhǔn)備公司打算另起爐灶的項(xiàng)目重構(gòu),重新搭建了基本框架,終于如愿以償?shù)挠昧艘淮蜶xJava+Retrofit+OKHttp的組合,所以就順便說(shuō)說(shuō)關(guān)于自己遇到的關(guān)于Retrofit的一些的疑問(wèn)。

Retrofit上傳文件時(shí)設(shè)置配置參數(shù)的問(wèn)題。

主要說(shuō)說(shuō)在文件上傳時(shí)我們?cè)O(shè)置的一些參數(shù)。舉個(gè)栗子,在很多上傳的文章中,我們都能看到下面的代碼:

@Multipart
@POST("mobile/upload")
Call<ResponseBody> upload(@Part MultipartBody.Part file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

第一段代碼很好理解,定義了Retrofit的Service接口,我們實(shí)例化后根據(jù)先關(guān)用法就可以實(shí)現(xiàn)大致的上傳邏輯了。第二段代碼主要是傳入我們需要上傳的文件。那么問(wèn)題來(lái)了,在很多文章出現(xiàn)的這兩段代碼,卻很難找到其中的參數(shù)配置這么寫的原因。@Multipart"image/jpg""file"這些參數(shù)究竟為什么要這么寫?能不能改成其他的?很多時(shí)候我們只知道怎么做,但是不知道為什么,以至于一腳踩到坑里面也不知道怎么出來(lái)。

要想搞懂這幾個(gè)配置的意義,還要從Http上傳文件說(shuō)起。最開(kāi)始的Http是只能進(jìn)行純文本傳輸,所以在后來(lái)支持文件上傳后,新增了一個(gè)MIME類型,叫multipart/form-data。
在純文本上傳時(shí),請(qǐng)求頭里會(huì)有這么一行

Content-Type:application/x-www-form-urlencoded; charset=UTF-8

而在上傳文件時(shí),請(qǐng)求頭對(duì)應(yīng)的類型屬性需要定義成如下的方式:

Content-Type:multipart/form-data;  boundary=---------------------------238d787e8233c8874f

Content-Type:multipart/form-data;說(shuō)明了這個(gè)請(qǐng)求的類型是用于文件上傳,boundary的值是用于多個(gè)文件上傳時(shí)的數(shù)據(jù)分隔,防止數(shù)據(jù)混在一起而無(wú)法解析,一般會(huì)用一段很難和正常文本重復(fù)的字符串。
上傳時(shí)文件的具體信息是存放在Request Payload中的,下面是個(gè)簡(jiǎn)單的例子:

Request Payload
-----------------------------238d787e8233c8874f
Content-Disposition: form-data; name="yourKeyName"; filename="image1.jpg"
Content-Type: image/jpg
...
-----------------------------238d787e8233c8874f
Content-Disposition: form-data; name="upload2"; filename="image2.jpg"
Content-Type: image/jpg
...
-----------------------------238d787e8233c8874f--

上面的具體數(shù)據(jù)可能不是很嚴(yán)謹(jǐn),但大概就是這么個(gè)意思。其中name規(guī)定了這個(gè)文件的名稱,方便后臺(tái)通過(guò)這個(gè)名稱來(lái)獲取,名字可以隨便取,只要和后臺(tái)約定統(tǒng)一即可;filename則是具體的上傳文件的文件名;Content-Type規(guī)定了上傳文件的后綴類型。

大致了解了Http上傳的套路之后,回到最開(kāi)始提到的那兩端代碼,這里為了方便不再往上翻,再次貼一下。

@Multipart
@POST("mobile/upload")
Call<ResponseBody> upload(@Part MultipartBody.Part file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
  • @Multipart
    我們?cè)谏衔目吹降腟ervice接口中的@Multipart注解其實(shí)就是告訴Retrofit這個(gè)Service采用了multipart/form-data的請(qǐng)求方式,對(duì)應(yīng)了請(qǐng)求頭中Content-Type:multipart/form-data;這一部分。
  • MediaType.parse("image/jpg");
    "image/jpg"很好理解,是我們要上傳的文件類型,你總得告訴別人你傳的文件是什么格式的對(duì)吧,對(duì)應(yīng)了請(qǐng)求體中的Content-Type: image/jpeg
  • MultipartBody.Part.createFormData("file", file.getName(), requestFile);
    至于"file"這個(gè)東西,就是一個(gè)單純的keyName,方便服務(wù)端開(kāi)發(fā)獲取我們上傳的文件,因?yàn)槊恳淮握?qǐng)求可能會(huì)傳多個(gè)文件,甚至是數(shù)據(jù)和文件同時(shí)上傳,所以這個(gè)keyName就是服務(wù)端獲取相應(yīng)文件的依據(jù)。

到這里用retrofit上傳文件的一些參數(shù)為什么要這樣寫大概就清楚了。不過(guò)上傳文件的寫法不止這一種,我們還經(jīng)常看到下面這種寫法:

 @Multipart    
 @POST("user/updateAvatar.do")
 Call<Response> upload(@Part("upload1\"; filename=\"image1.jpg\"") RequestBody imgs );

如果我們不知到Http上傳文件的請(qǐng)求頭和請(qǐng)求體的原理,這里我們看到@Part內(nèi)的參數(shù)多半是懵逼的。不過(guò)與下面的代碼對(duì)比一下,就能理解為什么這么寫了。

Content-Disposition: form-data; name="upload1"; filename="image1.jpg"

@Part("upload1\"; filename=\"image1.jpg\"")中的參數(shù)經(jīng)過(guò)轉(zhuǎn)義后對(duì)應(yīng)了upload1"; filename="image1.jpg" 這一段。這里要注意的是參數(shù)開(kāi)頭是沒(méi)有引號(hào)的,而結(jié)尾有引號(hào)。

關(guān)于動(dòng)態(tài)URL

扯完了文件上傳,搗鼓了retrofit這么久,在動(dòng)態(tài)url這一塊也遇到了一些疑問(wèn),這里就順便說(shuō)說(shuō)。
我們知道retrofit一個(gè)比較格式化的標(biāo)準(zhǔn)請(qǐng)求應(yīng)該是下面這樣:

 @GET("relativePath/...")//請(qǐng)求鏈接的相對(duì)路徑
 Observable<ResponseResultModel<List<Express>>> getExpress(@Url String url, @Query("type") String type, @Query("postid") String postid);

然后在我們發(fā)起請(qǐng)求的時(shí)候,通過(guò)Retrofit.Builder().baseUrl("baseUrl")會(huì)給retrofit一個(gè)baseURL,兩者拼接起來(lái)就是一個(gè)完整的路徑。然而這種方式比較死板,所以在retrofit2.0開(kāi)始引入了動(dòng)態(tài)URL,我們無(wú)需在@GET注解或者其他注解上去寫死相對(duì)路徑,取而代之的是在Service接口方法中以參數(shù)的方式定義:

 @GET
 Observable<Response> downloadData(@Url String url);

當(dāng)我們調(diào)用downloadData({relativeUrl})方法時(shí),傳入的相對(duì)URL就和baseURL拼接成了一個(gè)完整路徑。
舉個(gè)例子:

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://www.baidu.com/");
    .build();

MyService service = retrofit.create(MyService.class);  
service.login("user/login");

//拼接結(jié)果:https://www.baidu.com/user/login

這里我的URL是隨便亂寫的,不過(guò)原理大概是這個(gè)樣子。這里需要注意一種特殊情況,我們對(duì)上面的代碼進(jìn)行一下修改:

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://www.baidu.com/v3");
    .build();

MyService service = retrofit.create(MyService.class);  
service.login("/user/login");

//拼接結(jié)果:https://www.baidu.com/user/login
//不是https://www.baidu.com/v3/user/login!!!
//不是https://www.baidu.com/v3/user/login!!!
//不是https://www.baidu.com/v3/user/login!!!

可以看到,最后的拼接結(jié)果并不是我們所想的那樣。至于原因,是因?yàn)槲覀冊(cè)诠?jié)點(diǎn)URL前添加了一個(gè)"/"(service.login("/user/login")),這將導(dǎo)致retrofit在拼接鏈接時(shí),忽略掉hostURL后面的內(nèi)容,所以我們?nèi)サ艄?jié)點(diǎn)URL前的"/"就可以了。

當(dāng)然,我們也可以在參數(shù)中傳入全路徑,這時(shí)retrofit會(huì)自動(dòng)解析這個(gè)完整路徑,并不會(huì)再去和baseURL拼接:

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://www.baidu.com/");
    .build();

MyService service = retrofit.create(MyService.class);  
service.login("http://www.lxweimin.com");

//結(jié)果:http://www.lxweimin.com

后來(lái)突然想起來(lái),如果在使用動(dòng)態(tài)URL的同時(shí),還給@GET注解添加節(jié)點(diǎn)URL會(huì)怎么樣呢?

@GET
 Observable<Response> downloadData(@Url String url);

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://www.baidu.com/");
    .build();

MyService service = retrofit.create(MyService.class);  
service.login("user/login");

自己嘗試跑了一下,直接就拋了異常,說(shuō)明并沒(méi)有這種操作。
最后,本文的目的并非系統(tǒng)而完全的介紹retrofit的體系和用法,只是記錄和分享一些自己在使用過(guò)程中的疑問(wèn)和自己了解到的答案。如果能有緣正好幫到有同樣問(wèn)題的人,那自然甚好。當(dāng)然,如果對(duì)于文章有什么疑問(wèn)或者建議,也歡迎大家留言交流。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評(píng)論 6 545
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,795評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,943評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 64,057評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,773評(píng)論 6 414
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,106評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,282評(píng)論 0 291
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,793評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,507評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,741評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,929評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,325評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,661評(píng)論 1 296
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,482評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,702評(píng)論 2 380

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