Retrofit 2.0 超能實(shí)踐(三)零如,輕松實(shí)現(xiàn)多文件/圖片上傳/Json字符串/表單

通過前兩篇姿勢的入門

本文出自:http://blog.csdn.net/sk719887916/article/details/51755427 碼小白


通過對Retrofit2.0的前兩篇的基礎(chǔ)入門和案例實(shí)踐雁歌,掌握了怎么樣使用Retrofit訪問網(wǎng)絡(luò),加入自定義header頭柱查,包括加入SSL證書廓俭,基本的調(diào)試基礎(chǔ),cookie同步問題唉工,但很多場景需求是需要文件的上傳的研乒,今天主題就來分享怎么用Retrofit2.0+ RxJava 上傳文件和圖片,表單淋硝,包括上傳Json等雹熬。其實(shí)有無rxjava,Retrofit的圖片上傳姿勢都一樣谣膳,api返回的call換成 Observable即可


使用 Retrofit1.x上傳文件

大家都知道在2.0以前版本上傳圖片的姿勢

 public interface ApiManager {
    @Multipart
    @POST("/user/addCarInfo")
    void addCarInfo(@QueryMap Map<String, Object> options, @Part("file") TypedFile file, Callback<JsonElement> response);

}

使用 Retrofit 2.X 上傳

Retrofit 2.X上傳文件

使用2.0竿报,我們發(fā)現(xiàn)以前的TypedFile類型被私有化了 ,無法繼續(xù)使用1.9的傳方式继谚,因此2.x提供了上傳方案烈菌,可以MultipartBody.Part代替。

 public interface FileUploadService {  
 @Multipart
 @POST("upload")
 Call<ResponseBody> upload(@Part("description") RequestBody description,
                          @Part MultipartBody.Part file);
}

具體用法花履。

先看一個(gè)基本的用法:

// 創(chuàng)建 RequestBody芽世,用于封裝構(gòu)建RequestBody
RequestBody requestFile =
        RequestBody.create(MediaType.parse("multipart/form-data"), file);

// MultipartBody.Part  和后端約定好Key,這里的partName是用image
MultipartBody.Part body =
        MultipartBody.Part.createFormData("image", file.getName(), requestFile);

// 添加描述
String descriptionString = "hello, 這是文件描述";
RequestBody description =
        RequestBody.create(
                MediaType.parse("multipart/form-data"), descriptionString);

// 執(zhí)行請求
Call<ResponseBody> call = service.upload(description, body);
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call,
                           Response<ResponseBody> response) {
        Log.v("Upload", "success");
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e("Upload error:", t.getMessage());
      }
    });
}

上報(bào)一張圖片

@Multipart
 @POST("you methd url upload/")
Call<ResponseBody> uploadFile(
        @Part() RequestBody file);

或者 :

@Multipart
@POST()
Observable<ResponseBody> uploads(
        @Url String url,
        @Part() MultipartBody.Part file);

上報(bào)數(shù)量確定的多張圖片

@POST("upload/")
Call<ResponseBody> uploadFiles(@Part("filename") String description,
                                     @Part("pic\"; filename=\"image1.png") RequestBody imgs1,
                                     @Part("pic\"; filename=\"image2.png") RequestBody imgs2,
                                     @Part("pic\"; filename=\"image3.png") RequestBody imgs3,
                                     @Part("pic\"; filename=\"image4.png") RequestBody imgs4);

如果圖片數(shù)量不確定

  @Multipart
 @POST()
  Observable<ResponseBody> uploadFiles(
        @Url String url,
        @PartMap() Map<String, RequestBody> maps);

圖文同時(shí)上報(bào)

Part方式

@Multipart
@POST("upload/")
Call<ResponseBody> register(
                                   @FieldMap Map<String , String> usermaps,
                                   @Part("avatar\"; filename=\"avatar.jpg") RequestBody avatar,
                                   );

擴(kuò)展一下 將url動態(tài)化:

