之前在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协议关于文件上传的部分,所以我会另起一篇文章,这次就到这里。