【问题标题】:Retrofit @body with @multipart having Issue使用 @multipart 改造 @body 有问题
【发布时间】:2019-11-19 22:06:32
【问题描述】:

类类型对象中的图像多部分。

案例1.(我做过)

服务参数:

{"id":"1","name":"vishal","image/file":""} 

当时我的APIRetrofit

@Multipart
@POST("webservice")
Call<SignUpResp> loadSignupMultipart(@Part("description") RequestBody description, @Part MultipartBody.Part file, @QueryMap HashMap<String, String> params);

案例 2.(我有问题的地方)@Body class&lt;UploadwithImage&gt;

{
    "methodName":"submitLevel1Part2Icon",
    "userid":"150",
    "headerData":{
        "fiction":{
            "icon_type":"1",
            "icon_id":"3"},
        "nonfiction":{
            "icon_type":"2",
            "icon_id":"4"},
        "relation":{
            "icon_type":"3",
            "icon_id":"0",
            "name":"Ronak",
            "relative_image":"<File>",
            "relation_id":"3"},
        "self":{
            "icon_type":"4",
            "icon_id":"0"}
    }
}

我正在尝试这个API

 @Multipart
 @POST("webservice")
 Call<SubmitLevel1Part2IconResp> loadLevel1halfIconswithImage(@Part("description") RequestBody description, @Part MultipartBody.Part file, @Body UploadwithImage uploadImage);

Java 端

    /**
     * code for multipart
     */
     // create RequestBody instance from file
     RequestBody requestFile =  RequestBody.create(MediaType.parse("multipart/form-data"), fileUpload);

     // MultipartBody.Part is used to send also the actual filename
     MultipartBody.Part body =  MultipartBody.Part.createFormData("methodName[headerData][relation][relative_image]", fileUpload.getName(), requestFile);

     // add another part within the multipart request
     String descriptionString = "hello, this is description speaking";
     RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);

    call = service.loadLevel1halfIconswithImage(description, body, levelOneHalfIcons);

我不知道为什么,但它返回如下错误:

"@Body 参数不能与表单或多部分编码一起使用"

我们将不胜感激。