@Multipart
@POST
Observable<ResponseBody> uploadFileWithPartMap(
        @Url() String url,
        @PartMap() Map<String, RequestBody> partMap,
        @Part("file") MultipartBody.Part file);

注意如果你用retrofit2.0以上的版本請看下面姿勢诡壁,否則會如下拋異常济瓢!MultipartBody.Part的參數(shù)不能指定part() 的Key。

java.lang.IllegalArgumentException: @Part parameters using the MultipartBody.Part must not include a part name in the annotation. (parameter #2)

@Multipart
 @POST
 Observable<ResponseBody> uploadFileWithPartMap(
         @Url() String url,
         @PartMap() Map<String, RequestBody> partMap,
         @Part  MultipartBody.Part file);

java代碼:

    String token ="dsdsddadad244";
    RequestBody requestFile =
                RequestBody.create(MediaType.parse("multipart/form-data"), file);

        // MultipartBody.Part is used to send also the actual file name
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

        // create a map of data to pass along
        RequestBody tokenBody = RequestBody.create(
                MediaType.parse("multipart/form-data"), token);

        HashMap<String, RequestBody> map = new HashMap<>();
        map.put("token", tokenBody);

        Call<ResponseBody> call = service.uploadFileWithPartMap(url, requestBody );
       // 執(zhí)行
      call.enqueue(new Callback<ResponseBody>() {
       @Override
       public void onResponse(Call<ResponseBody> call,
        Response<ResponseBody> response) {
           Log.v("Upload", "success");
        }
    
       @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
          Log.e("Upload error:", t.getMessage());
        }
      });

更簡單的用Body方式:

 @POST()
Call<ResponseBody> upLoad(
   @Url() String url,
   @Body RequestBody Body);

如果支持RxJava就是:

    @POST()
     Observable<ResponseBody> upLoad(
       @Url() String url,
       @Body RequestBody Body);
    ```` 

 Java代碼:

       //構(gòu)建body
       RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
          .addFormDataPart("name", name)
          .addFormDataPart("psd", psd)
          .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file))
          .build();
    
       //如果和rxjava1.x , call就換成 Observable
       Call<ResponseBody> call = service.upload(url, requestBody );
      // 執(zhí)行
     call.enqueue(new Callback<ResponseBody>() {
       @Override
       public void onResponse(Call<ResponseBody> call,
        Response<ResponseBody> response) {
           Log.v("Upload", "success");
        }
    
       @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
          Log.e("Upload error:", t.getMessage());
        }
      });
  
 此種方式讓你很好的解決了用戶**注冊**問題,包含用戶全部的信息和頭像妹卿,完美解決你想用表單一起將文字和圖片一起提交的情況葬荷!

# 表單提交 #

 很多時(shí)候想用表單的方式:

    @Multipart
    @POST("upload/")
    Call<ResponseBody> register(
           @Body RequestBody body
                                       );

  Java代碼:

       RequestBody requestFile =
    RequestBody.create(MediaType.parse("multipart/form-data"), file);

      Call<ResponseBody> call = service.register(body);  
    Response<ResponseBody> response = call.execute();
 

##Json提交

 
**上傳Json**

    @POST("/uploadJson")
    Observable<ResponseBody> uploadjson(
            @Body RequestBody jsonBody);

**upLoadJson 也可以具體指明Content-Type 為 “application/json”格式的**

具體組裝我們的RequestBody則可以這樣:

     RequestBody body= 
                   RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonString);

接著可以這樣調(diào)用:

   
    // 執(zhí)行請求
    Call<ResponseBody> call = service.uploadJson(description, body);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call,
                               Response<ResponseBody> response) {
            Log.v("Upload", "success");
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.e("Upload error:", t.getMessage());
        }
    });
}

至于服務(wù)器返回什么類型的**model**, 開發(fā)者可以自定義, 譬如你可以把APi 中的 ResponseBody 指定為你自己的`javaBean`, 當(dāng)然上層構(gòu)建Call時(shí)候纽帖,`Callback`也必須是 `Call<MyBean>`

    @POST("/uploadJson")
    Call<MyBean> uploadjson(
            @Body RequestBody jsonBody);

