Дані формулярного формату POST за допомогою Retrofit 2.0, включаючи зображення


148

Я намагаюся зробити 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, який не містить зображення, і вони успішно виконані. Проблема полягає в тому, що я намагаюся включити байт до тіла.



Відповіді:


180

Я підкреслюю рішення як в 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();
    }
});

5
Так, добре, я думаю, що це проблема ( github.com/square/retrofit/isissue/1063 ) з модернізацією 2.0, ви можете захотіти дотримуватися 1.9
безсоння

2
дивіться мою редакцію, я її ще не пробував, вас вітають
безсоння

1
Я успішно завантажив зображення, використовуючи приклад de Retrofit 2.0.
jerogaren

3
@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

2
@insomniac Так, я щойно дізнався про це, також можу використовуватиMultiPartBody.Part
Bhargav

177

Існує правильний спосіб завантаження файлу з його іменем за допомогою 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анотацією.


2
як ми можемо надсилати кілька файлів за допомогою MultipartBody.Part?
Praveen Sharma

Ви можете використовувати кілька MultipartBody.Partаргументів в одному API.
jimmy0251

Мені потрібно надіслати колекції зображень із ключем "image []". Я спробував, @Part("images[]") List<MultipartBody.Part> imagesале це дає помилку, що@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma

Ви повинні використовувати @Body MultipartBody multipartBodyі MultipartBody.Builderдля надсилання колекції зображень.
jimmy0251

2
Як я можу додати ключ до mutipart
andro

23

Я використовував 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

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

16

Оновити код для завантаження файлів зображень у 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();

    }
});

Я намагався зробити багато способів, але не зміг отримати результатів. Я щойно змінив це ("Змінити MediaType.parse (" image / * ") на MediaType.parse (" image / jpeg ")"), як ви сказали, і це працює зараз, дуже дякую.
Гуннар

Бажаю, щоб я міг дати вам більше одного голосу, дякую.
Рохіт Маурія

якщо ваш апі має @Multipartте @Partанотації повинні надати ім'я або MultipartBody.Part типу параметра.
Рохіт

Гарне рішення! І ще одна цитата в @Part ("profile_pic \"; ім'я файлу = \ "pp.png \" ", це повинно бути@Part("profile_pic\"; filename=\"pp.png "
Ninja

15

Додавання до відповіді, наданої @insomniac . Ви можете створити a, 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 рядками?
Джей Дангар

Чи можна відповісти stackoverflow.com/questions/60428238/…
Ranjit

14

Тож дуже простий спосіб досягти поставленого завдання. Вам потрібно виконати наступний крок: -

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.

Для отримання додаткової інформації


3

Завантаження файлів за допомогою 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" - це ім'я вашого файлу, який надсилається із запитом.

Тепер для завантаження файлу вам потрібен метод, який дасть вам абсолютний шлях від Урі.

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

Для більш детального пояснення ви можете відвідати цей посібник із завантаження файлів із вдосконаленням .


Це злом, це було зафіксовано в модернізації 2.0 на деякий час. Дивіться відповідь jimmy0251 нижче.
Метт Вулф

1

Версія Котліна з оновленням для зображення 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


0

Не використовуйте декілька параметрів у назві функції, просто перейдіть за допомогою декількох простих довідок аргументів, які збільшать читабельність кодів , для цього можна зробити так -

// 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 / stackoverflow


0

в kotlin його досить легко, використовуючи розширення методів toMediaType , asRequestBody та toRequestBody ось приклад:

тут я розміщую пару звичайних полів разом із файлом pdf та файлом зображення за допомогою мультичастки

це декларація 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())
            )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.