POST Dữ liệu biểu mẫu đa dữ liệu bằng cách sử dụng Retrofit 2.0 bao gồm hình ảnh


148

Tôi đang cố gắng thực hiện HTTP POST đến máy chủ bằng 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();

Máy chủ trả về lỗi cho biết tệp không hợp lệ.

Điều này thật kỳ lạ vì tôi đã cố tải lên cùng một tệp có cùng định dạng trên iOS (sử dụng thư viện khác), nhưng nó tải lên thành công.

Tôi tự hỏi cách thích hợp để tải lên hình ảnh bằng Retrofit 2.0 là gì?

Tôi có nên lưu nó vào đĩa trước khi tải lên không?

PS: Tôi đã sử dụng trang bị thêm cho yêu cầu Đa mục khác không bao gồm hình ảnh và chúng đã hoàn thành thành công. Vấn đề là khi tôi đang cố gắng đưa một byte vào cơ thể.



Câu trả lời:


180

Tôi nhấn mạnh giải pháp trong cả 1.9 và 2.0 vì nó hữu ích cho một số

Trong 1.9, tôi nghĩ giải pháp tốt hơn là lưu tệp vào đĩa và sử dụng nó dưới dạng tệp đã nhập như:

RetroFit 1.9

(Tôi không biết về việc triển khai phía máy chủ của bạn) có phương thức giao diện API tương tự như thế này

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

Và sử dụng nó như

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

Đối với RetroFit 2 Sử dụng phương pháp sau

RetroFit 2.0 (Đây là một cách giải quyết cho một vấn đề trong RetroFit 2 hiện đã được khắc phục, đối với phương pháp chính xác, hãy tham khảo câu trả lời của jimmy0251 )

Giao diện 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);
}

Sử dụng nó như:

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
Vâng, tôi nghĩ đó là một vấn đề ( github.com/sapes/retrofit/issues/1063 ) với trang bị thêm 2.0, bạn có thể muốn gắn bó với 1.9
mất ngủ

2
xem bản chỉnh sửa của tôi, tôi chưa thử nó, bạn được chào đón
mất ngủ

1
Tôi đã tải lên thành công một hình ảnh bằng ví dụ de Retrofit 2.0.
jerogaren

3
@Bhargav Bạn có thể thay đổi giao diện thành @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);Và Khi bạn có tệp: Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
mất ngủ

2
@insomniac Có tôi mới phát hiện ra điều đó, cũng có thể sử dụngMultiPartBody.Part
Bhargav

177

Đây là một cách chính xác để tải lên một tệp có tên của nó với Retrofit 2 , mà không cần bất kỳ hack nào :

Xác định giao diện API:

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

Tải lên tệp như thế này:

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

Điều này chỉ thể hiện việc tải lên tệp, bạn cũng có thể thêm các tham số khác trong cùng phương thức với @Partchú thích.


2
Làm cách nào chúng tôi có thể gửi nhiều tệp bằng MultipartBody.Part?
Praveen Sharma

Bạn có thể sử dụng nhiều MultipartBody.Partđối số trong cùng một API.
jimmy0251

Tôi cần gửi bộ sưu tập hình ảnh với "hình ảnh []" làm khóa. Tôi đã thử @Part("images[]") List<MultipartBody.Part> imagesnhưng nó báo lỗi rằng@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma

Bạn nên sử dụng @Body MultipartBody multipartBodyMultipartBody.Builderđể gửi bộ sưu tập hình ảnh.
jimmy0251

2
làm thế nào tôi có thể thêm chìa khóa để mutipart
Andro

23

Tôi đã sử dụng Retrofit 2.0 cho người dùng đăng ký của mình, gửi nhiều tệp / biểu mẫu Ảnh và văn bản từ tài khoản đăng ký

Trong RegisterActivity của tôi, hãy sử dụng 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) {..}

Và trong lớp Register.java của tôi là nơi sử dụng Retrofit với cuộc gọi đồng bộ

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

Trong giao diện của 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
);
}

Đối với phân tích tiện ích của phản hồi 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

Cập nhật mã để tải lên tệp hình ảnh trong 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);
}

Thay đổi MediaType.parse("image/*")thànhMediaType.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();

    }
});

Tôi đã thử nhiều cách để làm điều này nhưng tôi không thể nhận được kết quả. Tôi vừa thay đổi điều này ("Thay đổi MediaType.parse (" image / * ") thành MediaType.parse (" image / jpeg ")") như bạn đã nói và nó hoạt động ngay bây giờ, cảm ơn bạn rất nhiều.
Gunnar

