Android 在单个请求中上传多个文件
Android Upload Multiple Files In A Single Request
我正在 post 在 multipart/form-data 请求中向服务器发送内容。
我正在 posting 的数据包含多个参数,包括文件数组参数 (files[]).
使用 postman 我将参数和文件设置为:
first-parameter=text content
second-parameter=text content
files[0]={first selected file}
files[1]={second selected file}
在postman上提交这个请求总是成功的,文件上传成功。
当我在 postman 上生成代码片段时,结果如下:
POST /api/*******/new HTTP/1.1
Host: ***.***.***.*** ip-address
Authorization: {authorization header}
Cache-Control: no-cache
Postman-Token: d15f13f1-4a65-81d1-bf91-f5accd1b1dd0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="first-parameter"
first-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="second-parameter"
second-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[0]"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[1]"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW--
在 Android 上,我将 Retrofit 与 HttpLoggingInterceptor 库一起使用,并在 post 请求时使用 @PartMap 注释:
//API定义:
interface ApiDefinitions {
@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
@PartMap Map<String, RequestBody> params);
}
//准备发送请求码:
public void postContent() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient
.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
ApiDefinitions api = retrofit.create(ApiDefinitions.class);
MediaType mediaType = MediaType.parse("multipart/form-data");
Map<String, RequestBody> params = new HashMap<>();
params.put("first-parameter", MultipartBody.create(mediaType, "first-parameter-value"));
params.put("second-parameter", MultipartBody.create(mediaType, "second-parameter-value"));
params.put("files[0]", MultipartBody.create(mediaType, new File("First file path")));
params.put("files[1]", MultipartBody.create(mediaType, new File("Second file path")));
Call<ApiResponse> call = api.submitNew("Auth Token", params);
enqueue.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
}
});
}
提交此请求没有错误,http 响应为 200,但是文件没有上传!!!
因为我使用的是 HttpLoggingInterceptor,所以我尝试将改造日志与 post 人工代码片段进行比较,这是改造日志:
D/OkHttp: --> POST http://{api_address}/api/*****/new http/1.1
D/OkHttp: Content-Type: multipart/form-data; boundary=64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Length: 119325
D/OkHttp: Authorization: {Authorization-Token}
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="first-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: first-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="second-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: second-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[0]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: �PNG
D/OkHttp:
D/OkHttp: ������IHDR��������������������r�B�������sBIT��O����� ��IDATx�4�˳mYv��}c�9���so�|W���R��2z�T%�8B�X�
D/OkHttp: &�D$��B��r�D��w�!@��������{��H���[�!,�@ �4h�P����>�A��&� ����B�[�,�fD�Mi�d�5)���5�{��-�MQt��ٗ&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[1]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: �PNG
D/OkHttp:
D/OkHttp: ������IHDR��������������������r�B�������sBIT��O����� ��IDATx�4�˳mYv��}c�9���so�|W���R��2z�T%�8B�X�
D/OkHttp: &�D$��B��r�D��w�!@��������{��H���[�!,�@ �4h�P����>�A��&� ����B�[�,�fD�Mi�d�5)���5�{��-�MQt��ٗ&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298--
D/OkHttp: --> END POST (119325-byte body)
D/OkHttp: <-- 200 OK http://{api_address}/api/*****/new (3409ms)
我试图通过比较来自 postman 和 retrofit 的两个日志来找出我的请求中的错误,但我找不到它!
随机边界字符串不同这是正常的,postman 使用 webkit,而 retrofit 则没有!我认为这根本不是问题。
我尝试使用 @Part List<MultipartBody.Part
而不是 @PartMap Map<String, RequestBody>
作为建议的文件 here,但它也不起作用。
上传的文件怎么办?
我认为您应该更改文件的 MediaType。并且您还检查 link 的 https://futurestud.io/tutorials/retrofit-2-how-to-upload-files-to-server 和使用 @Part MultipartBody.Part
发送文件
MediaType mediaTypeForText = MediaType.parse("text/plain");
MediaType mediaTypeForImage = MediaType.parse("image/*");
Map<String, RequestBody> params = new HashMap<>();
params.put("first-parameter", MultipartBody.create(mediaTypeForText, "first-parameter-value"));
params.put("second-parameter", MultipartBody.create(mediaTypeForText, "second-parameter-value"));
params.put("files[0]", MultipartBody.create(mediaTypeForImage, new File(fileUri.getPath()));
params.put("files[1]", MultipartBody.create(mediaTypeForImage, new File(fileUri.getPath()));
首先,我做错了一件事,那就是在 ApiDefinitions
界面中使用 @PartMap Map<String, RequestBody>
。
如果您正在执行带有多个参数和多个文件的 post 请求,请务必确保在定义 api 方法时对非文件参数使用 RequestBody
,并使用 MultipartBody.Part
作为文件参数。
在我的例子中,我需要发送一组文件,所以对我有用的参数是 MultipartBody.Part[]
。
这是新的 api 定义:
@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
@Part("first-parameter") RequestBody firstParameter,
@Part("first-parameter") RequestBody secondParameter,
@Part MultipartBody.Part[] files);
我犯的第二个错误是没有注意到这一点:
PostMan Log: Content-Disposition: form-data; name="files[0]"; filename=""
Retrofit Log: Content-Disposition: form-data; name="files[0]"
文件名未包含在多部分请求中,这显然是 known issue when uploading files to a php service !
我没有制作 api,也没有任何 php 背景,所以请不要评判我。
据我所知,我已成功将文件发送到服务 api,但 api 不知道如何保存这些文件!
所以发送请求时:
List<Uri> files; //These are the uris for the files to be uploaded
MediaType mediaType = MediaType.parse("");//Based on the Postman logs,it's not specifying Content-Type, this is why I've made this empty content/mediaType
MultipartBody.Part[] fileParts = new MultipartBody.Part[files.size()];
for (int i = 0; i < files.size(); i++) {
File file = new File(files.get(i).getPath());
RequestBody fileBody = RequestBody.create(mediaType, file);
//Setting the file name as an empty string here causes the same issue, which is sending the request successfully without saving the files in the backend, so don't neglect the file name parameter.
fileParts[i] = MultipartBody.Part.createFormData(String.format(Locale.ENGLISH, "files[%d]", i), file.getName(), fileBody);
}
Call<ApiResponse> call = api.submitNew("Auth Token", MultipartBody.create(mediaType, "first_parameter_values"), MultipartBody.create(mediaType, "second_parameter_values"), fileParts);
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
}
});
我正在 post 在 multipart/form-data 请求中向服务器发送内容。 我正在 posting 的数据包含多个参数,包括文件数组参数 (files[]).
使用 postman 我将参数和文件设置为:
first-parameter=text content
second-parameter=text content
files[0]={first selected file}
files[1]={second selected file}
在postman上提交这个请求总是成功的,文件上传成功。
当我在 postman 上生成代码片段时,结果如下:
POST /api/*******/new HTTP/1.1
Host: ***.***.***.*** ip-address
Authorization: {authorization header}
Cache-Control: no-cache
Postman-Token: d15f13f1-4a65-81d1-bf91-f5accd1b1dd0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="first-parameter"
first-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="second-parameter"
second-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[0]"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[1]"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW--
在 Android 上,我将 Retrofit 与 HttpLoggingInterceptor 库一起使用,并在 post 请求时使用 @PartMap 注释:
//API定义:
interface ApiDefinitions {
@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
@PartMap Map<String, RequestBody> params);
}
//准备发送请求码:
public void postContent() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient
.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
ApiDefinitions api = retrofit.create(ApiDefinitions.class);
MediaType mediaType = MediaType.parse("multipart/form-data");
Map<String, RequestBody> params = new HashMap<>();
params.put("first-parameter", MultipartBody.create(mediaType, "first-parameter-value"));
params.put("second-parameter", MultipartBody.create(mediaType, "second-parameter-value"));
params.put("files[0]", MultipartBody.create(mediaType, new File("First file path")));
params.put("files[1]", MultipartBody.create(mediaType, new File("Second file path")));
Call<ApiResponse> call = api.submitNew("Auth Token", params);
enqueue.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
}
});
}
提交此请求没有错误,http 响应为 200,但是文件没有上传!!!
因为我使用的是 HttpLoggingInterceptor,所以我尝试将改造日志与 post 人工代码片段进行比较,这是改造日志:
D/OkHttp: --> POST http://{api_address}/api/*****/new http/1.1
D/OkHttp: Content-Type: multipart/form-data; boundary=64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Length: 119325
D/OkHttp: Authorization: {Authorization-Token}
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="first-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: first-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="second-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: second-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[0]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: �PNG
D/OkHttp:
D/OkHttp: ������IHDR��������������������r�B�������sBIT��O����� ��IDATx�4�˳mYv��}c�9���so�|W���R��2z�T%�8B�X�
D/OkHttp: &�D$��B��r�D��w�!@��������{��H���[�!,�@ �4h�P����>�A��&� ����B�[�,�fD�Mi�d�5)���5�{��-�MQt��ٗ&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[1]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: �PNG
D/OkHttp:
D/OkHttp: ������IHDR��������������������r�B�������sBIT��O����� ��IDATx�4�˳mYv��}c�9���so�|W���R��2z�T%�8B�X�
D/OkHttp: &�D$��B��r�D��w�!@��������{��H���[�!,�@ �4h�P����>�A��&� ����B�[�,�fD�Mi�d�5)���5�{��-�MQt��ٗ&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298--
D/OkHttp: --> END POST (119325-byte body)
D/OkHttp: <-- 200 OK http://{api_address}/api/*****/new (3409ms)
我试图通过比较来自 postman 和 retrofit 的两个日志来找出我的请求中的错误,但我找不到它! 随机边界字符串不同这是正常的,postman 使用 webkit,而 retrofit 则没有!我认为这根本不是问题。
我尝试使用 @Part List<MultipartBody.Part
而不是 @PartMap Map<String, RequestBody>
作为建议的文件 here,但它也不起作用。
上传的文件怎么办?
我认为您应该更改文件的 MediaType。并且您还检查 link 的 https://futurestud.io/tutorials/retrofit-2-how-to-upload-files-to-server 和使用 @Part MultipartBody.Part
发送文件MediaType mediaTypeForText = MediaType.parse("text/plain");
MediaType mediaTypeForImage = MediaType.parse("image/*");
Map<String, RequestBody> params = new HashMap<>();
params.put("first-parameter", MultipartBody.create(mediaTypeForText, "first-parameter-value"));
params.put("second-parameter", MultipartBody.create(mediaTypeForText, "second-parameter-value"));
params.put("files[0]", MultipartBody.create(mediaTypeForImage, new File(fileUri.getPath()));
params.put("files[1]", MultipartBody.create(mediaTypeForImage, new File(fileUri.getPath()));
首先,我做错了一件事,那就是在 ApiDefinitions
界面中使用 @PartMap Map<String, RequestBody>
。
如果您正在执行带有多个参数和多个文件的 post 请求,请务必确保在定义 api 方法时对非文件参数使用 RequestBody
,并使用 MultipartBody.Part
作为文件参数。
在我的例子中,我需要发送一组文件,所以对我有用的参数是 MultipartBody.Part[]
。
这是新的 api 定义:
@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
@Part("first-parameter") RequestBody firstParameter,
@Part("first-parameter") RequestBody secondParameter,
@Part MultipartBody.Part[] files);
我犯的第二个错误是没有注意到这一点:
PostMan Log: Content-Disposition: form-data; name="files[0]"; filename=""
Retrofit Log: Content-Disposition: form-data; name="files[0]"
文件名未包含在多部分请求中,这显然是 known issue when uploading files to a php service !
我没有制作 api,也没有任何 php 背景,所以请不要评判我。 据我所知,我已成功将文件发送到服务 api,但 api 不知道如何保存这些文件!
所以发送请求时:
List<Uri> files; //These are the uris for the files to be uploaded
MediaType mediaType = MediaType.parse("");//Based on the Postman logs,it's not specifying Content-Type, this is why I've made this empty content/mediaType
MultipartBody.Part[] fileParts = new MultipartBody.Part[files.size()];
for (int i = 0; i < files.size(); i++) {
File file = new File(files.get(i).getPath());
RequestBody fileBody = RequestBody.create(mediaType, file);
//Setting the file name as an empty string here causes the same issue, which is sending the request successfully without saving the files in the backend, so don't neglect the file name parameter.
fileParts[i] = MultipartBody.Part.createFormData(String.format(Locale.ENGLISH, "files[%d]", i), file.getName(), fileBody);
}
Call<ApiResponse> call = api.submitNew("Auth Token", MultipartBody.create(mediaType, "first_parameter_values"), MultipartBody.create(mediaType, "second_parameter_values"), fileParts);
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
}
});