Android开发Android技术知识

Retrofit源码之http信息拼装(三)

2018-04-17  本文已影响53人  低情商的大仙

通过前几篇文章的分析,对于Retrofit的注解我们基本大致了解了是如何解析的,又是如何将数据委托给OkHttp的,但是我们还剩下一个@Part,@PartMap,@Body注解。

1. @Part

首先我们看下正常的上传文件是如何定义的:

@Multipart
    @POST("UploadServlet")
    Call<ResponseBody> upload(@Part("description") RequestBody description,
                              @Part MultipartBody.Part file);

从这里我们可以看到,@Part注解有两种情形,一种是有name的,一种是没有的。知道这些我们再看源码:

else if (annotation instanceof Part) {
        if (!isMultipart) {
          throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
        }
        Part part = (Part) annotation;
        gotPart = true;

        String partName = part.value();
        Class<?> rawParameterType = Utils.getRawType(type);
        if (partName.isEmpty()) {
            // ……
        } else {
           // ……
          
        }

首先确保使用@Part注解时必须使用@MultiPart注解,然后获取@Part注解的name,结合上面提到的@Part注解使用时的两种情况,这里对name是否为空分别处理。

加了name属性

对于加了name属性的情况:

else {
         //构建上传文件所需要的headers
          Headers headers =
              Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"",
                  "Content-Transfer-Encoding", part.encoding());

         //判断参数类型是否是Iterable类型
          if (Iterable.class.isAssignableFrom(rawParameterType)) {
            if (!(type instanceof ParameterizedType)) {
              throw parameterError(p, rawParameterType.getSimpleName()
                  + " must include generic type (e.g., "
                  + rawParameterType.getSimpleName()
                  + "<String>)");
            }
            ParameterizedType parameterizedType = (ParameterizedType) type;
            //获取Iterable集合中数据的类型
            Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
            if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
            //此处保证加了name属性的@Part注解 所修饰的参数不能是MultipartBody.Part类型
              throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
                  + "include a part name in the annotation.");
            }
            Converter<?, RequestBody> converter =
                retrofit.requestBodyConverter(iterableType, annotations, methodAnnotations);
            return new ParameterHandler.Part<>(headers, converter).iterable();
          } else if (rawParameterType.isArray()) {
          //判断修饰的参数是否是数组类型
            Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
            if (MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
              throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
                  + "include a part name in the annotation.");
            }
            Converter<?, RequestBody> converter =
                retrofit.requestBodyConverter(arrayComponentType, annotations, methodAnnotations);
            return new ParameterHandler.Part<>(headers, converter).array();
          } else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
            throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
                + "include a part name in the annotation.");
          } else {
            Converter<?, RequestBody> converter =
                retrofit.requestBodyConverter(type, annotations, methodAnnotations);
            return new ParameterHandler.Part<>(headers, converter);
          }

源码每一句都不难懂,尤其是稍微复杂的地方我都加了注释,这里主要解释下源码的思路。
首先,对于加了name的注解来说,必须要利用name生成一个Header。
然后就是之前遇到的常规操作,将注解修饰的参数分为了三类,Iterable类型、数组类型、常规类型,分别生成对应的Handler。但是我们也清楚,Iterable类型和数组类型相对于常规类型没有本质上的区别,只是前两种对应的Handler的apply方法被重写了,会遍历所有参数分别执行常规参数对应的Handler的apply方法罢了。
所以我们重点看常规参数类型的Handler怎么生成的:

else {
            Converter<?, RequestBody> converter =
                retrofit.requestBodyConverter(type, annotations, methodAnnotations);
            return new ParameterHandler.Part<>(headers, converter);
          }

这里主要就是拿到了一个converter加上之前生成的Header构造了一个ParameterHandler.Part对象罢了。然后我们再看下apply方法:

@Override 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 new RuntimeException("Unable to convert " + value + " to RequestBody", e);
      }
      builder.addPart(headers, body);
    }

可以看到,这里主要就是利用converter将我们传入的参数转成了RequestBody对象,然后和headers一起塞进了RequestBuilder中。

总结下,@Part如果加了name属性的话,不允许修饰MultipartBody.Part类型,利用name然后会生成一个header,拿到一个converter,生成一个对应的Handler,该handler提供了一个apply方法可以将必要数据设置到RequestBuilder中去。

不加name属性

接下来我们看下不加name的情况:

if (partName.isEmpty()) {
          if (Iterable.class.isAssignableFrom(rawParameterType)) {
            if (!(type instanceof ParameterizedType)) {
              throw parameterError(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(p,
                  "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
            }
            return ParameterHandler.RawPart.INSTANCE.iterable();
          } else if (rawParameterType.isArray()) {
            Class<?> arrayComponentType = rawParameterType.getComponentType();
            if (!MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
              throw parameterError(p,
                  "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
            }
            return ParameterHandler.RawPart.INSTANCE.array();
          } else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
            return ParameterHandler.RawPart.INSTANCE;
          } else {
            throw parameterError(p,
                "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
          }
        }

有了之前的基础,这里就很好分析了。同样也是对注解修饰的参数分成了Iterable、数组、常规类型三类,都做了检查,这里要保证不加name时参数只能是MultipartBody.Part类型,是固定的,也就不需要converter来转换,但不同的是这里生成的不再是ParameterHandler.Part对象,而是ParameterHandler.RawPart类型。我们来研究下它的apply方法能干什么:

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

这里主要就是将MultipartBody.Part对象塞给RequestBuilder对象。

2.@PartMap

@PartMap其实就是@Part的加强版,我们先看下他的用法:

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

从用法中我们可以推测出,其实就是将@Part的带name的用法和不带name的用法综合起来了,我们主要看下对应的Handler的apply用法:

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

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

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

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

从这里可以看出,利用了map中的key构造了Headers,相当于@Part注解中带name的用法,用value构造了body,相当于@Part中不带name的用法。

3.@Body

先上代码:

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

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

这里没什么,主要也是拿了一个Converter,然后生成了一个Handler对象,现在我们都可以推测出这个handler的apply方法就是将这个body用converter转换下传给一个RequestBuilder,看看是不是:

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

看到源码,一切不言而喻。

总结

进过三篇文章,我们将Retrofit的注解处理全部了解了一遍,下面我们主要总结以下几点:

result = new ServiceMethod.Builder<>(this, method).build();

即:在第一次调用自定义的接口方法时处理注解,之后将会缓存起来,无需重新分析注解。

上一篇下一篇

猜你喜欢

热点阅读