使用 Retrofit 2.0 发布多部分表单数据,包括图像
Posted
技术标签:
【中文标题】使用 Retrofit 2.0 发布多部分表单数据,包括图像【英文标题】:POST Multipart Form Data using Retrofit 2.0 including image 【发布时间】:2016-04-06 09:37:33 【问题描述】:我正在尝试使用 Retrofit 2.0
向服务器发送 HTTP POSTMediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
imageBitmap.compress(Bitmap.CompressFormat.JPEG,90,byteArrayOutputStream);
profilePictureByte = byteArrayOutputStream.toByteArray();
Call<APIResults> call = ServiceAPI.updateProfile(
RequestBody.create(MEDIA_TYPE_TEXT, emailString),
RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));
call.enqueue();
服务器返回一个错误,指出文件无效。
这很奇怪,因为我尝试在 ios 上上传相同格式的相同文件(使用其他库),但上传成功。
我想知道使用 Retrofit 2.0 上传图片的正确方法是什么?
我应该在上传之前先将其保存到磁盘吗?
P.S.:我已经对不包含图像的其他 Multipart 请求进行了改造,并且他们成功完成了。问题是当我试图在正文中包含一个字节时。
【问题讨论】:
github.com/square/retrofit/issues/1217 square.github.io/retrofit/2.x/retrofit/retrofit2/http/… 【参考方案1】:使用Retrofit 2上传文件名的正确方法,无需任何hack:
定义API接口:
@Multipart
@POST("uploadAttachment")
Call<MyResponse> uploadAttachment(@Part MultipartBody.Part filePart);
// You can add other parameters too
像这样上传文件:
File file = // initialize file here
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));
Call<MyResponse> call = api.uploadAttachment(filePart);
这里只演示文件上传,你也可以用@Part
注解在同一方法中添加其他参数。
【讨论】:
我们如何使用 MultipartBody.Part 发送多个文件? 您可以在同一个 API 中使用多个MultipartBody.Part
参数。
我需要发送以“image[]”为键的图像集合。我试过@Part("images[]") List<MultipartBody.Part> images
但它给出的错误是@Part parameters using the MultipartBody.Part must not include a part name
如何将密钥添加到多部分
@jimmy0251 可以帮助我解决这个问题 @Part("channel[comment][image]")
。我必须在这个channel[comment][image]
键上上传图片。我该怎么做。【参考方案2】:
我正在强调 1.9 和 2.0 中的解决方案,因为它对某些人有用
在1.9
,我认为更好的解决方案是将文件保存到磁盘并将其用作类型文件,例如:
RetroFit 1.9
(我不知道你的服务器端实现)有一个类似于这个的API接口方法
@POST("/en/Api/Results/UploadFile")
void UploadFile(@Part("file") TypedFile file,
@Part("folder") String folder,
Callback<Response> callback);
并像使用它
TypedFile file = new TypedFile("multipart/form-data",
new File(path));
对于 RetroFit 2 使用以下方法
RetroFit 2.0(这是 RetroFit 2 中 issue 的解决方法,现已修复,正确方法请参阅 jimmy0251's answer)
API 接口:
public interface ApiInterface
@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser(@Header("Authorization") String authorization,
@Part("file\"; filename=\"pp.png\" ") RequestBody file,
@Part("FirstName") RequestBody fname,
@Part("Id") RequestBody id);
像这样使用它:
File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse("image/*"),
file);
RequestBody name = RequestBody.create(MediaType.parse("text/plain"),
firstNameField.getText()
.toString());
RequestBody id = RequestBody.create(MediaType.parse("text/plain"),
AZUtils.getUserId(this));
Call<User> call = client.editUser(AZUtils.getToken(this),
fbody,
name,
id);
call.enqueue(new Callback<User>()
@Override
public void onResponse(retrofit.Response<User> response,
Retrofit retrofit)
AZUtils.printObject(response.body());
@Override
public void onFailure(Throwable t)
t.printStackTrace();
);
【讨论】:
是的,我认为这是改造 2.0 的问题 (github.com/square/retrofit/issues/1063),你可能想坚持使用 1.9 看我的编辑,我还没试过,欢迎你 我已经使用 de Retrofit 2.0 示例成功上传了一张图片。 @Bhargav 您可以将界面更改为@Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);
并且当您拥有文件时:Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
@insomniac 是的,我刚知道,也可以使用MultiPartBody.Part
【参考方案3】:
我为我的注册用户使用 Retrofit 2.0,从注册帐户发送多部分/表单文件图像和文本
在我的 RegisterActivity 中,使用 AsyncTask
//AsyncTask
private class Register extends AsyncTask<String, Void, String>
@Override
protected void onPreExecute() ..
@Override
protected String doInBackground(String... params)
new com.tequilasoft.mesasderegalos.dbo.Register().register(txtNombres, selectedImagePath, txtEmail, txtPassword);
responseMensaje = StaticValues.mensaje ;
mensajeCodigo = StaticValues.mensajeCodigo;
return String.valueOf(StaticValues.code);
@Override
protected void onPostExecute(String codeResult) ..
在我的 Register.java 类中使用 Retrofit 和同步调用
import android.util.Log;
import com.tequilasoft.mesasderegalos.interfaces.RegisterService;
import com.tequilasoft.mesasderegalos.utils.StaticValues;
import com.tequilasoft.mesasderegalos.utils.Utilities;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
/**Created by sam on 2/09/16.*/
public class Register
public void register(String nombres, String selectedImagePath, String email, String password)
try
// create upload service client
RegisterService service = ServiceGenerator.createUser(RegisterService.class);
// add another part within the multipart request
RequestBody requestEmail =
RequestBody.create(
MediaType.parse("multipart/form-data"), email);
// add another part within the multipart request
RequestBody requestPassword =
RequestBody.create(
MediaType.parse("multipart/form-data"), password);
// add another part within the multipart request
RequestBody requestNombres =
RequestBody.create(
MediaType.parse("multipart/form-data"), nombres);
MultipartBody.Part imagenPerfil = null;
if(selectedImagePath!=null)
File file = new File(selectedImagePath);
Log.i("Register","Nombre del archivo "+file.getName());
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
// MultipartBody.Part is used to send also the actual file name
imagenPerfil = MultipartBody.Part.createFormData("imagenPerfil", file.getName(), requestFile);
// finally, execute the request
Call<ResponseBody> call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres);
Response<ResponseBody> bodyResponse = call.execute();
StaticValues.code = bodyResponse.code();
StaticValues.mensaje = bodyResponse.message();
ResponseBody errorBody = bodyResponse.errorBody();
StaticValues.mensajeCodigo = errorBody==null
?null
:Utilities.mensajeCodigoDeLaRespuestaJSON(bodyResponse.errorBody().byteStream());
Log.i("Register","Code "+StaticValues.code);
Log.i("Register","mensaje "+StaticValues.mensaje);
Log.i("Register","mensajeCodigo "+StaticValues.mensaje);
catch (Exception e)
e.printStackTrace();
在RegisterService的接口中
public interface RegisterService
@Multipart
@POST(StaticValues.REGISTER)
Call<ResponseBody> registerUser(@Part MultipartBody.Part image,
@Part("email") RequestBody email,
@Part("password") RequestBody password,
@Part("nombre") RequestBody nombre
);
对于 InputStream 响应的 Utilities 解析
public class Utilities
public static String mensajeCodigoDeLaRespuestaJSON(InputStream inputStream)
String mensajeCodigo = null;
try
BufferedReader reader = new BufferedReader(
new InputStreamReader(
inputStream, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null)
sb.append(line).append("\n");
inputStream.close();
mensajeCodigo = sb.toString();
catch (Exception e)
Log.e("Buffer Error", "Error converting result " + e.toString());
return mensajeCodigo;
【讨论】:
【参考方案4】:Retrofit2.0
中的图片文件上传更新代码public interface ApiInterface
@Multipart
@POST("user/signup")
Call<UserModelResponse> updateProfilePhotoProcess(@Part("email") RequestBody email,
@Part("password") RequestBody password,
@Part("profile_pic\"; filename=\"pp.png")
RequestBody file);
将MediaType.parse("image/*")
更改为MediaType.parse("image/jpeg")
RequestBody reqFile = RequestBody.create(MediaType.parse("image/jpeg"),
file);
RequestBody email = RequestBody.create(MediaType.parse("text/plain"),
"upload_test4@gmail.com");
RequestBody password = RequestBody.create(MediaType.parse("text/plain"),
"123456789");
Call<UserModelResponse> call = apiService.updateProfilePhotoProcess(email,
password,
reqFile);
call.enqueue(new Callback<UserModelResponse>()
@Override
public void onResponse(Call<UserModelResponse> call,
Response<UserModelResponse> response)
String
TAG =
response.body()
.toString();
UserModelResponse userModelResponse = response.body();
UserModel userModel = userModelResponse.getUserModel();
Log.d("MainActivity",
"user image = " + userModel.getProfilePic());
@Override
public void onFailure(Call<UserModelResponse> call,
Throwable t)
Toast.makeText(MainActivity.this,
"" + TAG,
Toast.LENGTH_LONG)
.show();
);
【讨论】:
我尝试了很多方法来做到这一点,但我无法得到结果。我刚刚像你说的那样把它(“Change MediaType.parse("image/*") to MediaType.parse("image/jpeg")") 改了,现在可以用了,非常感谢。 希望我能给你多张选票,谢谢。 如果你的api有@Multipart
,那么@Part
注解必须提供一个名字或者使用MultipartBody.Part参数类型。
很好的解决方案! @Part("profile_pic\"; filename=\"pp.png\" " 中还有一个引用,应该是@Part("profile_pic\"; filename=\"pp.png "
【参考方案5】:
所以这是完成任务的非常简单的方法。您需要按照以下步骤操作:-
1.第一步
public interface APIService
@Multipart
@POST("upload")
Call<ResponseBody> upload(
@Part("item") RequestBody description,
@Part("imageNumber") RequestBody description,
@Part MultipartBody.Part imageFile
);
您需要以@Multipart request
进行整个通话。 item
和 image number
只是包裹在 RequestBody
中的字符串体。我们使用MultipartBody.Part class
,它允许我们在请求中发送除了二进制文件数据之外的实际文件名
2。第二步
File file = (File) params[0];
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body =MultipartBody.Part.createFormData("Image", file.getName(), requestBody);
RequestBody ItemId = RequestBody.create(okhttp3.MultipartBody.FORM, "22");
RequestBody ImageNumber = RequestBody.create(okhttp3.MultipartBody.FORM,"1");
final Call<UploadImageResponse> request = apiService.uploadItemImage(body, ItemId,ImageNumber);
现在你有了image path
,你需要转换成file
。现在使用RequestBody.create(MediaType.parse("multipart/form-data"), file)
方法将file
转换成RequestBody
。现在您需要使用方法 MultipartBody.Part.createFormData("Image", file.getName(), requestBody);
将您的 RequestBody requestFile
转换为 MultipartBody.Part
。
ImageNumber
和 ItemId
是我需要发送到服务器的另一个数据,所以我也将这两个数据都变成了RequestBody
。
For more info
【讨论】:
【参考方案6】:补充@insomniac 给出的答案。您可以创建一个Map
来放置RequestBody
的参数,包括图像。
接口代码
public interface ApiInterface
@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @PartMap Map<String, RequestBody> map);
Java 类代码
File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse("image/*"), file);
RequestBody name = RequestBody.create(MediaType.parse("text/plain"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse("text/plain"), AZUtils.getUserId(this));
Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"pp.png\" ", fbody);
map.put("FirstName", name);
map.put("Id", id);
Call<User> call = client.editUser(AZUtils.getToken(this), map);
call.enqueue(new Callback<User>()
@Override
public void onResponse(retrofit.Response<User> response, Retrofit retrofit)
AZUtils.printObject(response.body());
@Override
public void onFailure(Throwable t)
t.printStackTrace();
);
【讨论】:
如何上传多个包含 2 个字符串的文件? 可以回答***.com/questions/60428238/…【参考方案7】:在 kotlin 中非常简单,使用 toMediaType、asRequestBody 和 toRequestBody这是一个例子:
我在这里发布了几个普通字段以及一个 pdf 文件和一个使用 multipart 的图像文件
这是使用改造的 API 声明:
@Multipart
@POST("api/Lesson/AddNewLesson")
fun createLesson(
@Part("userId") userId: RequestBody,
@Part("LessonTitle") lessonTitle: RequestBody,
@Part pdf: MultipartBody.Part,
@Part imageFile: MultipartBody.Part
): Maybe<BaseResponse<String>>
这里是如何实际调用它:
api.createLesson(
userId.toRequestBody("text/plain".toMediaType()),
lessonTitle.toRequestBody("text/plain".toMediaType()),
startFromRegister.toString().toRequestBody("text/plain".toMediaType()),
MultipartBody.Part.createFormData(
"jpeg",
imageFile.name,
imageFile.asRequestBody("image/*".toMediaType())
),
MultipartBody.Part.createFormData(
"pdf",
pdfFile.name,
pdfFile.asRequestBody("application/pdf".toMediaType())
)
【讨论】:
【参考方案8】:使用 Retrofit 上传文件非常简单你需要构建你的 api 接口为
public interface Api
String BASE_URL = "http://192.168.43.124/ImageUploadApi/";
@Multipart
@POST("yourapipath")
Call<MyResponse> uploadImage(@Part("image\"; filename=\"myfile.jpg\" ") RequestBody file, @Part("desc") RequestBody desc);
在上面的代码中,image 是键名,所以如果你使用 php,你会写 $_FILES['image']['tmp_name'] 来得到这个. filename="myfile.jpg" 是与请求一起发送的文件的名称。
现在要上传文件,您需要一种方法,该方法将为您提供 Uri 的绝对路径。
private String getRealPathFromURI(Uri contentUri)
String[] proj = MediaStore.Images.Media.DATA;
CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
Cursor cursor = loader.loadInBackground();
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String result = cursor.getString(column_index);
cursor.close();
return result;
现在您可以使用以下代码上传您的文件。
private void uploadFile(Uri fileUri, String desc)
//creating a file
File file = new File(getRealPathFromURI(fileUri));
//creating request body for file
RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file);
RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), desc);
//The gson builder
Gson gson = new GsonBuilder()
.setLenient()
.create();
//creating retrofit object
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
//creating our api
Api api = retrofit.create(Api.class);
//creating a call and calling the upload image method
Call<MyResponse> call = api.uploadImage(requestFile, descBody);
//finally performing the call
call.enqueue(new Callback<MyResponse>()
@Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response)
if (!response.body().error)
Toast.makeText(getApplicationContext(), "File Uploaded Successfully...", Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(), "Some error occurred...", Toast.LENGTH_LONG).show();
@Override
public void onFailure(Call<MyResponse> call, Throwable t)
Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
);
更详细的解释可以访问这个Retrofit Upload File Tutorial。
【讨论】:
这是一个 hack,它已在改造 2.0 中修复了一段时间。请参阅下面的 jimmy0251 答案。【参考方案9】:Kotlin 版本更新了 RequestBody.create
的弃用:
改造界面
@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>
并上传
fun uploadFile(fileUrl: String)
val file = File(fileUrl)
val fileUploadService = RetrofitClientInstance.retrofitInstance.create(FileUploadService::class.java)
val requestBody = file.asRequestBody(file.extension.toMediaTypeOrNull())
val filePart = MultipartBody.Part.createFormData(
"blob",file.name,requestBody
)
val call = fileUploadService.uploadFile(filePart)
call.enqueue(object: Callback<FileResponse>
override fun onFailure(call: Call<FileResponse>, t: Throwable)
Log.d(TAG,"Fckd")
override fun onResponse(call: Call<FileResponse>, response: Response<FileResponse>)
Log.d(TAG,"success"+response.toString()+" "+response.body().toString()+" "+response.body()?.status)
)
感谢@jimmy0251
【讨论】:
【参考方案10】:* Return MultipartBody from file path
public static MultipartBody.Part generateFileBody(String imagePath)
File file = new File(imagePath);
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
return MultipartBody.Part.createFormData("mediaProfilePic", file.getName(), requestFile);
【讨论】:
【参考方案11】:不要在函数名中使用多个参数 只需使用简单的几个参数约定,这将增加代码的可读性,为此你可以这样做 -
// MultipartBody.Part.createFormData("partName", data)
Call<SomReponse> methodName(@Part MultiPartBody.Part part);
// RequestBody.create(MediaType.get("text/plain"), data)
Call<SomReponse> methodName(@Part(value = "partName") RequestBody part);
/* for single use or you can use by Part name with Request body */
// add multiple list of part as abstraction |ease of readability|
Call<SomReponse> methodName(@Part List<MultiPartBody.Part> parts);
Call<SomReponse> methodName(@PartMap Map<String, RequestBody> parts);
// this way you will save the abstraction of multiple parts.
您在使用 Retrofit 时可能会遇到多个异常,所有异常都记录为代码,请参阅retrofit2/RequestFactory.java
。您可以使用两个函数 parseParameterAnnotation
和 parseMethodAnnotation
可以抛出异常,请通过此操作,这将比 googling/***
【讨论】:
【参考方案12】:requestBody 可以用来上传
val body: RequestBody = Builder().setType(MultipartBody.FORM)
.addFormDataPart(
"file", "<image name you wish to give>",
RequestBody.create(
MediaType.parse("application/octet-stream"),
File(path)
)
)
.build()
uploadProfilePhoto(body)
然后像这样调用:
@POST("/**")
suspend fun uploadProfilePhoto(
@Body body: RequestBody,
): ResponseBody
【讨论】:
生成器 by okhttp3.MultipartBody.Builder() ??【参考方案13】:就我而言,我需要发送一个 PDF 文件 (application/pdf
) 以及 JSON 信息 (application/json
)。值得庆幸的是,Retrofit2 让这变得超级简单。
我的界面如下:
interface MyApi
@Multipart
@POST("upload")
fun uploadPDF(
@Part file: MultipartBody.Part,
@Part(value = "jsoninfo") jsoninfo: MyJsonObject
): Call<MyResponse>
jsoninfo
是我的 JSON 数据的名称,MyJsonObject
是我的数据类,MyResponse
是我期待的响应,当然。
然后,我只是调用我的 API 方法如下:
val myJsonObject = MyJsonObject(...)
// "file" is of type byte[] already
val requestBody = RequestBody.create(file, MediaType.parse("application/pdf"))
val filePart = MultipartBody.Part.createFormData("file", "myfile.pdf", requestBody)
api.uploadPDF(filePart, myJsonObject).enqueue(...)
【讨论】:
以上是关于使用 Retrofit 2.0 发布多部分表单数据,包括图像的主要内容,如果未能解决你的问题,请参考以下文章
Retrofit - @Body 参数不能与表单或多部分编码一起使用
AFNetworking 2.0 多部分/表单数据上传到 mySQL