仔細(xì)的朋友會發(fā)現(xiàn)有的地方用Call宠漩,有的地方用Observable,如果結(jié)合了RxJava就是用后者接收了,這里不關(guān)乎什么方式懊直。

上面的代碼片段中顯示的代碼初始化(RequestBody 和description), 以及如何使用文件上傳API扒吁。正如剛開始已經(jīng)提到的, 從OkHttp 的RequestBody類中,需要兩個(gè)RequestBody.create()方法

除了Body描述, 必須將添加文件包裝成MultipartBody的實(shí)例室囊。這就是你需要使用適當(dāng)?shù)膹目蛻舳松蟼魑募椒?wù)端雕崩。此外, 您可以添加createFormData中的uploadFile(Uri fileUri)方法,適合相機(jī)拍照回來上傳圖片的場景融撞。

      public static File getFile(Context context, Uri uri) {
        if (uri != null) {
            String path = getPath(context, uri);
            if (path != null && isLocal(path)) {
                return new File(path);
            }
        }
        return null;
    }

  通過以上代碼片段盼铁,可以構(gòu)造自己的file, 其他構(gòu)建ResquestBody的步驟同上所示,以上案列總有一款適合你的web后端尝偎。

設(shè)置  Content-Type
=========================

請注意設(shè)置的內(nèi)容類型饶火。如果你攔截底層OkHttp客戶端和更改內(nèi)容類型為application / json, 你的服務(wù)器可能反序列化過程出現(xiàn)的問題鹏控。請確保你沒有自定義更改multipart/form-data。

  **upLoad圖片也可以具體指明Content-Type 為 “image/jpg”格式的**

       RequestBody requestFile =
                RequestBody.create(MediaType.parse("image/jpg"), mFile);


上傳文件到服務(wù)端示例
======================================

如果你已經(jīng)有你的后端項(xiàng)目, 您可以依靠下面的示例代碼肤寝。我們使用一個(gè)簡單api 上傳到服務(wù)器当辐。此外我們告訴api 傳入?yún)?shù)的請求, 因?yàn)槲覀兪褂玫氖荖ode.js  

解析的回調(diào)函數(shù),我們記錄每個(gè)字段來顯示其輸出。

    method: 'POST', 
     path: '/upload',  
    config: {  
    payload: {
        maxBytes: 209715200,
        output: 'stream',
        parse: false
    },
    handler: function(request, reply) {
        var multiparty = require('multiparty');
        var form = new multiparty.Form();
        form.parse(request.payload, function(err, fields, files) {
            console.log(err);
            console.log(fields);
            console.log(files);

            return reply(util.inspect({fields: fields, files: files}));
        });
    }}

Android客戶端收到返回類型的字符串,  我們將接收到的上傳成功的狀態(tài)的回調(diào)鲤看。當(dāng)然你可以處理也可以不處理狀態(tài)缘揪。下面你將看到一個(gè)成功的請求的輸出端和有效載荷的解析。第一個(gè)空對象义桂。之后,你可以看到字段只描述作為請求的一部分找筝。接著可以收到文件描述,文件大小慷吊,文件昵稱和保存路徑袖裕。

服務(wù)器解析有效數(shù)據(jù)的日志
-----------------------------

       Null
      { description: [ 'hello, this is description speaking' ] }
    { picture:
      [ { fieldName: 'picture',
        originalFilename: '20160312_095248.jpg',
        path:      '/var/folders/rq/q_m4_21j3lqf1lw48fqttx_80000gn/T/X_sxX6LDUMBcuUcUGDMBKc2T.jpg',
       headers: [Object],
       size: 39369 } ] }
       
---------------------------------
回顧
==

文件上傳是應(yīng)用程序中必不可卻少的功能, 你可以將此功能集成在您的應(yīng)用程序使用。本文指導(dǎo)您完成你的Android程序上報(bào)文件到您的后端服務(wù)器的第一個(gè)步驟罢浇。

文件上傳和下載進(jìn)度實(shí)現(xiàn)陆赋,請繼續(xù)關(guān)注后續(xù)文章沐祷!

