Data Formulir Multipart POST menggunakan Retrofit 2.0 termasuk gambar


148

Saya mencoba melakukan HTTP POST ke server menggunakan 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();

Server mengembalikan kesalahan yang mengatakan file tidak valid.

Ini aneh karena saya telah mencoba mengunggah file yang sama dengan format yang sama di iOS (menggunakan perpustakaan lain), tetapi berhasil diunggah.

Saya bertanya-tanya apa cara yang tepat untuk mengunggah gambar menggunakan Retrofit 2.0 ?

Haruskah saya menyimpannya ke disk terlebih dahulu sebelum mengunggah?

PS: Saya telah menggunakan retrofit untuk permintaan Multipart lain yang tidak termasuk gambar dan mereka berhasil diselesaikan. Masalahnya adalah ketika saya mencoba memasukkan byte ke tubuh.



Jawaban:


180

Saya menyoroti solusi di 1.9 dan 2.0 karena berguna untuk beberapa

Dalam 1.9, saya pikir solusi yang lebih baik adalah menyimpan file ke disk dan menggunakannya sebagai file yang diketik seperti:

RetroFit 1.9

(Saya tidak tahu tentang implementasi sisi server Anda) memiliki metode antarmuka API yang mirip dengan ini

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

Dan gunakan itu seperti

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

Untuk RetroFit 2 Gunakan metode berikut

RetroFit 2.0 (Ini adalah solusi untuk masalah di RetroFit 2 yang diperbaiki sekarang, untuk metode yang benar merujuk jawaban jimmy0251 )

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

Gunakan seperti:

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
Ya, saya pikir ini adalah masalah ( github.com/square/retrofit/issues/1063 ) dengan retrofit 2.0, Anda mungkin ingin tetap menggunakan 1,9
insomnia

2
lihat hasil edit saya, saya belum mencobanya, Anda dipersilakan untuk
insomnia

1
Saya telah berhasil mengunggah gambar menggunakan contoh de Retrofit 2.0.
jerogaren

3
@Bhargav Anda dapat mengubah antarmuka ke @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);Dan Ketika Anda memiliki file: 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 Ya saya baru tahu tentang itu, bisa juga menggunakanMultiPartBody.Part
Bhargav

177

Ada cara yang benar untuk mengunggah file dengan namanya dengan Retrofit 2 , tanpa peretasan :

Tentukan antarmuka API:

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

Unggah file seperti ini:

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

Ini hanya menunjukkan pengunggahan file, Anda juga dapat menambahkan parameter lain dalam metode yang sama dengan @Partanotasi.


2
bagaimana kita bisa mengirim banyak file menggunakan MultipartBody.Part?
Praveen Sharma

Anda dapat menggunakan banyak MultipartBody.Partargumen di API yang sama.
jimmy0251

Saya perlu mengirim koleksi gambar dengan "image []" sebagai kunci. Saya mencoba @Part("images[]") List<MultipartBody.Part> imagestetapi memberikan kesalahan bahwa@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma

Anda harus menggunakan @Body MultipartBody multipartBodydan MultipartBody.Builderuntuk mengirim koleksi gambar.
jimmy0251

2
bagaimana saya dapat menambahkan kunci ke mutipart
andro

23

Saya menggunakan Retrofit 2.0 untuk pengguna register saya, mengirim multi-bagian / bentuk File gambar dan teks dari akun register

Di RegisterActivity saya, gunakan 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) {..}

Dan di kelas Register.java saya adalah tempat menggunakan Retrofit dengan panggilan sinkron

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

Di antarmuka 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
);
}

Untuk Utilitas, parsing atau respons 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

Perbarui Kode untuk mengunggah file gambar di 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);
}

Ubah MediaType.parse("image/*")keMediaType.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();

    }
});

Saya mencoba banyak cara untuk melakukan ini tetapi saya tidak bisa mendapatkan hasilnya. Saya baru saja mengubah ini ("Ubah MediaType.parse (" image / * ") menjadi MediaType.parse (" image / jpeg ")") seperti yang Anda katakan dan berfungsi sekarang, terima kasih banyak.
Gunnar

