Retrofit 2.x上传文件的两种方式及源码分析

A 使用@Part注解

@Multipart
@POST("update/img")
Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part MultipartBody.Part img);

构造请求数据时采用如下方式:

RequestBody requestFile = RequestBody.create(img, MediaType.parse("image/jpeg"));
// MultipartBody.Part is used to send also the actual filename
MultipartBody.Part img = MultipartBody.Part.createFormData("img", img.getName(), requestFile);

RequestBody param1 = RequestBody.create("param1", null);

Call<ResponseBody> call = service.postImg(param1, body);

如果是多文件:

@Multipart
@POST("update/img")
Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part MultipartBody.Part[] imgs);
// Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part List<MultipartBody.Part> imgs);

构造请求数据时传数组/List即可。

B 使用@Body注解

@POST("update/img")
Call<ResponseBody> postImg(@Body RequestBody requestBody);

注意:此处不能添加@Multipart@FormUrlEncoded注解,不然会报错。

构造请求数据时采用如下方式:

MultipartBody.Builder builder = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("param1","param1")
    	.addFormDataPart("img", img.getName(), RequestBody.create(img, MediaType.parse("image/jpeg")));

postImg(builder.build());

如果是多文件,接口注解不变,在添加文件时在继续执行addFormDataPart即可。

实际应用中,两种方式效果完全相同,那么,这两种方式在实现上有什么区别吗?

源码分析

Retrofit解析注解的步骤如下:

  1. 解析方法注解;
  2. 解析参数和参数注解。

RequestFactory类中解析方法注解部分代码如下:

private void parseMethodAnnotation(Annotation annotation) {
  ...
  } else if (annotation instanceof Multipart) {
    if (isFormEncoded) {
      throw methodError(method, "Only one encoding annotation is allowed.");
    }
    isMultipart = true;
  ...
}

观察代码,发现如果方法注解中含有Multipart,则另isMultiparttrue

下面我们来观察RequestFactory类中解析参数注解的部分代码:

  1. @Part的解析

    首先会根据isMultipart判断方法是否含有Multipart注解,没有注解就报错:

    if (!isMultipart) {
      throw parameterError(
          method, p, "@Part parameters can only be used with multipart encoding.");
    }
    

    然后得到@Part的值,对于@Part的值有以下几种可能。

    • @Part值为空,此时认为参数类型必须为Multipart.bodyMultipart.body数组或Iterable类型,如果不为以上三者,则报异常:

      throw parameterError(
            method,
            p,
            "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
      
    • @Part值为空,参数类型为Multipart.body:

      if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
         return ParameterHandler.RawPart.INSTANCE;
      }
      
    • @Part值为空,参数类型为Iterable

      if (Iterable.class.isAssignableFrom(rawParameterType)) { 
         if (!(type instanceof ParameterizedType)) {
           throw parameterError(
               method,
               p,
               rawParameterType.getSimpleName()
                   + " must include generic type (e.g., "
                   + rawParameterType.getSimpleName()
                   + "<String>)");
         }
         ParameterizedType parameterizedType = (ParameterizedType) type;
         Type iterableType = Utils.getParameterUpperBound(0, parameterizedType); 
         if (!MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
           throw parameterError(
               method,
               p,
               "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
         }
         return ParameterHandler.RawPart.INSTANCE.iterable();
      }
      

      这里首先判断泛型是否确定类型,然后判断泛型类型是否为MultipartBody.Part

    • @Part值为空,参数类型为Array

      if (rawParameterType.isArray()) {
         Class<?> arrayComponentType = rawParameterType.getComponentType();
         if (!MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
           throw parameterError(
               method,
               p,
               "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
         }
         return ParameterHandler.RawPart.INSTANCE.array();
      }
      
    • @Part值不为空,参数类型为MultipartBody.Part,则报错

      if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
          throw parameterError(
              method,
              p,
              "@Part parameters using the MultipartBody.Part must not "
              + "include a part name in the annotation.");
      } 
      
    • @Part值不为空,参数类型不为MultipartBody.Part

      Converter<?, RequestBody> converter =
          retrofit.requestBodyConverter(type, annotations, methodAnnotations);
      return new ParameterHandler.Part<>(method, p, headers, converter);
      
  2. @PartMap的解析

    @Part,首先会根据isMultipart判断方法是否含有Multipart注解,没有注解就报错。

    接着获取参数类型。

    • 判断参数类型是否为Map,不是则报错。

      if (!Map.class.isAssignableFrom(rawParameterType)) {
          throw parameterError(method, p, "@PartMap parameter type must be Map.");
      }
      
    • 判断Map是否指定泛型类型,未指定则报错。

      if (!(mapType instanceof ParameterizedType)) {
         throw parameterError(
             method, p, "Map must include generic types (e.g., Map<String, String>)");
      }
      
    • 判断Map键类型是否为String,不是则报错。

      if (String.class != keyType) {
        throw parameterError(method, p, "@PartMap keys must be of type String: " + keyType);
      }
      
    • 判断Map值类型是否为Multipart.body,是则报错。

      if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(valueType))) {
        throw parameterError(
            method,
            p,
            "@PartMap values cannot be MultipartBody.Part. "
                + "Use @Part List<Part> or a different value type instead.");
      }
      
  3. @Body的解析

    首先会判断是否方法注解是否包含@FormUrlEncoded@Multipart注解,有则报错。

    if (isFormEncoded || isMultipart) {
      throw parameterError(
          method, p, "@Body parameters cannot be used with form or multi-part encoding.");
    }
    

    然后调用转换器,尝试将数据转换为RequestBody

    Converter<?, RequestBody> converter;
    try {
      converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
    } catch (RuntimeException e) {
      // Wide exception range because factories are user code.
      throw parameterError(method, e, p, "Unable to create @Body converter for %s", type);
    }
    gotBody = true;
    return new ParameterHandler.Body<>(method, p, converter);
    

