Görüntü dahil Retrofit 2.0 kullanarak POST Çok Parçalı Form Verileri


155

Retrofit 2.0 kullanarak sunucuya bir HTTP POST yapmaya çalışıyorum

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

Sunucu, dosyanın geçerli olmadığını söyleyen bir hata döndürür.

Bu garip çünkü aynı dosyayı iOS'ta aynı formatta yüklemeye çalıştım (başka bir kitaplığı kullanarak), ancak başarıyla yüklüyor.

Ben kullanarak bir resim yüklemek için uygun yolu nedir merak ediyorum Sonradan montaj 2.0 ?

Yüklemeden önce diske kaydetmeli miyim?

Not: Görüntü içermeyen diğer Çok Parçalı istek için iyileştirme kullandım ve başarıyla tamamlandılar. Sorun, vücuda bir bayt eklemeye çalıştığım zamandır.



Yanıtlar:


186

Bazıları için faydalı olduğu için çözümü hem 1.9 hem de 2.0'da vurguluyorum.

İçinde 1.9, daha iyi çözümün dosyayı diske kaydetmek ve aşağıdaki gibi Yazılan dosya olarak kullanmak olduğunu düşünüyorum:

RetroFit 1.9

(Sunucu tarafı uygulamanızı bilmiyorum) buna benzer bir API arayüz yöntemi var

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

Ve gibi kullan

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

RetroFit 2 için Aşağıdaki yöntemi kullanın

RetroFit 2.0 (Bu, RetroFit 2'deki bir sorun için geçici bir çözümdü ve şimdi düzeltildi, doğru yöntem için jimmy0251'in cevabına bakın )

API Arayüzü:

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

Şunun gibi kullanın:

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
Evet, pekala bence bu bir sorun ( github.com/square/retrofit/issues/1063 ), iyileştirme 2.0 ile, 1.9'a bağlı kalmak isteyebilirsiniz
insomniac

2
düzenlememi gör, henüz denemedim, hoş geldiniz
insomniac

1
De Retrofit 2.0 örneğini kullanarak başarıyla bir resim yükledim.
jerogaren

3
@Bhargav Arayüzü @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);Ve Dosyaya sahip olduğunuzda olarak değiştirebilirsiniz : 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 Evet bunu yeni öğrendim, şunu da kullanabilirimMultiPartBody.Part
Bhargav

186

Retrofit 2 ile adıyla bir dosyayı herhangi bir hack olmadan yüklemenin doğru bir yolu vardır :

API arayüzünü tanımlayın:

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

Şöyle bir dosya yükle:

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

Bu sadece dosya yüklemeyi gösterir, ayrıca @Partaçıklama ile aynı yöntemde başka parametreler de ekleyebilirsiniz .


2
MultipartBody.Part kullanarak birden fazla dosyayı nasıl gönderebiliriz?
Praveen Sharma

MultipartBody.PartAynı API'de birden çok argüman kullanabilirsiniz .
jimmy0251

Anahtar olarak "image []" olan resim koleksiyonlarını göndermem gerekiyor. Denedim @Part("images[]") List<MultipartBody.Part> imagesama hata veriyor@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma

Resim koleksiyonunu göndermek için @Body MultipartBody multipartBodyve kullanmalısınız MultipartBody.Builder.
jimmy0251

2
mutipart'a nasıl anahtar ekleyebilirim
andro

23

Kayıtlı kullanıcılarım için Retrofit 2.0 kullandım, çok parçalı / formlu dosya görüntüsü ve kayıt hesabından metin gönderdim

RegisterActivity'de bir AsyncTask kullanın

//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) {..}

Ve Register.java sınıfımda Retrofit ile senkronize çağrı kullanılır

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 arayüzünde

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

Yardımcı programlar için InputStream yanıtının ayrıştırılması

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

17

Retrofit2.0'da görüntü dosyası yüklemek için Güncelleme Kodu

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

Değişim MediaType.parse("image/*")içinMediaType.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();

    }
});

