POST Multipart Form Data mit Retrofit 2.0 einschließlich Bild


148

Ich versuche, mit Retrofit 2.0 einen HTTP-POST für den Server durchzuführen

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

Der Server gibt einen Fehler zurück, der besagt, dass die Datei ungültig ist.

Das ist seltsam, weil ich versucht habe, dieselbe Datei mit demselben Format unter iOS (unter Verwendung einer anderen Bibliothek) hochzuladen, aber es wurde erfolgreich hochgeladen.

Ich frage mich, wie ich ein Bild mit Retrofit 2.0 richtig hochladen kann .

Sollte ich es vor dem Hochladen zuerst auf der Festplatte speichern?

PS: Ich habe die Nachrüstung für andere mehrteilige Anforderungen verwendet, die kein Bild enthalten, und sie wurden erfolgreich abgeschlossen. Das Problem ist, wenn ich versuche, ein Byte in den Körper aufzunehmen.



Antworten:


180

Ich hebe die Lösung sowohl in 1.9 als auch in 2.0 hervor, da sie für einige nützlich ist

In 1.9, ich denke , die bessere Lösung ist es, die Datei auf der Festplatte zu speichern und als typisierte Datei verwenden , wie:

RetroFit 1.9

(Ich weiß nichts über Ihre serverseitige Implementierung) haben eine ähnliche API-Schnittstellenmethode

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

Und benutze es gerne

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

Für RetroFit 2 Verwenden Sie die folgende Methode

RetroFit 2.0 (Dies war eine Problemumgehung für ein Problem in RetroFit 2, das jetzt behoben ist. Die richtige Methode finden Sie in der Antwort von jimmy0251. )

API-Schnittstelle:

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

Verwenden Sie es wie:

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
Ja, ich denke, es ist ein Problem ( github.com/square/retrofit/issues/1063 ) mit Retrofit 2.0. Vielleicht möchten Sie bei 1.9 bleiben
Schlaflosigkeit

2
siehe meine Bearbeitung, ich habe es noch nicht versucht, Sie sind herzlich willkommen
Schlaflosigkeit

1
Ich habe erfolgreich ein Bild mit dem Beispiel de Retrofit 2.0 hochgeladen.
Jerogaren

3
@ Bhargav Sie können die Schnittstelle zu ändern @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);und wenn Sie die Datei haben: 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 Ja, ich habe gerade davon erfahren, kann auch verwendenMultiPartBody.Part
Bhargav

177

Es gibt eine korrekte Möglichkeit, eine Datei mit ihrem Namen mit Retrofit 2 ohne Hack hochzuladen :

API-Schnittstelle definieren:

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

Datei wie folgt hochladen:

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

Dies zeigt nur das Hochladen von Dateien. Sie können in derselben Methode auch andere Parameter mit @PartAnmerkungen hinzufügen .


2
Wie können wir mit MultipartBody.Part mehrere Dateien senden?
Praveen Sharma

Sie können mehrere MultipartBody.PartArgumente in derselben API verwenden.
Jimmy0251

Ich muss Bildersammlungen mit "image []" als Schlüssel senden. Ich habe versucht, @Part("images[]") List<MultipartBody.Part> imagesaber es gibt Fehler, dass@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma

Sie sollten @Body MultipartBody multipartBodyund MultipartBody.Builderzum Senden einer Sammlung von Bildern verwenden.
Jimmy0251

2
wie ich Schlüssel zu mutipart hinzufügen kann
andro

23

Ich habe Retrofit 2.0 für meine Registrierungsbenutzer verwendet und ein mehrteiliges / Formular-Dateibild und einen Text vom Registrierungskonto gesendet

Verwenden Sie in meiner RegisterActivity eine 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) {..}

In meiner Register.java-Klasse wird Retrofit mit synchronem Aufruf verwendet

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

In der Schnittstelle von 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
);
}

Für die Utilities analysieren Sie die InputStream-Antwort

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

Update-Code für das Hochladen von Bilddateien in 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);
}

Wechseln Sie MediaType.parse("image/*")zuMediaType.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();

    }
});

