multipart/form-data
在最初的http协议中,没有定义上传文件的Method,为了实现这个功能,http协议组改造了post请求,添加了一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=${bound},其中${bound}是定义的分隔符,用于分割各项内容(文件,key-value对),OkHttp会自动生成boundary,无需手动编写规则
Retrofit其实是个网络代理框架,负责封装请求,然后把请求分发给http协议具体实现者httpclient,retrofit默认的httpclient是okhttp
Retrofit会根据注解封装网络请求,待httpclient请求完成后,把原始response内容通过转化器(converter)转化成我们需要的对象(object),在SsResponse中通过泛型注入
代码实现
本文分别用Retrofit和okhttp实现multipart/form-data的多文件上传:
- Response返回类型定义如下,body为返回的具体内容,格式为json字符
public class Response {
public final Map<String, String> headers;
public final String body;
public final int code;
public final String msg;
public Response(Map<String, String> headers, String body, int code, String msg) {
this.headers = headers;
this.body = body;
this.code = code;
this.msg = msg;
}
}
- Retrofit实现
- Retrofit构建
Retrofit retrofit = new Retrofit.Builder()
.client(client) //设置OKHttpClient
.baseUrl(BASE_URL) //设置baseUrl, baseUrl必须后缀"/"
.addConverterFactory(GsonConverterFactory.create()) //添加Gson转换器
.build();
- 接口定义,有两种定义方式:
- Retrofit会判断@Body的参数类型,如果参数类型为MultipartBody,则Retrofit不做包装处理,直接丢给okhttp3处理。因为MultipartBody是继承RequestBody,因此Retrofit不会自动包装这个对象。
- Retrofit会判断@Part的参数类型,如果参数类型为MultipartBody.Part,则Retrofit会把RequestBody封装成MultipartBody,再把Part添加到MultipartBody。
public interface RetrofitNetApi {
/**
* 通过 MultipartBody和@Body作为参数来上传
* @param body MultipartBody包含多个Part
* @return 状态信息
*/
@POST
Call<String> uploadFile(@Url String url, @Body MultipartBody body);
/**
* 通过 List<MultipartBody.Part> 传入多个part实现多文件上传
* @param parts
* @return 状态信息
*/
@Multipart
@POST
Call<String> uploadFile(@Url String url, @Part List<MultipartBody.Part> parts);
}
- 接口实现
// 方式1
/**
* @param url 上传文件地址
* @param filePaths 上传文件绝对路径,支持多文件上传
* @return Response
* @throws Exception
*/
public Response uploadFile(String url, List<String> filePaths) throws Exception {
RetrofitNetApi netApi = retrofit.create(RetrofitNetApi.class);
MultipartBody.Builder builder = new MultipartBody.Builder();
for (String filePath : filePaths) {
File file = new File(filePath);
builder.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
}
MultipartBody body = builder.build();
SsResponse<String> ret = netApi.uploadFile(url, body).execute();
return new Response(convertHeaders(ret.headers()), ret.body(), ret.code(), ret.raw().getReason());
}
// 方式2
/**
* @param url 上传文件地址
* @param filePaths 上传文件绝对路径,支持多文件上传
* @return Response
* @throws Exception
*/
public Response uploadFile(String url, List<String> filePaths) throws Exception {
RetrofitNetApi netApi = retrofit.create(RetrofitNetApi.class);
List<MultipartBody.Part> parts = new ArrayList<>(filePaths.size());
for (String filePath : filePaths) {
File file = new File(filePath);
MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
parts.add(part);
}
SsResponse<String> ret = netApi.uploadFile(url, parts).execute();
return new Response(convertHeaders(ret.headers()), ret.body(), ret.code(), ret.raw().getReason());
}
// convertHeaders实现
private Map<String, String> convertHeaders(List<Header> headers) {
HashMap<String, String> map = new HashMap<>();
if (!ListUtils.isEmpty(headers)) {
for (Header header : headers) {
map.put(header.getName(), header.getValue());
}
}
return map;
}
- okhttp3实现
- OkHttpClient构建
OkHttpClient mApiClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS).build();
- 具体实现
/**
* @param url 上传文件地址
* @param filePaths 上传文件绝对路径,支持多文件上传
* @return Response
* @throws Exception
*/
public Response uploadFile(@NonNull String url, @NonNull List<String> filePaths) throws Exception {
MultipartBody.Builder builder = new MultipartBody.Builder();
for (String filePath : filePaths) {
File file = new File(filePath);
builder.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
}
RequestBody body = builder.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
okhttp3.Response res = mApiClient.newCall(request).execute();
return new Response(headers(res.headers()),
res.code() == 200 ? res.body().string() : null, res.code(), res.message());
}
// res.headers转换为Map
private Map<String, String> headers(Headers headers) {
if (headers == null) {
return null;
}
Map<String, String> map = new HashMap<>();
for (String name : headers.names()) {
map.put(name, headers.get(name));
}
return map;
}