Retrofit2文件上傳

轉(zhuǎn)載請(qǐng)注明出處:Retrofit2文件上傳

前言

使用Retrofit2已經(jīng)有一段時(shí)間了策菜,在使用時(shí)一直在感嘆庫的易用性和靈活性,一直想深入的研究下源碼和機(jī)制,但是項(xiàng)目催得緊贞岭,深陷泥潭無法脫身。果然在多文件上傳時(shí)被卡住了饱狂。(今天犯懶曹步,明天就遭報(bào)應(yīng))研究半天終于跑通,特此記錄休讳。

Http MultiPart消息

其實(shí)無論什么庫讲婚,只要是發(fā)送Http請(qǐng)求,都得遵守Http協(xié)議俊柔,所以熟悉協(xié)議內(nèi)容對(duì)理解庫原理筹麸、調(diào)試是有很大幫助的。

Http上傳協(xié)議為MultiPart雏婶。下面是通過抓包獲取的一次多文件+文本的上傳消息物赶,每行前面的行數(shù)是為了標(biāo)注說明方便加上的,實(shí)際請(qǐng)求中沒有留晚。

1  POST http://host:8080/updata.action HTTP/1.1
2  Content-Type: multipart/form-data; boundary=bec890b3-d76c-4986-803d-dc4b57ba2421
3  Content-Length: 3046505
4  Host: host:8080
5  Connection: Keep-Alive
6  Accept-Encoding: gzip
7  User-Agent: okhttp/3.2.0
8
9  --bec890b3-d76c-4986-803d-dc4b57ba2421
10 Content-Disposition: form-data; name="title"
11 Content-Type: text/plain; charset=utf-8
12 Content-Length: 15
13
14 多文件上傳
15 --bec890b3-d76c-4986-803d-dc4b57ba2421
16 Content-Disposition: form-data; name="token"
17 Content-Type: text/plain; charset=utf-8
18 Content-Length: 32
19
20 登陸Token值
21 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
22 Content-Disposition: form-data; name="imgUrls"; filename="0.jpg"
23 Content-Type: image/*
24 Content-Length: 168637
25
26 (文件字節(jié)酵紫,一堆亂碼)@ h r   q   UY? e<?* ?  7C  Z 6?...
27 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
28 Content-Disposition: form-data; name="imgUrls"; filename="1.jpg"
29 Content-Type: image/*
30 Content-Length: 164004
31
32 (文件字節(jié),一堆亂碼)@ h r   q   UY? e<?* ?  7C  Z 6?...
33 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
34 Content-Disposition: form-data; name="imgUrls"; filename="2.jpg"
35 Content-Type: image/*
36 Content-Length: 167307
37
38 (文件字節(jié),一堆亂碼)@ h r   q   UY? e<?* ?  7C  Z 6?...
39 --776becce-5bd0-41d3-aa73-d3cd3ca4209d--
  • line1:請(qǐng)求行
  • line2-line7:消息頭
  • line2:定義請(qǐng)求類型及分隔符
  • line9-line39:消息正文
  • line9:分隔符奖地,用于分割正文的各條數(shù)據(jù)
  • line39:結(jié)尾分隔符
  • line10:name定義服務(wù)端獲取本條數(shù)據(jù)的key
  • line17:Content-Type定義本條數(shù)據(jù)類型為文本橄唬,charset定義編碼為utf-8
  • line22:name定義Key,filename定義上傳的文件名
  • line23:Content-Type定義本條數(shù)據(jù)類型為圖片文件

以上代碼為一次多文件+文本的表單請(qǐng)求参歹,Retrofit2基本將能封裝的內(nèi)容都封裝了仰楚,我們需要做的就是通過MultiPartBody.Part或者M(jìn)ultiPartBody將文本及文件數(shù)據(jù)封裝好并傳到接口中。

Retrofit2實(shí)現(xiàn)上傳請(qǐng)求

上面說到Retrofit2封裝請(qǐng)求消息是不完全正確的犬庇,因?yàn)镽etrofit2使用動(dòng)態(tài)代理將具體的請(qǐng)求分發(fā)給具體的http client去執(zhí)行僧界,一般使用Okhttp。

定義上傳接口

/**
 * 注意1:必須使用{@code @POST}注解為post請(qǐng)求<br>
 * 注意:使用{@code @Multipart}注解方法臭挽,必須使用{@code @Part}/<br>
 * {@code @PartMap}注解其參數(shù)<br>
 * 本接口中將文本數(shù)據(jù)和文件數(shù)據(jù)分為了兩個(gè)參數(shù)捂襟,是為了方便將封裝<br>
 * {@link MultipartBody.Part}的代碼抽取到工具類中<br>
 * 也可以合并成一個(gè){@code @Part}參數(shù)
 * @param params 用于封裝文本數(shù)據(jù)
 * @param parts 用于封裝文件數(shù)據(jù)
 * @return BaseResp為服務(wù)器返回的基本Json數(shù)據(jù)的Model類
 */
@Multipart
@POST(RequestApiPath.UPLOAD_WORK)
Observable<BaseResp> requestUploadWork(@PartMap Map<String, RequestBody> params,
                                       @Part List<MultipartBody.Part> parts);

/**
 * 注意1:必須使用{@code @POST}注解為post請(qǐng)求<br>
 * 注意2:使用{@code @Body}注解參數(shù),則不能使用{@code @Multipart}注解方法了<br>
 * 直接將所有的{@link MultipartBody.Part}合并到一個(gè){@link MultipartBody}中
 */
@POST(RequestApiPath.UPLOAD_WORK)
Observable<BaseResp> requestUploadWork(@Body MultipartBody body);

MultipartBody.Part/MultipartBody的封裝

