이미지를 포함하여 Retrofit 2.0을 사용하는 POST Multipart Form Data


148

Retrofit 2.0을 사용하여 서버에 HTTP POST를 수행하려고합니다.

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을 사용하여 이미지를 업로드하는 올바른 방법이 무엇인지 궁금합니다 .

업로드하기 전에 먼저 디스크에 저장해야합니까?

추신 : 나는 이미지가 포함되지 않은 다른 멀티 파트 요청에 대해 개조를 사용했으며 성공적으로 완료되었습니다. 문제는 본문에 바이트를 포함하려고 할 때입니다.



답변:


180

1.9와 2.0 모두에서 솔루션을 강조하고 있습니다.

에서 1.9, 나는 더 나은 솔루션은 디스크에 파일을 저장하고 같은 형식화 된 파일로 사용하는 것입니다 생각 :

레트로 핏 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
예, 글쎄, 그것이 retrofit 2.0 의 문제 ( github.com/square/retrofit/issues/1063 ) 라고 생각합니다. 1.9로 고정하고 싶을 수도 있습니다
불면증

2
내 편집을 참조하십시오, 나는 그것을 시도하지 않았습니다, 당신은 환영합니다
불면증

1
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 multipartBodyMultipartBody.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();

    }
});

나는 이것을하기 위해 여러 가지 방법을 시도했지만 결과를 얻을 수 없었습니다. 방금 말한 것처럼이 ( "Change MediaType.parse ("image / * ") to MediaType.parse ("image / jpeg ")") 변경하고 지금 작동합니다. 정말 감사합니다.
Gunnar

하나 이상의 투표를 할 수 있으면 좋겠습니다. 감사합니다.
Rohit Maurya 19

당신의 API가있는 경우 @Multipart다음 @Part주석의 이름을 제공하거나 MultipartBody.Part 매개 변수 유형을 사용해야합니다.
Rohit

좋은 해결책! @Part ( "profile_pic \"; filename = \ "pp.png \" "에는 한 가지 인용문이 더 있습니다.@Part("profile_pic\"; filename=\"pp.png "
Ninja

15

@insomniac의 답변에 추가하십시오 . 이미지 MapRequestBody포함 하기위한 매개 변수를 넣을 수 있습니다 .

인터페이스 코드

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

두 개의 문자열로 여러 파일을 어떻게 업로드 할 수 있습니까?
Jay Dangar


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부탁해 변환 fileRequestBody방법을 사용하여 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'] 을 작성하여 가져옵니다. 그리고 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을 방문하십시오 .


이것은 해킹입니다. 잠시 동안 retrofit 2.0으로 수정되었습니다. 아래의 jimmy0251 답변을 참조하십시오.
매트 울프

1

지원 중단 업데이트가 포함 된 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 감사합니다


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 . 당신이 할 수있는 두 가지 기능을 할 수 parseParameterAnnotationparseMethodAnnotation당신이를 통해 발생한 예외로 할 수 이동하시기 바랍니다 수있는, 그것은보다 시간을 당신의 많은 저장됩니다 / 유래 인터넷 검색


0

kotlin에서 toMediaType , asRequestBodytoRequestBody 의 확장 메소드를 사용하면 매우 쉽습니다. 여기에 예제가 있습니다.

여기에 multipart를 사용하여 pdf 파일 및 이미지 파일과 함께 두 개의 일반 필드를 게시하고 있습니다

이것은 retrofit을 사용한 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())
            )
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.