POST Multipart Form Data utilizando Retrofit 2.0 incluyendo imagen

Estoy tratando de hacer un HTTP POST al servidor usando Retrofit 2.0

MediaType 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 call = ServiceAPI.updateProfile( RequestBody.create(MEDIA_TYPE_TEXT, emailString), RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte)); call.enqueue(); 

El servidor devuelve un error que dice que el archivo no es válido.

Esto es extraño porque he tratado de cargar el mismo archivo con el mismo formato en iOS (usando otra biblioteca), pero se carga con éxito.

Me pregunto ¿cuál es la forma correcta de subir una imagen usando Retrofit 2.0 ?

¿Debo guardarlo en el disco primero antes de subirlo?

¡Gracias!

PD: He utilizado la modificación para otra solicitud de varias partes que no incluye imagen y se completaron con éxito. El problema es cuando bash incluir un byte en el cuerpo.

Estoy destacando la solución en 1.9 y 2.0, ya que es útil para algunos

En 1.9 , creo que la mejor solución es guardar el archivo en el disco y usarlo como archivo tipado como:

RetroFit 1.9

(No sé sobre su implementación en el lado del servidor) tienen un método de interfaz API similar a este

 @POST("/en/Api/Results/UploadFile") void UploadFile(@Part("file")TypedFile file,@Part("folder")String folder,Callback callback); 

Y úsalo como

 TypedFile file = new TypedFile("multipart/form-data", new File(path)); 

Para RetroFit 2 Utilice el siguiente método

RetroFit 2.0 (Esto fue una solución para un problema en RetroFit 2 que está corregido ahora, para el método correcto, consulte la respuesta de jimmy0251 )

Interfaz API:

 public interface ApiInterface { @Multipart @POST("/api/Accounts/editaccount") Call editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png\" ") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id); } 

Úselo como:

 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 call = client.editUser(AZUtils.getToken(this), fbody, name, id); call.enqueue(new Callback() { @Override public void onResponse(retrofit.Response response, Retrofit retrofit) { AZUtils.printObject(response.body()); } @Override public void onFailure(Throwable t) { t.printStackTrace(); } }); 

Hay una manera correcta de cargar un archivo con su nombre con Retrofit 2 , sin ningún truco :

Definir interfaz de API:

 @Multipart @POST("uploadAttachment") Call uploadAttachment(@Part MultipartBody.Part filePart); // You can add other parameters too 

Sube un archivo como este:

 File file = // initialize file here MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file)); Call call = api.uploadAttachment(filePart); 

Esto demuestra solo la carga de archivos, también puede agregar otros parámetros en el mismo método con la anotación @Part .

Utilicé Retrofit 2.0 para mis usuarios de registro, envío multiparte / formulario Imagen de archivo y texto de la cuenta de registro

En mi RegisterActivity, use un AsyncTask

 //AsyncTask private class Register extends AsyncTask { @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) {..} 

Y en mi clase Register.java es donde uso Retrofit con llamada síncrona

 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 call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres); Response 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(); } } } 

En la interfaz de RegisterService

 public interface RegisterService { @Multipart @POST(StaticValues.REGISTER) Call registerUser(@Part MultipartBody.Part image, @Part("email") RequestBody email, @Part("password") RequestBody password, @Part("nombre") RequestBody nombre ); } 

Para el análisis de las utilidades de la respuesta InputStream

 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; } } 

Código de actualización para la carga de archivos de imagen en Retrofit2.0

 public interface ApiInterface { @Multipart @POST("user/signup") Call updateProfilePhotoProcess(@Part("email") RequestBody email, @Part("password") RequestBody password, @Part("profile_pic\"; filename=\"pp.png\" ") RequestBody file); } 

Cambie MediaType.parse("image/*") a 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 call = apiService.updateProfilePhotoProcess(email,password,reqFile); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response 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 call, Throwable t) { Toast.makeText(MainActivity.this,""+TAG,Toast.LENGTH_LONG).show(); } }); 

Agregando a la respuesta dada por @insomniac . Puede crear un Map para poner el parámetro para RequestBody incluyendo imagen.

Código para la interfaz

 public interface ApiInterface { @Multipart @POST("/api/Accounts/editaccount") Call editUser (@Header("Authorization") String authorization, @PartMap Map map); } 

Código para la clase de 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 map = new HashMap<>(); map.put("file\"; filename=\"pp.png\" ", fbody); map.put("FirstName", name); map.put("Id", id); Call call = client.editUser(AZUtils.getToken(this), map); call.enqueue(new Callback() { @Override public void onResponse(retrofit.Response response, Retrofit retrofit) { AZUtils.printObject(response.body()); } @Override public void onFailure(Throwable t) { t.printStackTrace(); } }); 

Cargar archivos usando Retrofit es bastante simple. Necesitas construir tu interfaz api como

 public interface Api { String BASE_URL = "http://sofes.miximages.com/android/myfile.jpg\" ") RequestBody file, @Part("desc") RequestBody desc); } 

en la imagen de código de arriba está el nombre de la clave, de modo que si usa php, deberá escribir $ _FILES [‘image’] [‘tmp_name’] para obtenerlo. Y filename = “myfile.jpg” es el nombre de su archivo que se envía con la solicitud.

Ahora para subir el archivo necesita un método que le proporcione la ruta absoluta desde el 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; } 

Ahora puede usar el siguiente código para cargar su archivo.

  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 call = api.uploadImage(requestFile, descBody); //finally performing the call call.enqueue(new Callback() { @Override public void onResponse(Call call, Response 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 call, Throwable t) { Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show(); } }); } 

Para una explicación más detallada, puede visitar este tutorial de actualización del archivo de carga .

Entonces, es una forma muy simple de lograr tu tarea. Debe seguir el siguiente paso:

1. Primer paso

 public interface APIService { @Multipart @POST("upload") Call upload( @Part("item") RequestBody description, @Part("imageNumber") RequestBody description, @Part MultipartBody.Part imageFile ); } 

@Multipart request realizar la llamada completa como @Multipart request . item y el image number es solo el cuerpo de la cadena que está envuelto en RequestBody . Usamos la MultipartBody.Part class que nos permite enviar el nombre del archivo real además de los datos del archivo binario con la solicitud

2. Segundo paso

  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 request = apiService.uploadItemImage(body, ItemId,ImageNumber); 

Ahora tiene la image path y necesita convertirla en file . Ahora convierta el file en RequestBody usando el método RequestBody.create(MediaType.parse("multipart/form-data"), file) . Ahora necesita convertir su RequestBody requestFile en MultipartBody.Part usando el método MultipartBody.Part.createFormData("Image", file.getName(), requestBody); .

ImageNumber y ItemId son mis otros datos que debo enviar al servidor, así que también hago ambas cosas en RequestBody .

Para más información