美文网首页Android开发Android技术知识
Retrofit源码之http信息拼装(三)

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

作者: 低情商的大仙 | 来源:发表于2018-04-17 20:57 被阅读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的注解处理全部了解了一遍,下面我们主要总结以下几点:

    • 注解是什么时候处理的?
      其实核心的处理可以看到是在Retrofit的loadServiceMethod方法中的这句:
    result = new ServiceMethod.Builder<>(this, method).build();
    

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

    • 注解修饰的参数类型是固定的吗?
      视情况而定,凡是处理注解时获取了Converter对象的都能够通过自定义converter对象来处理任意类型,比如@Header,如果没有获取converter对象的,则不可以,比如@Part不加name属性时。
    • 能否自定义注解?
      从现有源码来看,由于是通过if-else的形式去处理一个个注解的,所以不修改Retrofit源码,无法自定义注解。

    相关文章

      网友评论

        本文标题:Retrofit源码之http信息拼装(三)

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