A 使用@Part注解
@Multipart
@POST("update/img")
Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part MultipartBody.Part img);
构造请求数据时采用如下方式:
RequestBody requestFile = RequestBody.create(img, MediaType.parse("image/jpeg"));
// MultipartBody.Part is used to send also the actual filename
MultipartBody.Part img = MultipartBody.Part.createFormData("img", img.getName(), requestFile);
RequestBody param1 = RequestBody.create("param1", null);
Call<ResponseBody> call = service.postImg(param1, body);
如果是多文件:
@Multipart
@POST("update/img")
Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part MultipartBody.Part[] imgs);
// Call<ResponseBody> postImg(@Part("param1") RequestBody param1, @Part List<MultipartBody.Part> imgs);
构造请求数据时传数组/List即可。
B 使用@Body注解
@POST("update/img")
Call<ResponseBody> postImg(@Body RequestBody requestBody);
注意:此处不能添加@Multipart或@FormUrlEncoded注解,不然会报错。
构造请求数据时采用如下方式:
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("param1","param1")
.addFormDataPart("img", img.getName(), RequestBody.create(img, MediaType.parse("image/jpeg")));
postImg(builder.build());
如果是多文件,接口注解不变,在添加文件时在继续执行addFormDataPart即可。
实际应用中,两种方式效果完全相同,那么,这两种方式在实现上有什么区别吗?
源码分析
Retrofit解析注解的步骤如下:
- 解析方法注解;
- 解析参数和参数注解。
RequestFactory类中解析方法注解部分代码如下:
private void parseMethodAnnotation(Annotation annotation) {
...
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
...
}
观察代码,发现如果方法注解中含有Multipart,则另isMultipart为true。
下面我们来观察RequestFactory类中解析参数注解的部分代码:
-
对
@Part的解析首先会根据
isMultipart判断方法是否含有Multipart注解,没有注解就报错:if (!isMultipart) { throw parameterError( method, p, "@Part parameters can only be used with multipart encoding."); }然后得到
@Part的值,对于@Part的值有以下几种可能。-
@Part值为空,此时认为参数类型必须为Multipart.body、Multipart.body数组或Iterable类型,如果不为以上三者,则报异常:throw parameterError( method, p, "@Part annotation must supply a name or use MultipartBody.Part parameter type."); -
@Part值为空,参数类型为Multipart.body:if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) { return ParameterHandler.RawPart.INSTANCE; } -
@Part值为空,参数类型为Iterable:if (Iterable.class.isAssignableFrom(rawParameterType)) { if (!(type instanceof ParameterizedType)) { throw parameterError( method, 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( method, p, "@Part annotation must supply a name or use MultipartBody.Part parameter type."); } return ParameterHandler.RawPart.INSTANCE.iterable(); }这里首先判断泛型是否确定类型,然后判断泛型类型是否为
MultipartBody.Part。 -
@Part值为空,参数类型为Array:if (rawParameterType.isArray()) { Class<?> arrayComponentType = rawParameterType.getComponentType(); if (!MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) { throw parameterError( method, p, "@Part annotation must supply a name or use MultipartBody.Part parameter type."); } return ParameterHandler.RawPart.INSTANCE.array(); } -
@Part值不为空,参数类型为MultipartBody.Part,则报错if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) { throw parameterError( method, p, "@Part parameters using the MultipartBody.Part must not " + "include a part name in the annotation."); } -
@Part值不为空,参数类型不为MultipartBody.Part。Converter<?, RequestBody> converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations); return new ParameterHandler.Part<>(method, p, headers, converter);
-
-
对
@PartMap的解析同
@Part,首先会根据isMultipart判断方法是否含有Multipart注解,没有注解就报错。接着获取参数类型。
-
判断参数类型是否为
Map,不是则报错。if (!Map.class.isAssignableFrom(rawParameterType)) { throw parameterError(method, p, "@PartMap parameter type must be Map."); } -
判断
Map是否指定泛型类型,未指定则报错。if (!(mapType instanceof ParameterizedType)) { throw parameterError( method, p, "Map must include generic types (e.g., Map<String, String>)"); } -
判断
Map键类型是否为String,不是则报错。if (String.class != keyType) { throw parameterError(method, p, "@PartMap keys must be of type String: " + keyType); } -
判断
Map值类型是否为Multipart.body,是则报错。if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(valueType))) { throw parameterError( method, p, "@PartMap values cannot be MultipartBody.Part. " + "Use @Part List<Part> or a different value type instead."); }
-
-
对
@Body的解析首先会判断是否方法注解是否包含
@FormUrlEncoded和@Multipart注解,有则报错。if (isFormEncoded || isMultipart) { throw parameterError( method, p, "@Body parameters cannot be used with form or multi-part encoding."); }然后调用转换器,尝试将数据转换为
RequestBody。Converter<?, RequestBody> converter; try { converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations); } catch (RuntimeException e) { // Wide exception range because factories are user code. throw parameterError(method, e, p, "Unable to create @Body converter for %s", type); } gotBody = true; return new ParameterHandler.Body<>(method, p, converter);
解析成功之后,会将数据传至ParameterHandler类进一步解析,下面让我们看看该类做了什么。
-
对于
@Part注解,涉及到ParameterHandler.Part和ParameterHandler.RawPart内部类,其中当@Part值不为空时,使用Part类解析,否则使用RawPart解析。 -
对于
@PartMap注解,使用ParameterHandler.Part进行解析。 -
对于
@Body注解,使用ParameterHandler.Body进行解析。
他们都含有一个共同的方法:
Part
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 Utils.parameterError(method, p, "Unable to convert " + value + " to RequestBody", e);
}
builder.addPart(headers, body);
}
PartMap
void apply(RequestBuilder builder, @Nullable Map<String, T> value) throws IOException {
if (value == null) {
throw Utils.parameterError(method, p, "Part map was null.");
}
for (Map.Entry<String, T> entry : value.entrySet()) {
String entryKey = entry.getKey();
if (entryKey == null) {
throw Utils.parameterError(method, p, "Part map contained null key.");
}
T entryValue = entry.getValue();
if (entryValue == null) {
throw Utils.parameterError(
method, p, "Part map contained null value for key '" + entryKey + "'.");
}
okhttp3.Headers headers =
okhttp3.Headers.of(
"Content-Disposition",
"form-data; name=\"" + entryKey + "\"",
"Content-Transfer-Encoding",
transferEncoding);
builder.addPart(headers, valueConverter.convert(entryValue));
}
}
RawPart
void apply(RequestBuilder builder, @Nullable MultipartBody.Part value) {
if (value != null) { // Skip null values.
builder.addPart(value);
}
}
Body
void apply(RequestBuilder builder, @Nullable T value) {
if (value == null) {
throw Utils.parameterError(method, p, "Body parameter value must not be null.");
}
RequestBody body;
try {
body = converter.convert(value);
} catch (IOException e) {
throw Utils.parameterError(method, e, p, "Unable to convert " + value + " to RequestBody");
}
builder.setBody(body);
}
}
我们只关系builder.addPart和builder.setBody方法。
-
查看
addPart方法源码,该方法在RequestBuilder类中:private @Nullable MultipartBody.Builder multipartBuilder; void addPart(Headers headers, RequestBody body) { multipartBuilder.addPart(headers, body); } void addPart(MultipartBody.Part part) { multipartBuilder.addPart(part); }对于
RawPart类型,将采用addPart(MultipartBody.Part part)方法,对于Part类型,将采用addPart(Headers headers, RequestBody body)方法。然后,我们查看MultipartBody.Builder中的addPart方法为:fun addPart(part: Part) = apply { parts += part } -
查看
setBody源码private @Nullable RequestBody body; void setBody(RequestBody body) { this.body = body; } -
获得
Request.Builder方法Request.Builder get() { ... RequestBody body = this.body; if (body == null) { // Try to pull from one of the builders. if (formBuilder != null) { body = formBuilder.build(); } else if (multipartBuilder != null) { body = multipartBuilder.build(); } else if (hasBody) { // Body is absent, make an empty body. body = RequestBody.create(null, new byte[0]); } } ... }在该方法中,首先判断
body是否为空,如果为空则调用formBuilder或multipartBuilder创建一个body。 -
最后,我们再看一下
MultipartBody.Builder中的addFormDataPart方法fun addFormDataPart(name: String, filename: String?, body: RequestBody) = apply { addPart(Part.createFormData(name, filename, body)) } fun addFormDataPart(name: String, value: String) = apply { addPart(Part.createFormData(name, value)) }可以看出最终还是调用了
addPart方法。fun addPart(part: Part) = apply { parts += part }
分析到这里,我们可以明白,原来两种上传文件方式只是表达方式不同,实际上都会调用MultipartBody.Builder的build方法创建RequetBody,只不过第一种方式创建是交给了Retrofit中的RequestBuilder类,而第二种是由我们自己调用MultipartBody.Builder的build方法创建而已。