欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Retrofit2.0 实现图文(参数+图片)上传方法总结

程序员文章站 2023-10-20 09:57:14
最近项目里用到了类似图文上传的功能,以前都是封装okhttp的文件上传功能,这次想换个姿势,想用retrofit2.0实现这样的功能,本来以为挺简单的,没想到进入了深坑,连...

最近项目里用到了类似图文上传的功能,以前都是封装okhttp的文件上传功能,这次想换个姿势,想用retrofit2.0实现这样的功能,本来以为挺简单的,没想到进入了深坑,连续调整了好几种姿势都报了同一个错,接着网上类似的文章找了一大推,讲得都是模棱两可,或者对多参数格式不够友好,最后还是去看了相关的源码,自己把这个问题提出来解决了,在这里记录一下。

一、定义网络请求接口

public interface goodsreturnapiservice {
  @multipart
  @post(compares.goods_return_post)  //这里是自己post文件的地址
  observable<goodsreturnpostentity> postgoodsreturnpostentitys(@partmap map<string, requestbody> map, @part list<multipartbody.part> parts);
}

上面定义了一个接口用于上传文件请求,有几个注解需要说明一下, @multipart这是retrofit专门用于文件上传的注解,需要配合@post一起使用。

方法postgoodsreturnpostentitys(@partmap map<string, requestbody> map, @part list<multipartbody.part> parts)第一个参数使用注解@partmap用于多参数的情况,如果是单个参数也可使用注解@part。

在类型map<string, requestbody>中,map第一个泛型string是服务器接收用于文件上传参数字段的key,第二个泛型requestbody是okhttp3包装的上传参数字段的value,这也是图文上传成功的关键所在。在后面会具体说到。

第二个参数使用注解@part用于文件上传,多文件上传使用集合类型list<multipartbody.part>,单文件可以使用类型multipartbody.part,具体的使用同样后面讲。

这里着重说明一下,postgoodsreturnpostentitys(@partmap map<string, requestbody> map, @part list<multipartbody.part> parts)方法参数这样写纯属个人习惯,你也可以直接使用一个参数postgoodsreturnpostentitys(@partmap map<string, requestbody> map),不过后面对requestbody的处理方式也要跟着变化,这里就不详细说了,只会介绍上面这种简便清晰的方式。

二、初始化retrofit

public class httprequestclient {

  public static final string tag = "httprequestclienttag";

  private static retrofit retrofit;

  private static okhttpclient getokhttpclient() {
    //日志显示级别
    httplogginginterceptor.level level= httplogginginterceptor.level.body;
    //新建log拦截器
    httplogginginterceptor logginginterceptor=new httplogginginterceptor(new httplogginginterceptor.logger() {
      @override
      public void log(string message) {
        log.d(tag, message);
      }
    });
    logginginterceptor.setlevel(level);
    //定制okhttp
    okhttpclient.builder httpclientbuilder = new okhttpclient
        .builder();
    //okhttp进行添加拦截器logginginterceptor
    httpclientbuilder.addinterceptor(logginginterceptor);
    return httpclientbuilder.build();
  }

  public static retrofit getretrofithttpclient(){
    if(null == retrofit){
      synchronized (httprequestclient.class){
        if(null == retrofit){
          retrofit = new retrofit.builder()
              .client(getokhttpclient())
              .baseurl(compares.url)
              .addconverterfactory(gsonconverterfactory.create())
              .addcalladapterfactory(rxjava2calladapterfactory.create())
              .build();
        }
      }
    }
    return retrofit;
  }
}

为了演示,retrofit封装比较简陋,为的是查看网络拦截,就不详细说了。

三、发起文件上传请求

private void postgoodspictoserver(){
    map<string,requestbody> params = new hashmap<>();
    //以下参数是伪代码,参数需要换成自己服务器支持的
    params.put("type", converttorequestbody("type"));
    params.put("title",converttorequestbody("title"));
    params.put("info",converttorequestbody("info");
    params.put("count",converttorequestbody("count"));

    //为了构建数据,同样是伪代码
    string path1 = environment.getexternalstoragedirectory() + file.separator + "test1.jpg";
    string path2 = environment.getexternalstoragedirectory() + file.separator + "test1.jpg";

    list<file> filelist = new arraylist<>();

    filelist.add(new file(path1));
    filelist.add(new file(path2));

    list<multipartbody.part> partlist = filestomultipartbodyparts(filelist);

    httprequestclient.getretrofithttpclient().create(goodsreturnapiservice.class)
        .postgoodsreturnpostentitys(params,partlist)
        .subscribeon(schedulers.newthread())
        .observeon(androidschedulers.mainthread())
        .subscribe(new observer<goodsreturnpostentity>() {
          @override
          public void onsubscribe(@nonnull disposable d) {

          }

          @override
          public void onnext(@nonnull goodsreturnpostentity goodsreturnpostentity) {

          }

          @override
          public void onerror(@nonnull throwable e) {

          }

          @override
          public void oncomplete() {

          }
        });
}

上面的params和filelist都是构造的伪代码,需要根据自己项目的业务需求改变。

下面是上传文件成功第一个关键,对参数请求头(姑且叫这个名字,对应retrofit上传文件时参数那部分请求头,下文件(图片)请求头同理,对应文件那部分请求头)的content-type赋值,使用converttorequestbody()方法。

private requestbody converttorequestbody(string param){
    requestbody requestbody = requestbody.create(mediatype.parse("text/plain"), param);
    return requestbody;
  }

因为gsonconverterfactory.create()转换器的缘故,会将参数请求头的content-type值默认赋值application/json,如果没有进行这步转换操作,就可以在okhttp3的日志拦截器中查看到这样的赋值,这样导致服务器不能正确识别参数,导致上传失败,所以这里需要对参数请求头的content-type设置一个正确的值:text/plain。

下面是上传文件成功第二个关键的地方,将文件(图片)请求头的content-type使用方法filestomultipartbodyparts()对其赋值"image/png",并返回multipartbody.part集合。

private list<multipartbody.part> filestomultipartbodyparts(list<file> files) {
    list<multipartbody.part> parts = new arraylist<>(files.size());
    for (file file : files) {
      requestbody requestbody = requestbody.create(mediatype.parse("image/png"), file);
      multipartbody.part part = multipartbody.part.createformdata("multipartfiles", file.getname(), requestbody);
      parts.add(part);
    }
    return parts;
  }

说到底,还是对参数请求头和文件(图片)请求头的content-type属性赋值处理,不要让retrofit 默认赋值,这里才是关键。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。