通过前几篇文章的分析,对于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源码,无法自定义注解。
网友评论