美文网首页AndroidWorldAndroidAndroid
Retrofit 2.0 超能实践(三),轻松实现多文件/图片上

Retrofit 2.0 超能实践(三),轻松实现多文件/图片上

作者: Tamic | 来源:发表于2016-06-24 20:12 被阅读69293次

    通过前两篇姿势的入门

    本文出自: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 超能实践(四),完成大文件断点下载](http://www.jianshu.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:https://img.haomeiwen.com/i2022038/a7b567ef3a0b0d1f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    >Tamic : http://www.jianshu.com/users/3bbb1ddf4fd5/

    相关文章

      网友评论

      • kyriej2:Call<ResponseBody> register(
        @FieldMap Map<String , String> usermaps,
        @pART("avatar\"; filename=\"avatar.jpg") RequestBody avatar,
        );

        这里的@Part部分里面带反斜杠能具体说下嘛,知道是转义,想知道下拼接规则
      • wmjwmj:使用Multipart。用拦截器拦截的时候,RequestBody是包含field文字和part图片吗? 怎么区分呢
      • 42f50affdaa2:非常感谢 整了一天 找到一个有用资料
      • 82fb67508fa0:上传图片二进制数据怎么用操作符?求指导
      • 刺鸟开源:好文章
      • 街道shu记:大白,你好
        Tamic:@街道书记 书记你好
      • aca16273a847:参考和译文不带原文链接就是窃取和抄袭。。。
      • L_Xian:其中有一张或多张上传失败要怎么重试
      • 只爱吃辣条:请教下:
        我的上传多张图片是这么写的:
        @Multipart
        @post("upload")
        Observable<RegisterEntity> getBrokeData(@Part MultipartBody.Part token, @pART("user_login") String phone, @pART("text") String content, @PartMap Map<String, RequestBody> params);

        for (int i = 0; i < size - 1; i++) {
        Album album = albums.get(i);
        MyLog.mLog("album.getImagePath() = " + album.getImagePath());
        File file = new File(album.getImagePath());
        RequestBody requestFile = RequestBody.create(MediaType.parse("image/png"), file);
        params.put("file" + i + "\"; filename=\"" + file.getName(), requestFile);
        }
        但是执行之后返回的是HTTP 404 Not Found这个错误;服务器端对于图片的参数的定义名字是 "img" 类型是String 是不是因为这个原因导致的?
      • 破音男:up主,链接三改下,跳错了
        Tamic: @破音男 还是你细心
      • cb9ceedb1def:请教哈图片和map信息一起传得写法
        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 );
        这里面的token,requestBody 哪来的,而且上面的file和map都没有用上
        Tamic:@cb9ceedb1def token自己的数据啊 就是一个String而已
      • 繁复至极返璞归简:这文章译的好生硬。。
        Tamic:@繁复至极返璞归简 :smile::smile:
      • b9f7216e5187:java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 19 path $.data
        post Json一直报这个错??
        Small_Cake:这个是你的数据类型转换不对吧,都给你提示了第几行了
      • 石野小真人:java.lang.IllegalArgumentException: @FieldMap parameters can only be used with form encoding .
        Tamic:@水牛 文件上传并不是只是单纯的客户端决定的,还要看你服务段接口怎么定义,甚至支持怎样传发,下面的是比较通用的,如果不行的话,没人能帮你

        @POST()
        Call<ResponseBody> upLoad(
        @URL() String url,
        @body RequestBody Body);
        Java代码:

        //构建body
        RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
        .addFormDataPart("name", name)
        .addFormDataPart("name", psd)
        .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file))
        .build();
        石野小真人:@Tamic 信口开河!上传接口如果有其他字符串参数,那么这些参数也应该包装成使用RequestBody,并使用@PartMap注解.
        uploadWithProgress(@Url String url,@PartMap Map<String, RequestBody> strParams,@PartMap Map<String, RequestBody> files) ;

        @FieldMap直接抛异常,@QueryMap无法传递参数.
        Tamic:@水牛01248 去掉编码模式就行了
      • c9d315830715:大神写的不错,不过带下自己的服务器最好
        Tamic:@黑色夜幕 服务器不好带呀
      • 639b7a5a8aa0:不错
        Tamic:@dengdai 感谢
      • 贰yang:如果,我上传的多张图片是不确定的呢?咋搞?
        d5ba366035a2:如果map的键名是一样的呢,会出现覆盖的现象,这个怎么解决呢
        Tamic:@PATH_洋 @Multipart
        @POST("{url}")
        Observable<ResponseBody> uploadFiles(
        @path("url") String url,
        @pART("filename") String description,
        @PartMap() Map<String, RequestBody> maps);

        @Multipart
        @POST("upload")
        Observable<ResponseBody> uploads(@Part("description") RequestBody description,
        @pART("key") MultipartBody.Part file);
        Tamic:@PATH_洋 用map可以的
      • 小渚:楼主能加我QQ694113370 请教点retrofit问题
        Tamic:@小渚 可以加1057531664
      • 薄炳鑫:加油,关注!!!
        Tamic:@薄炳鑫 谢谢支持
      • addapp: @Query 能用@post上 不报错 你确定吗???
        addapp:@Tamic_码小白 不是吧 我的报错 415 类型错误 因为@query 是用于get请求的 不能用于post请求 好奇怪
        Tamic:@addapp 不报错
        Tamic:@addapp 不报错
      • 小鄧子:为什么不把参考的英文原文链接附上?
        小鄧子:@Tamic 错!不能忽略原作的功劳。
        Tamic:@小鄧子 对于入门的同学来学,外文没啥卵用
        Tamic:@小鄧子 https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server
      • 丛蛋白:你好 ,能不能指点一下,现在项目也想换这个框架 能告诉我 怎么处理提交post的时候参数被转义,提交的参数是 json 实现 参数加密 响应解密 思路能指点一下吗。是在拦截器处理还是自定义转换器呢 我自己试了一下都不行。。求指点
        丛蛋白: @Tamic_码小白 嗯 谢谢
        Tamic:@丛蛋白 请求头里面加个支持json 就可以了
        Tamic:@丛蛋白 都可以的

      本文标题:Retrofit 2.0 超能实践(三),轻松实现多文件/图片上

      本文链接:https://www.haomeiwen.com/subject/etaodttx.html