解析成功之后,会将数据传至ParameterHandler类进一步解析,下面让我们看看该类做了什么。

  1. 对于@Part注解,涉及到ParameterHandler.PartParameterHandler.RawPart内部类,其中当@Part值不为空时,使用Part类解析,否则使用RawPart解析。

  2. 对于@PartMap注解,使用ParameterHandler.Part进行解析。

  3. 对于@Body注解,使用ParameterHandler.Body进行解析。

他们都含有一个共同的方法:

Part

void apply(RequestBuilder builder, @Nullable T value) {
  if (value == null) return; // Skip null values.

  RequestBody body;
  try {
    body = converter.convert(value);
  } catch (IOException e) {
    throw Utils.parameterError(method, p, "Unable to convert " + value + " to RequestBody", e);
  }
  builder.addPart(headers, body);
}

PartMap

void apply(RequestBuilder builder, @Nullable Map<String, T> value) throws IOException {
  if (value == null) {
    throw Utils.parameterError(method, p, "Part map was null.");
  }

  for (Map.Entry<String, T> entry : value.entrySet()) {
    String entryKey = entry.getKey();
    if (entryKey == null) {
      throw Utils.parameterError(method, p, "Part map contained null key.");
    }
    T entryValue = entry.getValue();
    if (entryValue == null) {
      throw Utils.parameterError(
          method, p, "Part map contained null value for key '" + entryKey + "'.");
    }

    okhttp3.Headers headers =
        okhttp3.Headers.of(
            "Content-Disposition",
            "form-data; name=\"" + entryKey + "\"",
            "Content-Transfer-Encoding",
            transferEncoding);

    builder.addPart(headers, valueConverter.convert(entryValue));
  }
}

RawPart

void apply(RequestBuilder builder, @Nullable MultipartBody.Part value) {
  if (value != null) { // Skip null values.
    builder.addPart(value);
  }
}

Body

void apply(RequestBuilder builder, @Nullable T value) {
    if (value == null) {
      throw Utils.parameterError(method, p, "Body parameter value must not be null.");
    }
    RequestBody body;
    try {
      body = converter.convert(value);
    } catch (IOException e) {
      throw Utils.parameterError(method, e, p, "Unable to convert " + value + " to RequestBody");
    }
    builder.setBody(body);
  }
}

我们只关系builder.addPartbuilder.setBody方法。

  • 查看addPart方法源码,该方法在RequestBuilder类中:

    private @Nullable MultipartBody.Builder multipartBuilder;
    
    void addPart(Headers headers, RequestBody body) {
      multipartBuilder.addPart(headers, body);
    }
    
    void addPart(MultipartBody.Part part) {
      multipartBuilder.addPart(part);
    }
    

    对于RawPart类型,将采用addPart(MultipartBody.Part part)方法,对于Part类型,将采用addPart(Headers headers, RequestBody body)方法。然后,我们查看MultipartBody.Builder中的addPart方法为:

    fun addPart(part: Part) = apply {
      parts += part
    }
    
  • 查看setBody源码

    private @Nullable RequestBody body;
    
    void setBody(RequestBody body) {
      this.body = body;
    }
    
  • 获得Request.Builder方法

    Request.Builder get() {
      ...
    
      RequestBody body = this.body;
      if (body == null) {
        // Try to pull from one of the builders.
        if (formBuilder != null) {
          body = formBuilder.build();
        } else if (multipartBuilder != null) {
          body = multipartBuilder.build();
        } else if (hasBody) {
          // Body is absent, make an empty body.
          body = RequestBody.create(null, new byte[0]);
        }
      }
    
      ...
    }
    

    在该方法中,首先判断body是否为空,如果为空则调用formBuildermultipartBuilder创建一个body

  • 最后,我们再看一下MultipartBody.Builder中的addFormDataPart方法

    fun addFormDataPart(name: String, filename: String?, body: RequestBody) = apply {
      addPart(Part.createFormData(name, filename, body))
    }
    
    fun addFormDataPart(name: String, value: String) = apply {
      addPart(Part.createFormData(name, value))
    }
    

    可以看出最终还是调用了addPart方法。

    fun addPart(part: Part) = apply {
      parts += part
    }
    

分析到这里,我们可以明白,原来两种上传文件方式只是表达方式不同,实际上都会调用MultipartBody.Builderbuild方法创建RequetBody,只不过第一种方式创建是交给了Retrofit中的RequestBuilder类,而第二种是由我们自己调用MultipartBody.Builderbuild方法创建而已。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值