Retrofit2文件上傳

轉載請注明出處:Retrofit2文件上傳

前言

使用Retrofit2已經有一段時間了,在使用時一直在感嘆庫的易用性和靈活性,一直想深入的研究下源碼和機制,但是項目催得緊,深陷泥潭無法脫身。果然在多文件上傳時被卡住了。(今天犯懶,明天就遭報應)研究半天終于跑通,特此記錄。

Http MultiPart消息

其實無論什么庫,只要是發送Http請求,都得遵守Http協議,所以熟悉協議內容對理解庫原理、調試是有很大幫助的。

Http上傳協議為MultiPart。下面是通過抓包獲取的一次多文件+文本的上傳消息,每行前面的行數是為了標注說明方便加上的,實際請求中沒有。

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 (文件字節,一堆亂碼)@ 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 (文件字節,一堆亂碼)@ 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 (文件字節,一堆亂碼)@ h r   q   UY? e<?* ?  7C  Z 6?...
39 --776becce-5bd0-41d3-aa73-d3cd3ca4209d--
  • line1:請求行
  • line2-line7:消息頭
  • line2:定義請求類型及分隔符
  • line9-line39:消息正文
  • line9:分隔符,用于分割正文的各條數據
  • line39:結尾分隔符
  • line10:name定義服務端獲取本條數據的key
  • line17:Content-Type定義本條數據類型為文本,charset定義編碼為utf-8
  • line22:name定義Key,filename定義上傳的文件名
  • line23:Content-Type定義本條數據類型為圖片文件

以上代碼為一次多文件+文本的表單請求,Retrofit2基本將能封裝的內容都封裝了,我們需要做的就是通過MultiPartBody.Part或者MultiPartBody將文本及文件數據封裝好并傳到接口中。

Retrofit2實現上傳請求

上面說到Retrofit2封裝請求消息是不完全正確的,因為Retrofit2使用動態代理將具體的請求分發給具體的http client去執行,一般使用Okhttp。

定義上傳接口

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

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

MultipartBody.Part/MultipartBody的封裝

/**
 * 將文件路徑數組封裝為{@link List<MultipartBody.Part>}
 * @param key 對應請求正文中name的值。目前服務器給出的接口中,所有圖片文件使用<br>
 * 同一個name值,實際情況中有可能需要多個
 * @param filePaths 文件路徑數組
 * @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);
       // 根據類型及File對象創建RequestBody(okhttp的類)
       RequestBody requestBody = RequestBody.create(imageType, file);
       // 將RequestBody封裝成MultipartBody.Part類型(同樣是okhttp的)
       MultipartBody.Part part = MultipartBody.Part.
               createFormData(key, file.getName(), requestBody);
       // 添加進集合
       parts.add(part);
   }
   return parts;
}

/**
 * 其實也是將File封裝成RequestBody,然后再封裝成Part,<br>
 * 不同的是使用MultipartBody.Builder來構建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 參數名(name屬性)
 * @param value 文本內容
 * @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 用于構建MultipartBody的Builder
 * @param key 參數名(name屬性)
 * @param value 文本內容
 */
public static MultipartBody.Builder addTextPart(MultipartBody.Builder builder,
                                                String key, String value) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
    // MultipartBody.Builder的addFormDataPart()有一個直接添加key value的重載,但坑的是這個方法
    // 不會設置編碼類型,會出亂碼,所以可以使用3個參數的,將中間的filename置為null就可以了
    // builder.addFormDataPart(key, value);
    // 還有一個坑就是,后臺取數據的時候有可能是有順序的,比如必須先取文本后取文件,
    // 否則就取不到(真弱啊...),所以還要注意add的順序
    builder.addFormDataPart(key, null, requestBody);
    return builder;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,455評論 25 708
  • 整體Retrofit內容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 15,163評論 4 39
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 自從聯合國做了個聲明,“「青年」的定義是年齡介于15歲與24歲之間的群體。” 萬千90后(尤其90-92年為甚)的...
    怪味wiwi閱讀 431評論 0 5
  • 一個朋友給我發來一篇微信文章《阿里不去清華招人的真正原因》。并附上一句話:“沒有效率的增長,不是慢性自殺,而是加速...
    燕靈灣閱讀 288評論 0 0