Retrofit 2.0 超能实践(三),轻松实现多文件/图片上传/Json字符串/表单

通过前两篇姿势的入门

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


通过对Retrofit2.0的前两篇的基础入门和案例实践,掌握了怎么样使用Retrofit访问网络,加入自定义header头,包括加入SSL证书,基本的调试基础,cookie同步问题,但很多场景需求是需要文件的上传的,今天主题就来分享怎么用Retrofit2.0+ RxJava 上传文件和图片,表单,包括上传Json等。其实有无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,我们发现以前的TypedFile类型被私有化了 ,无法继续使用1.9的传方式,因此2.x提供了上传方案,可以MultipartBody.Part代替。

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

具体用法

先看一个基本的用法:

// 创建 RequestBody,用于封装构建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);

// 执行请求
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());
      }
    });
}

上报一张图片

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

或者 :

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

上报数量确定的多张图片

@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);

如果图片数量不确定

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

图文同时上报

Part方式

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

扩展一下 将url动态化:

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

注意如果你用retrofit2.0以上的版本请看下面姿势,否则会如下抛异常!MultipartBody.Part的参数不能指定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 );
       // 执行
      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代码:

       //构建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 );
      // 执行
     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());
        }
      });
  
 此种方式让你很好的解决了用户**注册**问题,包含用户全部的信息和头像,完美解决你想用表单一起将文字和图片一起提交的情况!

# 表单提交 #

 很多时候想用表单的方式:

    @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);

接着可以这样调用:

   
    // 执行请求
    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());
        }
    });
}

至于服务器返回什么类型的**model**, 开发者可以自定义, 譬如你可以把APi 中的 ResponseBody 指定为你自己的`javaBean`, 当然上层构建Call时候,`Callback`也必须是 `Call<MyBean>`

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

仔细的朋友会发现有的地方用Call,有的地方用Observable,如果结合了RxJava就是用后者接收了,这里不关乎什么方式。

上面的代码片段中显示的代码初始化(RequestBody 和description), 以及如何使用文件上传API。正如刚开始已经提到的, 从OkHttp 的RequestBody类中,需要两个RequestBody.create()方法

除了Body描述, 必须将添加文件包装成MultipartBody的实例。这就是你需要使用适当的从客户端上传文件到服务端。此外, 您可以添加createFormData中的uploadFile(Uri fileUri)方法,适合相机拍照回来上传图片的场景。

      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;
    }

  通过以上代码片段,可以构造自己的file, 其他构建ResquestBody的步骤同上所示,以上案列总有一款适合你的web后端。

设置  Content-Type
=========================

请注意设置的内容类型。如果你拦截底层OkHttp客户端和更改内容类型为application / json, 你的服务器可能反序列化过程出现的问题。请确保你没有自定义更改multipart/form-data。

  **upLoad图片也可以具体指明Content-Type 为 “image/jpg”格式的**

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


上传文件到服务端示例
======================================

如果你已经有你的后端项目, 您可以依靠下面的示例代码。我们使用一个简单api 上传到服务器。此外我们告诉api 传入参数的请求, 因为我们使用的是Node.js  

解析的回调函数,我们记录每个字段来显示其输出。

    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客户端收到返回类型的字符串,  我们将接收到的上传成功的状态的回调。当然你可以处理也可以不处理状态。下面你将看到一个成功的请求的输出端和有效载荷的解析。第一个空对象。之后,你可以看到字段只描述作为请求的一部分。接着可以收到文件描述,文件大小,文件昵称和保存路径。

服务器解析有效数据的日志
-----------------------------

       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 } ] }
       
---------------------------------
回顾
==

文件上传是应用程序中必不可却少的功能, 你可以将此功能集成在您的应用程序使用。本文指导您完成你的Android程序上报文件到您的后端服务器的第一个步骤。

文件上传和下载进度实现,请继续关注后续文章!

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

Retrofit 2.0系列请阅读简书 
更多技术文章请关注码小白

**Retrofit 2.0+RxJava系列请阅读**

- [Retrofit 2.0(一) 超能实践,完美支持Https传输](http://blog.csdn.net/sk719887916/article/details/51597816)

- [Retrofit2.0(二) 完美同步Cookie实现免登录](http://blog.csdn.net/sk719887916/article/details/51700659)
   
- [Retrofit 2.0 超能实践(三),轻松实现文件/图片上传](http://blog.csdn.net/sk719887916/article/details/51700659)

- [Retrofit 2.0 超能实践(四),完成大文件断点下载](//www.greatytc.com/p/582e0a4a4ee9)

- [基于Retrofit2.0+RxJava 封装的超好用的RetrofitClient工具类(六)](http://blog.csdn.net/sk719887916/article/details/51958010)
 
- [玩转IOC,教你徒手实现自定义的Retrofit框架(七)](http://blog.csdn.net/sk719887916/article/details/51957819)

- [Retrofit,Okhttp对每个Request统一动态添加header和参数(五)](http://blog.csdn.net/sk719887916/article/details/52132106)
-  [Rxjava和Retrofit 需要掌握的几个实用技巧,缓存问题和统一对有无网络处理问题(八)](http://blog.csdn.net/sk719887916/article/details/52132106)
-  [Novate:对Retrofit2.0的又一次完美改进加强!(九)](http://blog.csdn.net/sk719887916/article/details/52195428)

第一时间获取技术文章请关注微信公众号!

![开发者技术前线](http://upload-images.jianshu.io/upload_images/2022038-a7b567ef3a0b0d1f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>Tamic : //www.greatytc.com/users/3bbb1ddf4fd5/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容