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
方法创建而已。