Bunu yapmanın birçok yolunu deniyordum ama sonuçları alamadım. Dediğin gibi bunu ("MediaType.parse (" image / * ") MediaType.parse (" image / jpeg ") olarak değiştirdim ve şimdi çalışıyor, çok teşekkür ederim.
Gunnar

keşke size birden fazla oy verebilseydim, teşekkür ederim.
Rohit Maurya

Lütfen api varsa @Multiparto zaman @Partek açıklama bir ad tedarikini veya MultipartBody.Part parametre türü kullanılmalıdır.
Rohit

Güzel çözüm! Ve @Part'ta bir alıntı daha var ("profile_pic \"; dosya adı = \ "pp.png \" ", öyle olmalı@Part("profile_pic\"; filename=\"pp.png "
Ninja,

15

@İnsomniac tarafından verilen cevaba ekleniyor . Görüntüyü Mapeklemek için parametre koymak için bir oluşturabilirsiniz RequestBody.

Arayüz Kodu

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

Java sınıfı için kod

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 dizeli birden çok dosyayı nasıl yükleyebilirim?
Jay Dangar


14

Yani görevinize ulaşmanın çok basit yolu. Aşağıdaki adımı izlemeniz gerekir: -

1. İlk adım

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

Tüm aramayı olarak yapmanız gerekir @Multipart request. itemve image numbersadece sarılmış bir dize gövdesidir RequestBody. MultipartBody.Part classİkili dosya verilerinin yanı sıra gerçek dosya adını istekle birlikte göndermemize izin veren kullanıyoruz

2. İkinci adım

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

Şimdi var image pathve kendisine dönüştürmek gerekir file.Şimdi dönüştürmek fileiçine RequestBodyyöntemi kullanılarak RequestBody.create(MediaType.parse("multipart/form-data"), file). Artık dönüştürmek gerekir RequestBody requestFileiçine MultipartBody.Partyöntemi kullanarak MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumberve ItemIdsunucuya göndermem gereken başka bir veridir, bu yüzden ikisini de içine alıyorum RequestBody.

Daha fazla bilgi için


3

Retrofit kullanarak Dosya Yükleme Oldukça Basit Api arayüzünüzü şu şekilde oluşturmanız gerekir:

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

}

Yukarıdaki kod görüntüsündeki anahtar adıdır, bu nedenle php kullanıyorsanız, bunu elde etmek için $ _FILES ['resim'] ['tmp_name'] yazacaksınız . Ve dosyaadı = "dosyam.jpg" , istekle birlikte gönderilen dosyanızın adıdır.

Şimdi dosyayı yüklemek için size Uri'den mutlak yolu verecek bir yönteme ihtiyacınız var.

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

Şimdi dosyanızı yüklemek için aşağıdaki kodu kullanabilirsiniz.

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

Daha ayrıntılı açıklama için bu Retrofit Dosya Yükleme Eğitimini ziyaret edebilirsiniz .


Bu bir hack, bir süredir güçlendirme 2.0'da düzeltildi. Aşağıdaki jimmy0251 cevabına bakın.
Matt Wolfe

1

Aşağıdakilerin kullanımdan kaldırılması için güncellemeli Kotlin sürümü RequestBody.create:

Güçlendirme arayüzü

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

ve Yüklemek için

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'e teşekkürler


1

kotlin'de toMediaType , asRequestBody ve toRequestBody'nin uzantı yöntemlerini kullanmak oldukça kolaydır. İşte bir örnek:

burada bir pdf dosyası ve çok parçalı bir görüntü dosyasıyla birlikte birkaç normal alan gönderiyorum

bu, retrofit kullanan API bildirimidir:

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

ve işte aslında nasıl adlandırılacağı:

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

0

İşlev adında birden fazla parametre kullanmayın , yalnızca kodların okunabilirliğini artıracak basit birkaç argüman kuralını kullanın, bunun için bunu yapabilirsiniz -

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

Olabilir Eğer Sonradan montaj, kod olarak belgelenmiş istisnalar tüm kullanırken karşılaşabilecekleri birden istisnalar , bir gidiş yolu varretrofit2/RequestFactory.java . iki fonksiyona sahip olabilirsiniz parseParameterAnnotationve parseMethodAnnotationnerede istisna yapabiliyorsanız, lütfen bunu gözden geçirin, googling / stackoverflow'dan daha fazla zaman kazandıracaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.