**源碼:[https://github.com/Tamicer/Novate](https://github.com/Tamicer/Novate)**

Retrofit 2.0系列請閱讀簡書 
更多技術(shù)文章請關(guān)注碼小白

**Retrofit 2.0+RxJava系列請閱讀**

- [Retrofit 2.0(一) 超能實(shí)踐嚷闭,完美支持Https傳輸](http://blog.csdn.net/sk719887916/article/details/51597816)

- [Retrofit2.0(二) 完美同步Cookie實(shí)現(xiàn)免登錄](http://blog.csdn.net/sk719887916/article/details/51700659)
   
- [Retrofit 2.0 超能實(shí)踐(三),輕松實(shí)現(xiàn)文件/圖片上傳](http://blog.csdn.net/sk719887916/article/details/51700659)

- [Retrofit 2.0 超能實(shí)踐(四)赖临,完成大文件斷點(diǎn)下載](http://www.reibang.com/p/582e0a4a4ee9)

- [基于Retrofit2.0+RxJava 封裝的超好用的RetrofitClient工具類(六)](http://blog.csdn.net/sk719887916/article/details/51958010)
 
- [玩轉(zhuǎn)IOC胞锰,教你徒手實(shí)現(xiàn)自定義的Retrofit框架(七)](http://blog.csdn.net/sk719887916/article/details/51957819)

- [Retrofit,Okhttp對每個(gè)Request統(tǒng)一動態(tài)添加header和參數(shù)(五)](http://blog.csdn.net/sk719887916/article/details/52132106)
-  [Rxjava和Retrofit 需要掌握的幾個(gè)實(shí)用技巧,緩存問題和統(tǒng)一對有無網(wǎng)絡(luò)處理問題(八)](http://blog.csdn.net/sk719887916/article/details/52132106)
-  [Novate:對Retrofit2.0的又一次完美改進(jìn)加強(qiáng)>ふァ(九)](http://blog.csdn.net/sk719887916/article/details/52195428)

第一時(shí)間獲取技術(shù)文章請關(guān)注微信公眾號嗅榕!

![開發(fā)者技術(shù)前線](http://upload-images.jianshu.io/upload_images/2022038-a7b567ef3a0b0d1f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>Tamic : http://www.reibang.com/users/3bbb1ddf4fd5/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吵聪,隨后出現(xiàn)的幾起案子凌那,更是在濱河造成了極大的恐慌,老刑警劉巖吟逝,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帽蝶,死亡現(xiàn)場離奇詭異,居然都是意外死亡块攒,警方通過查閱死者的電腦和手機(jī)励稳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囱井,“玉大人驹尼,你說我怎么就攤上這事∨优唬” “怎么了新翎?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我料祠,道長骆捧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任髓绽,我火速辦了婚禮敛苇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顺呕。我一直安慰自己枫攀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布株茶。 她就那樣靜靜地躺著来涨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪启盛。 梳的紋絲不亂的頭發(fā)上蹦掐,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音僵闯,去河邊找鬼卧抗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鳖粟,可吹牛的內(nèi)容都是我干的社裆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼向图,長吁一口氣:“原來是場噩夢啊……” “哼泳秀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起榄攀,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嗜傅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后檩赢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吕嘀,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年漠畜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了币他。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡憔狞,死狀恐怖蝴悉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瘾敢,我是刑警寧澤拍冠,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布尿这,位于F島的核電站,受9級特大地震影響庆杜,放射性物質(zhì)發(fā)生泄漏射众。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一晃财、第九天 我趴在偏房一處隱蔽的房頂上張望叨橱。 院中可真熱鬧,春花似錦断盛、人聲如沸罗洗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伙菜。三九已至,卻和暖如春命迈,著一層夾襖步出監(jiān)牢的瞬間贩绕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工壶愤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淑倾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓公你,卻偏偏與公主長得像踊淳,于是被迫代替她去往敵國和親假瞬。 傳聞我的和親對象是個(gè)殘疾皇子陕靠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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