ước gì tôi có thể cho bạn nhiều hơn một phiếu, Cảm ơn bạn.
Rohit Maurya

nếu api của bạn có chú thích @Multipartthì @Partphải cung cấp tên hoặc sử dụng loại tham số MultipartBody.Part.
Rohit

Giải pháp tốt! Và có thêm một trích dẫn trong @Part ("profile_pic \"; filename = \ "pp.png \" ", nó sẽ là@Part("profile_pic\"; filename=\"pp.png "
Ninja

15

Thêm vào câu trả lời được đưa ra bởi @insomniac . Bạn có thể tạo một Mapđể đặt tham số RequestBodybao gồm hình ảnh.

Mã cho giao diện

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

Mã cho lớp 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();
 }
});

Làm thế nào tôi có thể tải lên nhiều tập tin với 2 chuỗi?
Jay Dangar


14

Vì vậy, cách rất đơn giản để đạt được nhiệm vụ của bạn. Bạn cần làm theo bước dưới đây: -

1. Bước đầu tiên

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

Bạn cần phải thực hiện toàn bộ cuộc gọi như @Multipart request. itemimage numberchỉ là cơ thể chuỗi được bọc trong RequestBody. Chúng tôi sử dụngMultipartBody.Part class cho phép chúng tôi gửi tên tệp thực tế bên cạnh dữ liệu tệp nhị phân với yêu cầu

2. Bước thứ hai

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

Bây giờ bạn có image pathvà bạn cần chuyển đổi thành file. Bây giờ chuyển đổi filethành RequestBodyphương thức sử dụng RequestBody.create(MediaType.parse("multipart/form-data"), file). Bây giờ bạn cần chuyển đổi RequestBody requestFilethành MultipartBody.Partphương thức sử dụngMultipartBody.Part.createFormData("Image", file.getName(), requestBody); .

ImageNumberItemIdlà một dữ liệu khác của tôi mà tôi cần gửi đến máy chủ vì vậy tôi cũng đưa cả hai vào RequestBody.

Để biết thêm thông tin


3

Tải lên tệp bằng Retrofit khá đơn giản Bạn cần xây dựng giao diện api của mình như

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

}

trong hình ảnh mã ở trên là tên khóa, vì vậy nếu bạn đang sử dụng php, bạn sẽ viết $ _FILES ['image'] ['tmp_name'] để có được điều này. Và tên tệp = "myfile.jpg" là tên tệp của bạn đang được gửi cùng với yêu cầu.

Bây giờ để tải lên tệp bạn cần một phương pháp sẽ cung cấp cho bạn đường dẫn tuyệt đối từ 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;
}

Bây giờ bạn có thể sử dụng mã dưới đây để tải lên tập tin của bạn.

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

Để được giải thích chi tiết hơn, bạn có thể truy cập Hướng dẫn tệp tải lên trang bị thêm này .


Đây là một bản hack, nó đã được sửa trong bản nâng cấp 2.0 trong một thời gian. Xem câu trả lời jimmy0251 dưới đây.
Matt Wolfe

1

Phiên bản Kotlin với bản cập nhật cho việc mô tả RequestBody.create:

Giao diện trang bị thêm

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

và để tải lên

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

    })
}

Cảm ơn @ jimmy0251


0

Không sử dụng nhiều tham số trong tên hàm chỉ bằng một vài quy ước đơn giản sẽ tăng khả năng đọc mã , vì điều này bạn có thể làm như thế -

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

Có thể có nhiều ngoại lệ mà bạn có thể gặp phải trong khi sử dụng Retrofit, tất cả các ngoại lệ được ghi lại dưới dạng mã , đều có hướng dẫnretrofit2/RequestFactory.java . bạn có thể có hai chức năng parseParameterAnnotationparseMethodAnnotationnơi bạn có thể ngoại lệ bị ném, xin vui lòng thực hiện điều này, nó sẽ tiết kiệm nhiều thời gian của bạn hơn so với googling / stackoverflow


0

trong kotlin khá dễ dàng, sử dụng các phương thức mở rộng của toMediaType , asRequestBodytoRequestBody đây là một ví dụ:

Ở đây tôi đang đăng một vài trường bình thường cùng với tệp pdf và tệp hình ảnh bằng cách sử dụng nhiều phần

đây là khai báo API bằng cách sử dụng thêm:

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

và đây là cách thực sự gọi nó:

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())
            )
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.