berharap saya bisa memberi Anda lebih dari satu suara, Terima kasih.
Rohit Maurya

jika api Anda memiliki @Multipartkemudian@Part anotasi harus memberikan nama atau menggunakan tipe parameter MultipartBody.Part.
Rohit

Solusi bagus! Dan ada satu kutipan lagi di @Part ("profile_pic \"; filename = \ "pp.png \" ", seharusnya@Part("profile_pic\"; filename=\"pp.png "
Ninja

15

Menambah jawaban yang diberikan oleh @insomniac . Anda dapat membuat Mapuntuk menempatkan parameter untuk RequestBodymenyertakan gambar.

Kode untuk Antarmuka

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

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

bagaimana saya bisa mengunggah banyak file dengan 2 string?
Jay Dangar

Apakah mungkin bagi Anda untuk menjawab stackoverflow.com/questions/60428238/…
Ranjit

14

Jadi cara yang sangat sederhana untuk mencapai tugas Anda. Anda harus mengikuti langkah di bawah ini: -

1. Langkah pertama

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

Anda harus menjadikan seluruh panggilan sebagai @Multipart request. itemdan image numberhanya string body yang dibungkus RequestBody. Kami menggunakan MultipartBody.Part classyang memungkinkan kami untuk mengirim nama file yang sebenarnya selain data file biner dengan permintaan

2. Langkah kedua

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

Sekarang Anda miliki image pathdan Anda perlu mengkonversi ke file. Sekarang dikonversi filemenjadi RequestBodymenggunakan metode RequestBody.create(MediaType.parse("multipart/form-data"), file). Sekarang Anda perlu mengkonversi Anda RequestBody requestFilemenjadi MultipartBody.Partmenggunakan metode MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumber dan ItemId data saya yang lain yang harus saya kirim ke server jadi saya juga membuat keduanya RequestBody.

Untuk info lebih lanjut


3

Mengunggah File menggunakan Retrofit Cukup Sederhana. Anda perlu membuat antarmuka api sebagai

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

}

pada gambar kode di atas adalah nama kunci jadi jika Anda menggunakan php Anda akan menulis $ _FILES ['gambar'] ['tmp_name'] untuk mendapatkan ini. Dan nama file = "myfile.jpg" adalah nama file Anda yang dikirim bersama permintaan.

Sekarang untuk mengunggah file, Anda memerlukan metode yang akan memberi Anda jalur absolut dari 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;
}

Sekarang Anda dapat menggunakan kode di bawah ini untuk mengunggah file Anda.

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

Untuk penjelasan lebih rinci, Anda dapat mengunjungi Tutorial File Unggahan Retrofit ini .


Ini peretasan, sudah diperbaiki di retrofit 2.0 untuk sementara. Lihat jimmy0251 jawaban di bawah ini.
Matt Wolfe

1

Versi Kotlin dengan pembaruan untuk pencabutan RequestBody.create :

Antarmuka retrofit

@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>

dan untuk Mengunggah

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

    })
}

Terima kasih kepada @ jimmy0251


0

Jangan gunakan banyak parameter dalam nama fungsi, cukup gunakan konvensi beberapa arg sederhana yang akan meningkatkan keterbacaan kode , karena ini dapat Anda lakukan seperti -

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

Mungkin ada beberapa pengecualian yang mungkin Anda temui saat menggunakan Retrofit, semua pengecualian yang didokumentasikan sebagai kode , memiliki panduanretrofit2/RequestFactory.java . Anda dapat dua fungsi parseParameterAnnotationdan di parseMethodAnnotationmana Anda dapat pengecualian dilemparkan, silakan melalui ini, itu akan menghemat banyak waktu Anda daripada googling / stackoverflow


0

di kotlin cukup mudah, menggunakan metode ekstensi toMediaType , asRequestBody dan toRequestBody berikut ini contohnya:

di sini saya memposting beberapa bidang normal bersama dengan file pdf dan file gambar menggunakan multi-bagian

ini adalah deklarasi API menggunakan retrofit:

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

dan inilah cara sebenarnya menyebutnya:

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())
            )
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.