文件上传在应用中是比较常用的的一种功能,比如用户的意见反馈功能。用户可以上传图片,音视频等文件,方便用户反馈问题的情况。
下来我将和大家一起梳理下需要掌握的知识,少走弯路一步到位,实现文件上传功能。
第一步:了解请求头
需要先大致了解一下上传信息需要的请求结构。当我们掌握了请求头的结构,就可以根据自己项目情况选择sdk或者自己实现HTTP请求。
使用HTTP进行文件上传需要用到POST请求。POST请求包含请求Head 和请求Body。
上传文件需要使用 Content-type为multipart/form-data 的格式,发送请求信息。这种格式的数据会有一个边界线用于分割不同的字段。如下文中的:boundary=86489a0c-3d1f-4351-8f49-58315690babe
请求Head: 一般会携带客户端的一些信息包括签名,token等。服务端用这些信息来校验是否合法。
请求Body: 会携带文件的信息和相关的参数便于服务端进行区分。
消息头可以携带签名信息等字段,用于服务端校验。请求头中的 head1 和head2,代表token,uuid,key等信息用于校验。
POST https://*****/upload
Content-Type: multipart/form-data; boundary=86489a0c-3d1f-4351-8f49-58315690babe
Content-Length: 83014
head1: param
head2: param
消息主体以分界线开始,紧接着就是内容描述信息,可以携带多个字段进行资源数据的描述。最后是字段具体的内容(文本或二进制)。
请求体中的param1 和param2 代表携带文件的相关参数信息,比如文件扩展名等,方便服务端进行区分
--86489a0c-3d1f-4351-8f49-58315690babe
Content-Disposition: form-data; name="param1"
Content-Length: 8
param1
--86489a0c-3d1f-4351-8f49-58315690babe
Content-Disposition: form-data; name="param2"
Content-Length: 3
param2
--86489a0c-3d1f-4351-8f49-58315690babe
Content-Disposition: form-data; name="file"; filename="pic.png"
Content-Type: application/otcet-stream
Content-Length: 82560
*****
--86489a0c-3d1f-4351-8f49-58315690babe
消息主体以boundary结束。
第二步:实践
我们将用目前比较流行的框架实现上面的结构。为了方便了解,我会一一对应的结构,实现上面的Post接口,实际应用中可以充分利用框架的进行接口优化。
方案1:直接使用OKHttp 上传文件
File file= new File(file);
// 构建okhttpclient,使用HttpLoggingInterceptor 打印调试日志
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.SECONDS);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
clientBuilder.addInterceptor(loggingInterceptor);
OkHttpClient client = clientBuilder.build();
// 构造请求头
Headers.Builder headerBuilder = new Headers.Builder();
headerBuilder.add("head1","***");
headerBuilder.add("head2","***");
// 构造请求体
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
builder.addPart(
Headers.of("Content-Disposition", "form-data; name=\"param1\""),
RequestBody.create(null, "param1"));
builder.addPart(
Headers.of("Content-Disposition", "form-data; name=\"param2\""),
RequestBody.create(null, "param2"));
MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
RequestBody filebody = MultipartBody.create(MEDIA_TYPE_PNG, file);
builder.addFormDataPart("file",file.getName(),filebody);
MultipartBody body = builder.build();
// 构造请求
Request request = new Request.Builder()
.headers(headerBuilder.build())
.url("***********")
.post(body)
.build();
// 发起请求
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
方案2:利用Retrofit 上传文件
实例化Retroift 对象
// 构造okhttp
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.SECONDS);
// 打印网络请求日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
clientBuilder.addInterceptor(loggingInterceptor);
mOkHttpClient = httpClientBuilder.build();
// 构造Retrofit
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl(BASE_URL)
// 增加 Gson转换
.addConverterFactory(GsonConverterFactory.create())
.build();
定义Retrofit接口
@Multipart
@POST("***")
Call uploadFile(@Header("head1") String head1,
@Header("head2") String head2,
@Part MultipartBody.Part param1,
@Part MultipartBody.Part param2,
@Part MultipartBody.Part multipartBody);
调用Retrofit接口
mApiService = NetworkService.getInstance().getService(Api.class);
//请求体
MultipartBody.Part param1 = MultipartBody.Part.createFormData("param1", "param1");
MultipartBody.Part param2 = MultipartBody.Part.createFormData("param2", "param2");
File file = new File("path");
RequestBody body = RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part filebody = MultipartBody.Part.createFormData("file",file.getName(),body);
call = mApiService.uploadFile("head1","head2",param1,param2,filebody);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call,
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
调试工具
对于在家没有后端支持的同学,可以使用 Http File Server 在局域网类部署服务端,然后进行接口测试。
Http File Server 工具可以在百度上搜到,使用的中可以关闭用户校验功能。