[轉(zhuǎn)]Retrofit2 multpart多文件上傳詳解

轉(zhuǎn)載自:陳開華博客

Retrofit2是目前很流行的android網(wǎng)絡(luò)框架募书,運用注解和動態(tài)代理绪囱,極大的簡化了網(wǎng)絡(luò)請求的繁瑣步驟,非常適合處理restfull網(wǎng)絡(luò)請求莹捡。在項目中鬼吵,經(jīng)常需要上傳文件到服務(wù)器,有時候是需要上傳多個文件篮赢。網(wǎng)上文章基本都是單文件上傳教程齿椅,固定數(shù)量多文件上傳,這篇文章主要講retrofit在不確定文件數(shù)量下的多文件上傳實現(xiàn)启泣。
個人覺得有必要深入理解http協(xié)議涣脚,這樣無論使用哪個網(wǎng)絡(luò)框架,碰到類似這樣上傳的問題寥茫,一眼就能知道問題出在哪里遣蚀。因此就有必要了解http協(xié)議的上傳機制。

了解multipart/form-data

在最初的http協(xié)議中,沒有定義上傳文件的Method芭梯,為了實現(xiàn)這個功能险耀,http協(xié)議組改造了post請求,添加了一種post規(guī)范玖喘,設(shè)定這種規(guī)范的Content-Typemultipart/form-data;boundary=${bound},其中${bound}是定義的分隔符胰耗,用于分割各項內(nèi)容(文件,key-value對),不然服務(wù)器無法正確識別各項內(nèi)容芒涡。post body里需要用到,盡量保證隨機唯一柴灯。

post格式如下:

--${bound}
Content-Disposition: form-data; name="Filename"
 
HTTP.pdf
--${bound}
Content-Disposition: form-data; name="file000"; filename="HTTP協(xié)議詳解.pdf"
Content-Type: application/octet-stream
 
%PDF-1.5
file content
%%EOF
 
--${bound}
Content-Disposition: form-data; name="Upload"
 
Submit Query
--${bound}--

${bound}Content-Typeboundary的值

Retrofit2 對multipart/form-data的封裝

Retrofit其實是個網(wǎng)絡(luò)代理框架,負責封裝請求费尽,然后把請求分發(fā)給http協(xié)議具體實現(xiàn)者-httpclient赠群。retrofit默認的httpclient是okhttp。

既然Retrofit不實現(xiàn)http旱幼,為啥還用它呢查描。因為他方便!柏卤!
Retrofit會根據(jù)注解封裝網(wǎng)絡(luò)請求冬三,待httpclient請求完成后,把原始response內(nèi)容通過轉(zhuǎn)化器(converter)轉(zhuǎn)化成我們需要的對象(object)缘缚。

具體怎么使用 retrofit2,請參考:Retrofit2官網(wǎng)

那么Retrofit和okhttp怎么封裝這些multipart/form-data
上傳數(shù)據(jù)呢

  • retrofit中:
    • @retrofit2.http.Multipart: 標記一個請求是multipart/form-data類型,需要和@retrofit2.http.POST一同使用勾笆,并且方法參數(shù)必須是@retrofit2.http.Part注解。
    • @retrofit2.http.Part: 代表Multipart里的一項數(shù)據(jù),即用${bound}分隔的內(nèi)容塊桥滨。
  • okhttp3中:
    • okhttp3.MultipartBody :multipart/form-data 的抽象封裝,繼承okhttp3.RequestBody
    • okhttp3.MultipartBody.Part:multipart/form-data里的一項數(shù)據(jù)窝爪。

Service接口定義

假設(shè)服務(wù)器上傳接口返回數(shù)據(jù)類型為application/json,字段如下

{
data: "上傳了3個文件",
msg: "訪問成功",
code: 200
}

因此需要對返回數(shù)據(jù)封裝成一個對象,考慮到復用性,封裝成泛型最佳:

public class BaseResponse<T> {
    public int code;
    public String msg;
    public T data;
}

接著定義一個上傳的網(wǎng)絡(luò)請求Service:

public interface FileuploadService {
    /**
     * 通過 List<MultipartBody.Part> 傳入多個part實現(xiàn)多文件上傳
     * @param parts 每個part代表一個
     * @return 狀態(tài)信息
     */
    @Multipart
    @POST("users/image")
    Call<BaseResponse<String>> uploadFilesWithParts(@Part() List<MultipartBody.Part> parts);


    /**
     * 通過 MultipartBody和@body作為參數(shù)來上傳
     * @param multipartBody MultipartBody包含多個Part
     * @return 狀態(tài)信息
     */
    @POST("users/image")
    Call<BaseResponse<String>> uploadFileWithRequestBody(@Body MultipartBody multipartBody);
}

由上可知齐媒,有兩種方式實現(xiàn)上傳

  • 使用@Multipart注解方法蒲每,并用@Part注解方法參數(shù),類型是List<okhttp3.MultipartBody.Part>
  • 不使用@Multipart注解方法喻括,直接使用@Body注解方法參數(shù)邀杏,類型是okhttp3.MultipartBody