Ich habe viele Möglichkeiten versucht, dies zu tun, aber ich konnte die Ergebnisse nicht erhalten. Ich habe dies gerade geändert ("Change MediaType.parse (" image / * ") in MediaType.parse (" image / jpeg ")"), wie Sie gesagt haben, und es funktioniert jetzt, vielen Dank.
Gunnar

Ich wünschte, ich könnte Ihnen mehr als eine Stimme geben. Danke.
Rohit Maurya

wenn Ihr api hat @Multipartdann @Partmüssen Annotation einen Namen liefern oder MultipartBody.Part Parametertyp verwenden.
Rohit

Gute Lösung! Und es gibt noch ein Zitat in @Part ("profile_pic"; filename = "pp.png"), es sollte sein@Part("profile_pic\"; filename=\"pp.png "
Ninja

15

Hinzufügen zu der Antwort von @insomniac . Sie können ein erstellen Map, um den Parameter für das RequestBodyEinschließen des Bildes festzulegen.

Code für die Schnittstelle

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

Code für die Java-Klasse

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

Wie kann ich mehrere Dateien mit 2 Zeichenfolgen hochladen?
Jay Dangar


14

Es ist also eine sehr einfache Möglichkeit, Ihre Aufgabe zu erfüllen. Sie müssen den folgenden Schritt ausführen: -

1. Erster Schritt

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

Sie müssen den gesamten Anruf als tätigen @Multipart request. itemund image numberist nur ein String-Körper, der eingewickelt ist RequestBody. Wir verwenden das MultipartBody.Part class, mit dem wir den tatsächlichen Dateinamen neben den Binärdateidaten mit der Anfrage senden können

2. Zweiter Schritt

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

Jetzt haben Sie image pathund Sie müssen in file.Now konvertieren filein RequestBodyusing-Methode RequestBody.create(MediaType.parse("multipart/form-data"), file). Jetzt müssen Sie Ihre Methode RequestBody requestFilein MultipartBody.Partusing konvertieren MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumberund ItemIdsind meine anderen Daten, die ich an den Server senden muss, damit ich auch beides mache RequestBody.

Für mehr Information


3

Das Hochladen von Dateien mit Retrofit ist ganz einfach. Sie müssen Ihre API-Oberfläche als erstellen

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

}

in dem obigen Code Bild ist der Schlüssel Name also , wenn Sie PHP verwenden Sie schreiben , werden $ _FILES [ ‚Bild‘] [ ‚tmp_name‘] , dies zu erhalten. Und filename = "myfile.jpg" ist der Name Ihrer Datei, die mit der Anfrage gesendet wird.

Um die Datei hochzuladen, benötigen Sie eine Methode, die Ihnen den absoluten Pfad vom Uri gibt.

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

Jetzt können Sie den folgenden Code verwenden, um Ihre Datei hochzuladen.

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

Weitere Informationen finden Sie in diesem Tutorial zum Nachrüsten von Dateien .


Dies ist ein Hack, der seit einiger Zeit in Retrofit 2.0 behoben wurde. Siehe jimmy0251 Antwort unten.
Matt Wolfe

1

Kotlin-Version mit Update für den Entzug von RequestBody.create:

Nachrüstschnittstelle

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

und zum Hochladen

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

    })
}

Danke an @ jimmy0251


0

Verwenden Sie nicht mehrere Parameter im Funktionsnamen geht nur mit einfacher wenige args Konvention , die die Lesbarkeit von Codes erhöhen , denn dies kann man wie tun -

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

Es kann sein , mehrere Ausnahmen, die auftreten können , während mit Retrofit, die alle die Ausnahmen als Code dokumentiert , eine Komplettlösung zuretrofit2/RequestFactory.java . Sie können zwei Funktionen ausführenparseParameterAnnotation und parseMethodAnnotationwo Sie Ausnahmen auslösen können, gehen Sie dies bitte durch. Dies spart Ihnen viel Zeit als Googeln / Stackoverflow


0

In Kotlin ist es ganz einfach, Erweiterungsmethoden von toMediaType , asRequestBody und toRequestBody zu verwenden. Hier ein Beispiel:

Hier poste ich ein paar normale Felder zusammen mit einer PDF-Datei und einer Bilddatei mit mehreren Teilen

Dies ist eine API-Deklaration mit Nachrüstung:

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

und so nennt man es eigentlich:

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())
            )
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.