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

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

作者: 低情商的大仙 | 来源:发表于2018-04-03 21:32 被阅读171次

    之前在Retrofit细节之http信息拼装(一)中,我们分析了Retrofit是如何处理方法级别的注解信息的,并且明确了对参数级别的注解解析就是每个注解都生成一个ParameterHandler,事先明确一点,我们这里记录的都是注解的信息,至于方法中参数的具体值,每次调用都不一样,是后面传入的,所以在ServiceMethod中是不会有这些信息存在的,则ParameterHandler只会记录注解信息,而不会记录注解对应的值。
    在本文中,我们将一一分析参数级别的注解信息是如何生成ParameterHandler的。

    生成过程是在parseParameter方法中进行的,先上代码:

    private ParameterHandler<?> parseParameter(
            int p, Type parameterType, Annotation[] annotations) {
          ParameterHandler<?> result = null;
          for (Annotation annotation : annotations) {
            ParameterHandler<?> annotationAction = parseParameterAnnotation(
                p, parameterType, annotations, annotation);
    
            if (annotationAction == null) {
              continue;
            }
    
            if (result != null) {
              throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
            }
    
            result = annotationAction;
          }
    
          if (result == null) {
            throw parameterError(p, "No Retrofit annotation found.");
          }
    
          return result;
        }
    

    这个方法中核心就是parseParameterAnnotation方法,剩下的就是一些检查而已,所以继续跟进去:

        private ParameterHandler<?> parseParameterAnnotation(
            int p, Type type, Annotation[] annotations, Annotation annotation) {
          if (annotation instanceof Url) {
                //……
          } else if (annotation instanceof Path) {
                //……
          } else if (annotation instanceof Query) {
                //……
          } else if (annotation instanceof QueryName) {
                //……
          } else if (annotation instanceof QueryMap) {
                //……
          } else if (annotation instanceof Header) {
                //…… 
          } else if (annotation instanceof HeaderMap) {
                //…… 
          } else if (annotation instanceof Field) {
                //……
          } else if (annotation instanceof FieldMap) {
                //……
          } else if (annotation instanceof Part) {
                //…… 
    
          } else if (annotation instanceof PartMap) {
                //……
          } else if (annotation instanceof Body) {
                //……
          }
    
          return null; // Not a Retrofit annotation.
        }
    
    

    这个方法非常长,大致结构就是判断注解类型,然后生成对应的ParameterHandler,我们要做的就是一一分析每个注解的ParameterHandler是如何生成的。

    1. @Url

    if (annotation instanceof Url) {
            if (gotUrl) {
              throw parameterError(p, "Multiple @Url method annotations found.");
            }
            if (gotPath) {
              throw parameterError(p, "@Path parameters may not be used with @Url.");
            }
            if (gotQuery) {
              throw parameterError(p, "A @Url parameter must not come after a @Query");
            }
            if (relativeUrl != null) {
              throw parameterError(p, "@Url cannot be used with @%s URL", httpMethod);
            }
    
            gotUrl = true;
    
            if (type == HttpUrl.class
                || type == String.class
                || type == URI.class
                || (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
              return new ParameterHandler.RelativeUrl();
            } else {
              throw parameterError(p,
                  "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
            }
    
          }
    

    本注解用于标注动态url,处理起来比较简单,除了做一些检查外,就直接生成了一个ParameterHandler.RelativeUrl(),这个handler能做什么呢?我们看看它的apply方法:

     @Override void apply(RequestBuilder builder, @Nullable Object value) {
          checkNotNull(value, "@Url parameter is null.");
          builder.setRelativeUrl(value);
        }
    

    这个handler主要就是吧value设置到RequestBuilder的relativeUrl上。

    2. @Path

    先回顾下@Path注解的作用,用来替换url中的动态占位符:

    @POST("/{a}") Call<String> postRequestBody(@Path("a") Object a);
    

    由于有可能有多个占位符,所以此处注解中加了一个name参数"a",用来知名本参数将用来替换哪个占位符。然后看源码:

    else if (annotation instanceof Path) {
            if (gotQuery) {
              throw parameterError(p, "A @Path parameter must not come after a @Query.");
            }
            if (gotUrl) {
              throw parameterError(p, "@Path parameters may not be used with @Url.");
            }
            if (relativeUrl == null) {
              throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod);
            }
            gotPath = true;
    
            Path path = (Path) annotation;
            String name = path.value();
            validatePathName(p, name);
    
            Converter<?, String> converter = retrofit.stringConverter(type, annotations);
            return new ParameterHandler.Path<>(name, converter, path.encoded());
    
          }
    

    首先是做一些常规检查,然后取出了@Path注解中的name字段,最后加上一个converter一起生成了一个ParameterHandler.Path对象。
    其他的都能理解,但这里为什么需要那个converter对象呢?还是去看下这个handler的apply方法是怎么用到这个converter的:

    @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
          if (value == null) {
            throw new IllegalArgumentException(
                "Path parameter \"" + name + "\" value must not be null.");
          }
          builder.addPathParam(name, valueConverter.convert(value), encoded);
        }
    

    可以看到,将name,value放到RequestBuilder之前要用converter对value进行处理下,因为这个value是我们自己传入到方法中的,可能是string,也可能是其他的对象,怎么处理要由这个converter决定。
    通常情况我们传入的都是一个String,所以默认取得也是ToStringConverter,但如果传入的不是String,这个converter就得我们自己定义了,然后在初始化时传入到Retrofit对象中去。

    3. @Query

    @Query注解是用来将参数加到url后面的,看下处理的源码:

    else if (annotation instanceof Query) {
            Query query = (Query) annotation;
            String name = query.value();
            boolean encoded = query.encoded();
    
            Class<?> rawParameterType = Utils.getRawType(type);
            gotQuery = true;
            //首先判断当前传入的参数是不是Iterable类型,此处以List<String>为例
            if (Iterable.class.isAssignableFrom(rawParameterType)) {
              if (!(type instanceof ParameterizedType)) {
                throw parameterError(p, rawParameterType.getSimpleName()
                    + " must include generic type (e.g., "
                    + rawParameterType.getSimpleName()
                    + "<String>)");
              }
              //获取当前是那种Iterable,此处获取的就是List
              ParameterizedType parameterizedType = (ParameterizedType) type;
              //获取迭代对象的类型,此处获取的是List<String>中的String类型
              Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
              Converter<?, String> converter =
                  retrofit.stringConverter(iterableType, annotations);
              return new ParameterHandler.Query<>(name, converter, encoded).iterable();
            } else if (rawParameterType.isArray()) {
            //此处获取的是数组成员的类型,如果是基本类型会返回对应的包装类型
              Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
              Converter<?, String> converter =
                  retrofit.stringConverter(arrayComponentType, annotations);
              return new ParameterHandler.Query<>(name, converter, encoded).array();
            } else {
            //正常处理单个对象
              Converter<?, String> converter =
                  retrofit.stringConverter(type, annotations);
              return new ParameterHandler.Query<>(name, converter, encoded);
            }
    
          }
    

    对于Query的参数处理主要是分成了三类,一种是可迭代类型,比如list,一种是数组,还有一种是普通对象。
    首先是对普通对象的处理,和@Path注解同理converter对象是必须要的,当然,这里还有一个encoded字段,主要是表明当前传入的参数有没有经过url编码,没有的话系统会进行编码。而这个handler的apply方法我们也研究下:

     @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
          if (value == null) return; // Skip null values.
    
          String queryValue = valueConverter.convert(value);
          if (queryValue == null) return; // Skip converted but null values
    
          builder.addQueryParam(name, queryValue, encoded);
        }
    

    其实就是简单的吧value用converter转化下,然后将相应的信息赛道RequesetBuilder中。
    接下来就是对于参数是数组或Iterable类型的处理,这里不是通过正常的构造方法生成handler对象,而是调用了iterable和array方法,这些都是父类中的,我们看下源码:

    final ParameterHandler<Iterable<T>> iterable() {
        return new ParameterHandler<Iterable<T>>() {
          @Override void apply(RequestBuilder builder, @Nullable Iterable<T> values)
              throws IOException {
            if (values == null) return; // Skip null values.
    
            for (T value : values) {
              ParameterHandler.this.apply(builder, value);
            }
          }
        };
      }
    
      final ParameterHandler<Object> array() {
        return new ParameterHandler<Object>() {
          @Override void apply(RequestBuilder builder, @Nullable Object values) throws IOException {
            if (values == null) return; // Skip null values.
    
            for (int i = 0, size = Array.getLength(values); i < size; i++) {
              //noinspection unchecked
              ParameterHandler.this.apply(builder, (T) Array.get(values, i));
            }
          }
        };
      }
    

    通过这两个方法生成的对象还是对应的对象,但重写了apply方法,会遍历所有值去调用正常handler对象中的apply方法。

    4. @QueryName

    @QueryNamey用来向url中添加没有的value的key,其处理方式和@Query几乎一模一样,只是没有value而已,这里就不介绍了。

    5. @QueryMap

    @QueryMap是用来将一个map形式的参数添加到Url后面,看下处理源码:

    else if (annotation instanceof QueryMap) {
            Class<?> rawParameterType = Utils.getRawType(type);
            if (!Map.class.isAssignableFrom(rawParameterType)) {
              throw parameterError(p, "@QueryMap parameter type must be Map.");
            }
            Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
            if (!(mapType instanceof ParameterizedType)) {
              throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
            }
            ParameterizedType parameterizedType = (ParameterizedType) mapType;
            Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
            if (String.class != keyType) {
              throw parameterError(p, "@QueryMap keys must be of type String: " + keyType);
            }
            Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
            Converter<?, String> valueConverter =
                retrofit.stringConverter(valueType, annotations);
    
            return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap) annotation).encoded());
    
          } 
    

    这里三个if做了三个检查,分别要求传入的参数必须是map类型,map的值类型必须是基本类型,map的key必须是string类型。
    最后利用传入map的value的类型和一个converter生成了一个handler,到这一部我们已经可以猜测这个handler必然可以遍历这个map,然后将键值对放到url中去,可以看下apply的代码:

        @Override void apply(RequestBuilder builder, @Nullable Map<String, T> value)
            throws IOException {
          if (value == null) {
            throw new IllegalArgumentException("Query map was null.");
          }
    
          for (Map.Entry<String, T> entry : value.entrySet()) {
            String entryKey = entry.getKey();
            if (entryKey == null) {
              throw new IllegalArgumentException("Query map contained null key.");
            }
            T entryValue = entry.getValue();
            if (entryValue == null) {
              throw new IllegalArgumentException(
                  "Query map contained null value for key '" + entryKey + "'.");
            }
    
            String convertedEntryValue = valueConverter.convert(entryValue);
            if (convertedEntryValue == null) {
              throw new IllegalArgumentException("Query map value '"
                  + entryValue
                  + "' converted to null by "
                  + valueConverter.getClass().getName()
                  + " for key '"
                  + entryKey
                  + "'.");
            }
    
            builder.addQueryParam(entryKey, convertedEntryValue, encoded);
          }
        }
    
    

    这里证实了之前的猜测,整个apply方法就是遍历map,然后将一个个的value的值经过converter转换后最终传到RequestBuilder中去。

    6. @Header

    @Header 注解用来将参数放到http请求的header中去,此注解需要设置name字段,看源码:

    else if (annotation instanceof Header) {
            Header header = (Header) annotation;
            String name = header.value();
    
            Class<?> rawParameterType = Utils.getRawType(type);
            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);
              Converter<?, String> converter =
                  retrofit.stringConverter(iterableType, annotations);
              return new ParameterHandler.Header<>(name, converter).iterable();
            } else if (rawParameterType.isArray()) {
              Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
              Converter<?, String> converter =
                  retrofit.stringConverter(arrayComponentType, annotations);
              return new ParameterHandler.Header<>(name, converter).array();
            } else {
              Converter<?, String> converter =
                  retrofit.stringConverter(type, annotations);
              return new ParameterHandler.Header<>(name, converter);
            }
    
          } 
    

    和@Query注解类似,这里也将传入的参数类型分为了三类,Iterable,Array以及普通的单个的类型,对应生成了不同的handler,核心handler还是ParameterHandler.Header中的apply方法:

        @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
          if (value == null) return; // Skip null values.
    
          String headerValue = valueConverter.convert(value);
          if (headerValue == null) return; // Skip converted but null values.
    
          builder.addHeader(name, headerValue);
        }
    
    

    这里就只是简单的将注解修饰的参数用converter转换后塞给RequestBuilder。那么我们可以知道,利用ParameterHandler.iterable()方法以及array方法生成的也是这种handler,只不过apply方法中会遍历所有的数据,对每一个调用一次上面的apply方法罢了。

    7. @HeaderMap

    @HeaderMap注解是将一个map中的数据添加到http请求的Header中,整个处理过程和@Header一模一样,只是对应的handler的apply方法会遍历一遍这个map,然后一一放到RequestBuilder中去,这里就不贴代码了。

    8. @Field,@FieldMap

    经过上面7个注解的分析,对于这两个注解的处理其实大致一样,都是生成对应的handler,然后大家理解handler中的apply方法就行了,这里留给大家自己分析。

    9. @Part 重点

    写到这里,即使我偷懒省掉了几个注解的分析,留给了大家,但篇幅还是有点长了,尤其是@Part注解涉及到文件上传,和之前的截然不同,要理解源码得先弄懂http协议关于文件上传的部分,所以我会另起一篇文章,这次就到这里。

    相关文章

      网友评论

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

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