可以看到,無論方法參數(shù)類型是MultipartBody.Part還是MultipartBody,這些類都不是Retrofit的類唬血,而是okhttp實現(xiàn)上傳的源生類望蜡。

為什么可以這樣寫

  • Retrofit會判斷@Body的參數(shù)類型,如果參數(shù)類型為okhttp3.RequestBody,則Retrofit不做包裝處理刁品,直接丟給okhttp3處理泣特。而MultipartBody是繼承RequestBody,因此Retrofit不會自動包裝這個對象挑随。
  • 同理,Retrofit會判斷@Part的參數(shù)類型,如果參數(shù)類型為okhttp3.MultipartBody.Part,則Retrofit會把RequestBody封裝成MultipartBody兜挨,再把Part添加到MultipartBody膏孟。

上傳多個文件

寫好service接口后,來看看怎么構(gòu)造MultipartBody
可以寫一個方法拌汇,用于把File對象轉(zhuǎn)化成MultipartBody:

    public static MultipartBody filesToMultipartBody(List<File> files) {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        
        for (File file : files) {
            // TODO: 16-4-2  這里為了簡單起見柒桑,沒有判斷file的類型 
            RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
            builder.addFormDataPart("file", file.getName(), requestBody);
        }

        builder.setType(MultipartBody.FORM);
        MultipartBody multipartBody = builder.build();
        return multipartBody;
    }

或者把File轉(zhuǎn)化成MultipartBody.Part:

    public static List<MultipartBody.Part> filesToMultipartBodyParts(List<File> files) {
        List<MultipartBody.Part> parts = new ArrayList<>(files.size());
        for (File file : files) {
            // TODO: 16-4-2  這里為了簡單起見,沒有判斷file的類型
            RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
            MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody);
            parts.add(part);
        }
        return parts;
    }

最后就剩下調(diào)用了
為了復用噪舀,因此把構(gòu)建Retrofit簡單封裝成一個builder類:

public class RetrofitBuilder {
    private static Retrofit retrofit;

    public synchronized static Retrofit buildRetrofit() {
        if (retrofit == null) {
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
            GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(logging).retryOnConnectionFailure(true)
                    .build();
            retrofit = new Retrofit.Builder().client(client)
                    .baseUrl(Config.NetURL.BASE_URL)
                    .addConverterFactory(gsonConverterFactory)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

接著可以在activity里調(diào)用FileUploadService的接口了:

    private void uploadFile() {
        MultipartBody body = MultipartBuilder.filesToMultipartBody(mFileList);

        RetrofitBuilder.buildRetrofit().create(FileUploadService.class).uploadFileWithRequestBody(body)
        .enqueue(new Callback<BaseResponse<String>>() {
            @Override
            public void onResponse(Call<BaseResponse<String>> call, Response<BaseResponse<String>> response) {

                if (response.isSuccessful()) {
                    BaseResponse<String> body = response.body();
                    Toast.makeText(LoginActivity.this, "上傳成功:"+response.body().getMsg(), Toast.LENGTH_SHORT).show();
                } else {
                        Log.d(TAG,"上傳失敗");
                        Toast.makeText(RegisterActivity.this, "上傳失敗", Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<BaseResponse<String>> call, Throwable t) {
                Toast.makeText(RegisterActivity.this, "網(wǎng)絡(luò)連接失敗", Toast.LENGTH_SHORT).show();
            }
        });
    }

參考資料

http://my.oschina.net/cnlw/blog/168466

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魁淳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子与倡,更是在濱河造成了極大的恐慌界逛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纺座,死亡現(xiàn)場離奇詭異息拜,居然都是意外死亡,警方通過查閱死者的電腦和手機净响,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門少欺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人馋贤,你說我怎么就攤上這事赞别。” “怎么了配乓?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵氯庆,是天一觀的道長。 經(jīng)常有香客問我扰付,道長堤撵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任羽莺,我火速辦了婚禮实昨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盐固。我一直安慰自己荒给,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布刁卜。 她就那樣靜靜地躺著志电,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛔趴。 梳的紋絲不亂的頭發(fā)上挑辆,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音,去河邊找鬼鱼蝉。 笑死洒嗤,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的魁亦。 我是一名探鬼主播渔隶,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洁奈!你這毒婦竟也來了间唉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤利术,失蹤者是張志新(化名)和其女友劉穎呈野,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氯哮,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡际跪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喉钢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆打。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肠虽,靈堂內(nèi)的尸體忽然破棺而出幔戏,到底是詐尸還是另有隱情,我是刑警寧澤税课,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布闲延,位于F島的核電站,受9級特大地震影響韩玩,放射性物質(zhì)發(fā)生泄漏垒玲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一找颓、第九天 我趴在偏房一處隱蔽的房頂上張望合愈。 院中可真熱鬧,春花似錦击狮、人聲如沸佛析。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸莫。三九已至,卻和暖如春档冬,著一層夾襖步出監(jiān)牢的瞬間膘茎,已是汗流浹背桃纯。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辽狈,地道東北人慈参。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓呛牲,卻偏偏與公主長得像刮萌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子娘扩,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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