Данные многостраничной формы POST с использованием Retrofit 2.0, включая изображение

Я пытаюсь сделать HTTP POST на сервер с помощью 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<APIResults> call = ServiceAPI.updateProfile(
        RequestBody.create(MEDIA_TYPE_TEXT, emailString),
        RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));

call.enqueue();

Сервер возвращает ошибку о том, что файл недействителен.

Это странно, потому что я пытался загрузить тот же файл в том же формате на iOS (используя другую библиотеку), но он успешно загружен.

Мне интересно, как правильно загрузить изображение с помощью Retrofit 2.0?

Должен ли я сохранить его на диск перед загрузкой?

PS: Я использовал модификацию для другого запроса Multipart, который не включает изображение, и они успешно завершены. Проблема в том, когда я пытаюсь включить байт в тело.

Ответ 1

Я подчеркиваю решение в 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, которая теперь исправлена, для правильного метода обратитесь к ответу jimmy0251)

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

Существует правильный способ загрузки файла с его именем с помощью Retrofit 2 без хака:

Определить интерфейс 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.

Ответ 3

Я использовал Retrofit 2.0 для своих пользователей регистрации, отправлю multipart/form Изображение и текст файла из учетной записи регистрации

В моей 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

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"), "[email protected]");
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();

    }
});

Ответ 5

Добавление к ответу @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();
 }
});

Ответ 6

Так что это очень простой способ достичь своей цели. Вы должны следовать ниже шаг:

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 Теперь преобразуйте file в RequestBody используя метод RequestBody.create(MediaType.parse("multipart/form-data"), file). Теперь вам нужно преобразовать ваш RequestBody requestFile в MultipartBody.Part используя метод MultipartBody.Part.createFormData("Image", file.getName(), requestBody); ,

ImageNumber и ItemId - это мои другие данные, которые мне нужно отправить на сервер, поэтому я тоже превращаю обе вещи в RequestBody.

Для получения дополнительной информации

Ответ 7

Загрузка файлов с помощью 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);

}

в приведенном выше коде изображение - это имя ключа, поэтому, если вы используете php, вы напишете $_ FILES ['image'] ['tmp_name'], чтобы получить это, И имя_файла = "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();
            }
        });
    }

Для получения более подробного объяснения вы можете посетить этот Обновление загружаемого файла загрузки.