【问题标题】:POST Multipart Form Data using Retrofit 2.0 including image使用 Retrofit 2.0 发布多部分表单数据,包括图像
【发布时间】:2016-04-06 09:37:33
【问题描述】:

我正在尝试使用 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 上传图片的正确方法是什么?

我应该在上传之前先将其保存到磁盘吗?

P.S.:我已经对不包含图像的其他 Multipart 请求进行了改造,并且他们成功完成了。问题是当我试图在正文中包含一个字节时。

【问题讨论】:

标签: android retrofit androidhttpclient retrofit2


【解决方案1】:

使用Retrofit 2上传文件名的正确方法,无需任何hack

定义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注解在同一方法中添加其他参数。

【讨论】:

  • 我们如何使用 MultipartBody.Part 发送多个文件?
  • 您可以在同一个 API 中使用多个 MultipartBody.Part 参数。
  • 我需要发送以“image[]”为键的图像集合。我试过@Part("images[]") List&lt;MultipartBody.Part&gt; images 但它给出的错误是@Part parameters using the MultipartBody.Part must not include a part name
  • 如何将密钥添加到多部分
  • @jimmy0251 可以帮助我解决这个问题 @Part("channel[comment][image]") 。我必须在这个channel[comment][image] 键上上传图片。我该怎么做。
【解决方案2】:

我正在强调 1.9 和 2.0 中的解决方案,因为它对某些人有用

1.9,我认为更好的解决方案是将文件保存到磁盘并将其用作类型文件,例如:

RetroFit 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 中 issue 的解决方法,现已修复,正确方法请参阅 jimmy0251's answer

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

【讨论】:

  • 是的,我认为这是改造 2.0 的问题 (github.com/square/retrofit/issues/1063),你可能想坚持使用 1.9
  • 看我的编辑,我还没试过,欢迎你
  • 我已经使用 de Retrofit 2.0 示例成功上传了一张图片。
  • @Bhargav 您可以将界面更改为@Multipart @POST("/api/Accounts/editaccount") Call&lt;User&gt; editUser(@PartMap Map&lt;String, RequestBody&gt; params); 并且当您拥有文件时:Map&lt;String, RequestBody&gt; map = new HashMap&lt;&gt;(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
  • @insomniac 是的,我刚知道,也可以使用MultiPartBody.Part
【解决方案3】:

我为我的注册用户使用 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 响应的 Utilities 解析

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

【讨论】:

    【解决方案4】:

    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")") 改了,现在可以用了,非常感谢。
    • 希望我能给你多张选票,谢谢。
    • 如果你的api有@Multipart,那么@Part注解必须提供一个名字或者使用MultipartBody.Part参数类型。
    • 很好的解决方案! @Part("profile_pic\"; filename=\"pp.png\" " 中还有一个引用,应该是@Part("profile_pic\"; filename=\"pp.png "
    【解决方案5】:

    所以这是完成任务的非常简单的方法。您需要按照以下步骤操作:-

    1.第一步

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

    您需要以@Multipart request 进行整个通话。 itemimage 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。现在使用RequestBody.create(MediaType.parse("multipart/form-data"), file)方法将file转换成RequestBody。现在您需要使用方法 MultipartBody.Part.createFormData("Image", file.getName(), requestBody); 将您的 RequestBody requestFile 转换为 MultipartBody.Part

    ImageNumberItemId 是我需要发送到服务器的另一个数据,所以我也将这两个数据都变成了RequestBody

    For more info

    【讨论】:

      【解决方案6】:

      补充@insomniac 给出的答案。您可以创建一个Map 来放置RequestBody 的参数,包括图像。

      接口代码

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

      【讨论】:

      【解决方案7】:

      在 kotlin 中非常简单,使用 toMediaTypeasRequestBodytoRequestBody这是一个例子:

      我在这里发布了几个普通字段以及一个 pdf 文件和一个使用 multipart 的图像文件

      这是使用改造的 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())
                  )
      

      【讨论】:

        【解决方案8】:

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

        在上面的代码中,image 是键名,所以如果你使用 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

        【讨论】:

        • 这是一个 hack,它已在改造 2.0 中修复了一段时间。请参阅下面的 jimmy0251 答案。
        【解决方案9】:

        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

        【讨论】:

          【解决方案10】:
          * Return MultipartBody from file path
          
           public static MultipartBody.Part generateFileBody(String imagePath)
              {
                  File file = new File(imagePath);
                  RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
                  return MultipartBody.Part.createFormData("mediaProfilePic", file.getName(), requestFile);
              }
          

          【讨论】:

            【解决方案11】:

            不要在函数名中使用多个参数 只需使用简单的几个参数约定,这将增加代码的可读性,为此你可以这样做 -

            // 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 可以抛出异常,请通过此操作,这将比 googling/stackoverflow

            节省大量时间

            【讨论】:

              【解决方案12】:

              requestBody 可以用来上传

                val body: RequestBody = Builder().setType(MultipartBody.FORM)
                      .addFormDataPart(
                          "file", "<image name you wish to give>",
                          RequestBody.create(
                              MediaType.parse("application/octet-stream"),
                              File(path)
                          )
                      )
                      .build()
              uploadProfilePhoto(body)
              

              然后像这样调用:

                 @POST("/**")
                  suspend fun uploadProfilePhoto(
                      @Body body: RequestBody,
                  ): ResponseBody
              }
              

              【讨论】:

              • 生成器 by okhttp3.MultipartBody.Builder() ??
              【解决方案13】:

              就我而言,我需要发送一个 PDF 文件 (application/pdf) 以及 JSON 信息 (application/json)。值得庆幸的是,Retrofit2 让这变得超级简单。

              我的界面如下:

              interface MyApi {
                  @Multipart
                  @POST("upload")
                  fun uploadPDF(
                      @Part file: MultipartBody.Part,
                      @Part(value = "jsoninfo") jsoninfo: MyJsonObject
                  ): Call<MyResponse>
              }
              

              jsoninfo 是我的 JSON 数据的名称,MyJsonObject 是我的数据类,MyResponse 是我期待的响应,当然。

              然后,我只是调用我的 API 方法如下:

              val myJsonObject = MyJsonObject(...)
              
              // "file" is of type byte[] already
              val requestBody = RequestBody.create(file, MediaType.parse("application/pdf"))
              val filePart = MultipartBody.Part.createFormData("file", "myfile.pdf", requestBody)
              
              api.uploadPDF(filePart, myJsonObject).enqueue(...)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2017-11-01
                • 2015-06-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-03-25
                相关资源
                最近更新 更多