【问题讨论】:

    标签: android annotations retrofit2


    【解决方案1】:

    改变你的方法

    @Multipart
    @POST("users/{id}/user_photos")
    Call<models.UploadResponse> uploadPhoto(@Path("id") int userId, @PartMap Map<String, RequestBody> params);
    

    现在创建您的请求参数,

    //All the String parameters, you have to put like
    Map<String, RequestBody> map = new HashMap<>();
    map.put("methodName", toRequestBody(methodName));
    map.put("userid", toRequestBody(userId));
    map.put("relation", toRequestBody(relation));
    map.put("icon_type", toRequestBody(iconType));
    map.put("icon_id", toRequestBody(iconId));
    map.put("name", toRequestBody(name));
    map.put("relation_id", toRequestBody(relationId));
    
    //To put your image file you have to do
    File file = new File("file_name");
    RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
    map.put("relative_image\"; filename=\"some_file_name.png\"", fileBody);
    
    // This method  converts String to RequestBody
    public static RequestBody toRequestBody (String value) {
        RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value);
        return body ;
    }
    
    //To send your request
    call = service.loadLevel1halfIconswithImage(description, params);
    

    如果您不想使用 PartMap,您可以简单地将它们作为参数传递。检查我的回答 https://stackoverflow.com/a/37052548/1320616 以获取有关发送带有请求的图像文件的一些线索。

    【讨论】:

    • 嗨,我有更新我的问题,对不起,我之前没有写完整的 json 请求,你能指导我吗。
    • 我不确定,但据我所知,我认为这是不可能的。多部分请求总是作为表单数据而不是 requestBody。在这里,您尝试在 json 中发送文件。您只考虑是否可以将文件附加到json。我认为您甚至不能在邮递员或其他休息客户端中复制此 api 请求。
    【解决方案2】:

    简单来说,我是这样做的:

    我有tested通过改变

    Call<Result> resultCall = service.uploadImage(body); 
    

    Call&lt;Result&gt; resultCall = service.uploadImage(body, result); 其中结果

    我的 API 的

    Result.java 类(响应):

    public class Result {
    
        @SerializedName("result")
        @Expose
        private String result;
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        @SerializedName("value")
        @Expose
        private String value;
    
        /**
         * @return The result
         */
        public String getResult() {
            return result;
        }
    
        /**
         * @param result The result
         */
        public void setResult(String result) {
            this.result = result;
        }
    
    }
    

    并创建了类似的对象:

    Result result = new Result();
    result.setResult("success");
    result.setValue("my value");
    

    您可以根据需要更改班级,然后在发送请求时传递对象。所以你的 ApiService 类将是:

    ApiService.java

    /**
     * @author Pratik Butani on 23/4/16.
     */
    public interface ApiService {
    
        /*
        Retrofit get annotation with our URL
        And our method that will return us the List of Contacts
        */
        @Multipart
        @POST("upload.php")
        Call<Result> uploadImage(@Part MultipartBody.Part file, @Part("result") Result result);
    
    }
    

    我的 PHP 代码是:

    <?php
    
        $file_path = "";
        $var = $_POST['result']; //here I m getting JSON
    
        $file_path = $file_path . basename( $_FILES['uploaded_file']['name']);
        if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
            $result = array("result" => "success", "value" => $var);
        } else{
            $result = array("result" => "error");
        }
    
        echo json_encode($result);
    
    ?>
    

    希望对您有所帮助。谢谢。

    【讨论】:

    • 谢谢,这是处理这种情况的好方法。 +1
    • 一切都很好。添加@Part MultipartBody.Part file 时出现问题。它抛出Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $。注意:我正在使用自定义 converterFactory(通过实施 Gson)。会不会有问题?
    • @FARID 在这两种情况下检查您的响应,可能您从服务器端收到错误。
    • @PratikButani 是的,谢谢,这就是问题所在。已解决)
    • 还有一个问题。您是否知道将@part 对象(在您的情况下为结果对象)作为 JSON 而不是 POST 参数发送的任何方法?
    【解决方案3】:

    您还可以使用MapRequestBody 作为值和字符串作为键来添加参数,您可以使用Multipart 和PartMap 发送它。

    检查以下代码,希望对您有所帮助:

    @Multipart
    @POST("/add")
    Call<ResponseBody> addDocument(@PartMap Map<String,RequestBody> params);
    
    Map<String, RequestBody> map = new HashMap<>();
    
    map.put("user_id", RequestBody.create(MediaType.parse("multipart/form-data"), SessionManager.getInstance().getCurrentUserId()));
    map.put("doc_name", RequestBody.create(MediaType.parse("multipart/form-data"), CommonUtils.removeExtension(textFile.getName())));
    map.put("doc_category", RequestBody.create(MediaType.parse("multipart/form-data"), category));
    map.put("doc_image_file", RequestBody.create(MediaType.parse("multipart/form-data"), imageFile));
    map.put("doc_text_content", RequestBody.create(MediaType.parse("multipart/form-data"), body));
    map.put("doc_update_time", RequestBody.create(MediaType.parse("multipart/form-data"), "" + new Date(textFile.lastModified())));
    

    【讨论】:

    • 在 kotlin 中执行此操作` Map`
    • 图片推送不正确
    【解决方案4】:

    我们可以在具有指定类型的多部分车身生成器中添加所有请求参数,如下面的一个图像文件。我已经设置了媒体类型解析multipart/form-data 和一些其他参数我已经设置了媒体类型解析text/plain。该构建器将构建用于制作 Multipart Body 并可以通过在 multipart body 中使用 body annotation 发送。

    @Multipart
    @POST("user/update")
    Call<ResponseBody> addDocument(@@Part MultipartBody file);
    
    
    final MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder()
          .setType(MultipartBody.FORM);
    
    requestBodyBuilder.addFormDataPart("doc_image_file", imageFile.getName(),
          RequestBody.create(MediaType.parse("multipart/form-data"), imageFile));
    requestBodyBuilder.addFormDataPart("user_id", null, RequestBody.create(MediaType.parse("text/plain"),"12"));
    requestBodyBuilder.addFormDataPart("doc_name", null, RequestBody.create(MediaType.parse("text/plain"),"myfile"));
    requestBodyBuilder.addFormDataPart("doc_category", null, RequestBody.create(MediaType.parse("text/plain"),category));
    requestBodyBuilder.addFormDataPart("doc_image_file", imageFile.getName(),RequestBody.create(MediaType.parse("multipart/form-data"),imageFile));
    requestBodyBuilder.addFormDataPart("doc_text_content", null, RequestBody.create(MediaType.parse("text/plain"),body));
    RequestBody multipartBody = requestBodyBuilder.build();
    

    【讨论】:

    • RequestBody multipartBody = requestBodyBuilder.build();实际上这一行 MultpartBdy 或 requestBdy
    【解决方案5】:
    Here is my json request format is :
    {
    "task":{
    "category_id":"1",
    "price":"10",
    "description":"1",
    "task_videos_attributes":[
    {
    "link":"video file goes here",
    "size":"100x100"
    }
    ]
    }
    }
    
    
    
    // my request becomes 
     HashMap<String, RequestBody> task = new HashMap();          
      task.put("task[category_id]", createPartFromString(categoryId));
      task.put("task[price]", createPartFromString("" + etPrice.getText().toString()));
                task.put("task[description]", createPartFromString("" + etDescription.getText().toString()));
    
    
    // for videos file list
      final List<MultipartBody.Part> body = new ArrayList<>();
      for (int i = 0; i < videos.size(); i++) {
    
     task.put("task[task_videos_attributes][" + i+ "][size]", createPartFromString("100x100"));
    
     File videoFile = new File(videos.get(i));
                            RequestBody requestBody = RequestBody.create(MediaType.parse("video/mp4"), videoFile);
                            MultipartBody.Part fileToUpload = MultipartBody.Part.createFormData("task[task_videos_attributes][" + i + "][link]", videoFile.getName(), requestBody);
                            body.add(fileToUpload);
    
    }
    
    
    // here is a final call
      new RestClient(this).getInstance().get().postTask(body, task).enqueue(callback);
    
    
    
    
    // This function converts my string to request body
       @NonNull
        private RequestBody createPartFromString(String descriptionString) {
            if (descriptionString == null)
                return RequestBody.create(MultipartBody.FORM, "");
            return RequestBody.create(
                    MultipartBody.FORM, descriptionString);
    
        }
    

    希望对你有帮助...

    【讨论】:

    【解决方案6】:

    只需跟随网络浏览器如何进行多部分。他们将嵌套键放在“[]”中,并为图像提供键。

    Call<SubmitLevel1Part2IconResp> loadLevel1halfIconswithImage(@Part("headerdata[relation][icon_type]") RequestBody icon_type, @Part("headerdata[relation][name]") RequestBody name, @Part MultipartBody.Part file);
    

    然后在java中

     // MultipartBody.Part is used to send also the actual filename
     MultipartBody.Part body =  MultipartBody.Part.createFormData("headerdata[relation][relative_image]", fileUpload.getName(), requestFile);
    
    
    
    call = service.loadLevel1halfIconswithImage(icon_type, name, body);
    

    【讨论】:

      【解决方案7】:

      https://www.linkedin.com/pulse/retrofit-2-how-upload-multiple-files-server-mahesh-gawale

      我想这个问题的最佳答案可以在这里找到。它对我来说非常有效。

      这是在 Android 中使用改造上传文件数组的示例。

      这就是服务的样子

      public interface ApiService {
      
          @POST("/event/store")
          Call<ResModel> event_store(@Body RequestBody file);
      }
      

      这是 Client 类的样子

      public class ApiClient   {
          public static final String API_BASE_URL = "api base url";
      
          private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
      
          private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create());
      
          public static ApiService createService(Class<ApiService> serviceClass)
          {
              Retrofit retrofit = builder.client(httpClient.build()).build();
              return retrofit.create(serviceClass);
          }
      }
      

      在活动或片段或您想要的地方上传这样的内容

          ApiService service = ApiClient.createService(ApiService.class);
      
          MultipartBody.Builder builder = new MultipartBody.Builder();
          builder.setType(MultipartBody.FORM);
      
      
          builder.addFormDataPart("event_name", "xyz");
          builder.addFormDataPart("desc", "Lorem ipsum");
      
          // Single Image
          builder.addFormDataPart("files",file1.getName(),RequestBody.create(MediaType.parse("image/*"), file1));
      
          // Multiple Images 
          for (int i = 0; i <filePaths.size() ; i++) {
                  File file = new File(filePaths.get(i));
                  RequestBody requestImage = RequestBody.create(MediaType.parse("multipart/form-data"), file);
                  builder.addFormDataPart("event_images[]", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
                  }
      
      
          MultipartBody requestBody = builder.build();
          Call<ResModel> call = service.event_store(requestBody);
          call.enqueue(new Callback<ResponseBody>() {
               @Override
               public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                   Toast.makeText(getBaseContext(),"All fine",Toast.LENGTH_SHORT).show();
               }
      
               @Override
               public void onFailure(Call<ResponseBody> call, Throwable t) {
                  Toast.makeText(getBaseContext(),t.getMessage(),Toast.LENGTH_SHORT).show();
               }
           });
      

      注意:filePaths.size() 是拾取图像路径的 Arraylist。 我希望这篇文章对你有用。请在此处以评论的形式分享您的反馈。

      【讨论】:

        【解决方案8】:

        这对我有用。

        我所做的是添加每个额外的参数:

        MultipartBody.Part Partname = MultipartBody.Part.createFormData("ParamName", "Value");
        

        也许您不需要创建另一个主体,只需添加文件之外的其他参数或您发送的任何内容。最后在界面上,我把我需要的每个身体部位都作为参数。

        @Multipart
        @POST("api/service/uploadVideo")
        Call<ResponseBody> uploadVideoToServer(
                @Part MultipartBody.Part video,
                @Part MultipartBody.Part param2,
                @Part MultipartBody.Part param3 ....
        );
        

        【讨论】:

          猜你喜欢
          • 2021-06-04
          • 1970-01-01
          • 2014-05-12
          • 2016-01-05
          • 1970-01-01
          • 2021-11-13
          • 1970-01-01
          • 2017-09-06
          • 2018-02-17
          相关资源
          最近更新 更多