/**
 * 將文件路徑數(shù)組封裝為{@link List<MultipartBody.Part>}
 * @param key 對(duì)應(yīng)請(qǐng)求正文中name的值埋哟。目前服務(wù)器給出的接口中笆豁,所有圖片文件使用<br>
 * 同一個(gè)name值,實(shí)際情況中有可能需要多個(gè)
 * @param filePaths 文件路徑數(shù)組
 * @param imageType 文件類型
 */
public static List<MultipartBody.Part> files2Parts(String key,
                          String[] filePaths, MediaType imageType) {
   List<MultipartBody.Part> parts = new ArrayList<>(filePaths.length);
   for (String filePath : filePaths) {
       File file = new File(filePath);
       // 根據(jù)類型及File對(duì)象創(chuàng)建RequestBody(okhttp的類)
       RequestBody requestBody = RequestBody.create(imageType, file);
       // 將RequestBody封裝成MultipartBody.Part類型(同樣是okhttp的)
       MultipartBody.Part part = MultipartBody.Part.
               createFormData(key, file.getName(), requestBody);
       // 添加進(jìn)集合
       parts.add(part);
   }
   return parts;
}

/**
 * 其實(shí)也是將File封裝成RequestBody赤赊,然后再封裝成Part闯狱,<br>
 * 不同的是使用MultipartBody.Builder來構(gòu)建MultipartBody
 * @param key 同上
 * @param filePaths 同上
 * @param imageType 同上
 */
public static MultipartBody filesToMultipartBody(String key,
                                                 String[] filePaths,
                                                 MediaType imageType) {
    MultipartBody.Builder builder = new MultipartBody.Builder();
    for (String filePath : filePaths) {
        File file = new File(filePath);
        RequestBody requestBody = RequestBody.create(imageType, file);
        builder.addFormDataPart(key, file.getName(), requestBody);
    }
    builder.setType(MultipartBody.FORM);
    return builder.build();
}

文本類型的MultipartBody.Part封裝

/**
 * 直接添加文本類型的Part到的MultipartBody的Part集合中
 * @param parts Part集合
 * @param key 參數(shù)名(name屬性)
 * @param value 文本內(nèi)容
 * @param position 插入的位置
 */
public static void addTextPart(List<MultipartBody.Part> parts,
                              String key, String value, int position) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
    MultipartBody.Part part = MultipartBody.Part.createFormData(key, null, requestBody);
    parts.add(position, part);
}

/**
 * 添加文本類型的Part到的MultipartBody.Builder中
 * @param builder 用于構(gòu)建MultipartBody的Builder
 * @param key 參數(shù)名(name屬性)
 * @param value 文本內(nèi)容
 */
public static MultipartBody.Builder addTextPart(MultipartBody.Builder builder,
                                                String key, String value) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
    // MultipartBody.Builder的addFormDataPart()有一個(gè)直接添加key value的重載,但坑的是這個(gè)方法
    // 不會(huì)設(shè)置編碼類型抛计,會(huì)出亂碼哄孤,所以可以使用3個(gè)參數(shù)的,將中間的filename置為null就可以了
    // builder.addFormDataPart(key, value);
    // 還有一個(gè)坑就是吹截,后臺(tái)取數(shù)據(jù)的時(shí)候有可能是有順序的瘦陈,比如必須先取文本后取文件,
    // 否則就取不到(真弱啊...)波俄,所以還要注意add的順序
    builder.addFormDataPart(key, null, requestBody);
    return builder;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晨逝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懦铺,更是在濱河造成了極大的恐慌捉貌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冬念,死亡現(xiàn)場(chǎng)離奇詭異趁窃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)急前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門醒陆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裆针,你說我怎么就攤上這事刨摩∷律危” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵码邻,是天一觀的道長折剃。 經(jīng)常有香客問我另假,道長像屋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任边篮,我火速辦了婚禮己莺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戈轿。我一直安慰自己凌受,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布思杯。 她就那樣靜靜地躺著胜蛉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪色乾。 梳的紋絲不亂的頭發(fā)上誊册,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音暖璧,去河邊找鬼案怯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛澎办,可吹牛的內(nèi)容都是我干的嘲碱。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼局蚀,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼麦锯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琅绅,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤扶欣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奉件,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宵蛀,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年县貌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了术陶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡煤痕,死狀恐怖梧宫,靈堂內(nèi)的尸體忽然破棺而出接谨,到底是詐尸還是另有隱情,我是刑警寧澤塘匣,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布脓豪,位于F島的核電站,受9級(jí)特大地震影響忌卤,放射性物質(zhì)發(fā)生泄漏扫夜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一驰徊、第九天 我趴在偏房一處隱蔽的房頂上張望笤闯。 院中可真熱鬧,春花似錦棍厂、人聲如沸颗味。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浦马。三九已至,卻和暖如春张漂,著一層夾襖步出監(jiān)牢的瞬間晶默,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國打工鹃锈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荤胁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓屎债,卻偏偏與公主長得像仅政,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盆驹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,322評(píng)論 25 707
  • 整體Retrofit內(nèi)容如下: 1圆丹、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 15,094評(píng)論 4 39
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理躯喇,服務(wù)發(fā)現(xiàn)辫封,斷路器,智...
    卡卡羅2017閱讀 134,715評(píng)論 18 139
  • 自從聯(lián)合國做了個(gè)聲明欣福,“「青年」的定義是年齡介于15歲與24歲之間的群體〗孤模” 萬千90后(尤其90-92年為甚)的...
    怪味wiwi閱讀 422評(píng)論 0 5
  • 一個(gè)朋友給我發(fā)來一篇微信文章《阿里不去清華招人的真正原因》拓劝。并附上一句話:“沒有效率的增長雏逾,不是慢性自殺,而是加速...
    燕靈灣閱讀 276評(píng)論 0 0