使用OkHttp进行多部分上传大文件

36

我该如何在Android中使用OKhttp对单个大文件(更具体地说,是上传到s3)进行分块上传?


2
请问您能否更新使用的代码以解决这个问题? - StErMi
1
你也可以关注这个链接:https://dev59.com/2Yvda4cB1Zd3GeqPUAva#34037063 - Pratik Butani
1
这些解决方案都无法处理“大”文件。请删除它们,以便记录一个真正的解决方案。 - behelit
5个回答

43

获取OkHttp 2.1,并使用MultipartBuilder.addFormDataPart(),该方法接受文件名作为参数。

       /**
         * Upload Image
         *
         * @param memberId
         * @param sourceImageFile
         * @return
         */
        public static JSONObject uploadImage(String memberId, String sourceImageFile) {
    
            try {
                File sourceFile = new File(sourceImageFile);
    
                Log.d(TAG, "File...::::" + sourceFile + " : " + sourceFile.exists());
             //Determining the media type        
             final MediaType MEDIA_TYPE = sourceImageFile.endsWith("png") ? 

                MediaType.parse("image/png") : MediaType.parse("image/jpeg");
                
    
                RequestBody requestBody = new MultipartBuilder()
                        .type(MultipartBuilder.FORM)
                        .addFormDataPart("member_id", memberId)
                        .addFormDataPart("file", "profile.png", RequestBody.create(MEDIA_TYPE, sourceFile))
                        .build();
    
                Request request = new Request.Builder()
                        .url(URL_UPLOAD_IMAGE)
                        .post(requestBody)
                        .build();
    
                OkHttpClient client = new OkHttpClient();
                Response response = client.newCall(request).execute();
                return new JSONObject(response.body().string());
    
            } catch (UnknownHostException | UnsupportedEncodingException e) {
                Log.e(TAG, "Error: " + e.getLocalizedMessage());
            } catch (Exception e) {
                Log.e(TAG, "Other Error: " + e.getLocalizedMessage());
            }
            return null;
        }

#为okhttp3进行了编辑:

compile 'com.squareup.okhttp3:okhttp:3.4.1'

RequestBody被替换为:

RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("uploaded_file", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
                    .addFormDataPart("result", "my_image")
                    .build();

#已上传演示,请查看GITHUB: ##我已回答多图上传的问题 :)


@droid_dev 它返回 JSONObject 类型的返回值。请检查它。 - Pratik Butani
我正在使用enqueue方法而不是execute。我们可以通过multipart发送多个图像吗? - moDev
我得到了我的问题的答案。 - Pratik Butani
1
@PratikButani .addFormDataPart("result", "my_image") 是什么意思?! - Dr.jacky
1
@priyankasinghal 更改 MediaType.parse("application/pdf") - Pratik Butani
显示剩余8条评论

40

OkHttp Recipes 页面,这段代码将图像上传到 Imgur:

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
  // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
  RequestBody requestBody = new MultipartBuilder()
      .type(MultipartBuilder.FORM)
      .addPart(
          Headers.of("Content-Disposition", "form-data; name=\"title\""),
          RequestBody.create(null, "Square Logo"))
      .addPart(
          Headers.of("Content-Disposition", "form-data; name=\"image\""),
          RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
      .build();

  Request request = new Request.Builder()
      .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
      .url("https://api.imgur.com/3/image")
      .post(requestBody)
      .build();

  Response response = client.newCall(request).execute();
  if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

  System.out.println(response.body().string());
}

你需要将其适应于S3,但你所需的类应该是相同的。


6
你可以创建一个自定义的 RequestBody 来跟踪字节移动。你需要包装传递给 RequestBody 的 Sink。 - Jesse Wilson
1
Tomer,这里是我的答案,请查看:https://dev59.com/FF8e5IYBdhLWcg3wNYUn#26376724 - Eduard B.
1
我已经弄清楚了。使用imgur时,您不必提供文件名,因此像我在上面的评论中所做的那样发送图像字节是有效的。但是,对于我实际交互的API,它需要一个文件名,因此这个方法可行:multipartBuilder.addFormDataPart("image", fileName, entry.getValue()); - shoke
5
现在MultiPartBuilder被称为MultipartBody.Builder,请参见:https://dev59.com/-FsW5IYBdhLWcg3wspAF - Warpzit
1
链接到食谱页面已经失效。这是新链接:https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PostMultipart.java - Jatin
显示剩余7条评论

7

对于 okhttp 4.* 版本,请使用 MultipartBody.Builder

fun postMultipart(url: String, text: String, imagePath: String, imageFileName: String): okhttp3.Response? {
    val file = File(imagePath)
    val fileRequestBody = file.asRequestBody("image/jpeg".toMediaType())
    val requestBody = MultipartBody.Builder()
        .addFormDataPart("text", text)
        .addFormDataPart("image", imageFileName, fileRequestBody)
        .build()

    val request = getRequestBuilder(url)
        .post(requestBody)
        .build()

    val client = OkHttpClient()
    client.newCall(request).execute().use { response ->
        return response
    }
}

“Multipart” 是使上传大文件成为可能的组件吗?我正在使用 “RequestBody”,但似乎无法上传非常大的文件。 - abhiarora
处理大文件时,请使用Okio库。我在这里提供了一个示例 https://dev59.com/KGAf5IYBdhLWcg3wyVJ1#68481022 - gmanjon

2

对于OkHttp 2.6.0版本,

    try {
        File file = new File(Environment.getExternalStorageDirectory().getPath()+"/xxx/share/" + "ic_launcher.png");
        String contentType = file.toURL().openConnection().getContentType();
        RequestBody fileBody = RequestBody.create(MediaType.parse(contentType), file);

        RequestBody requestBody = new MultipartBuilder()
                .type(MultipartBuilder.FORM)
                .addFormDataPart("fileUploadType","1")
                .addFormDataPart("miniType",contentType)
                .addFormDataPart("ext",file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))
                .addFormDataPart("fileTypeName","img")
                .addFormDataPart("Filedata","ss.png",fileBody)
                .build();
        Request request = new Request.Builder()
                .url(Contains.MULTIPARTY_POST)
                .post(requestBody)
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvGetNews.setText("upload fail");
                    }
                });
            }

            @Override
            public void onResponse(Response response) throws IOException {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvGetNews.setText("upload success");
                    }
                });
            }
        });
    } catch (IOException e) {
        e.printStackTrace();
    }

}

“Multipart” 是使上传大文件成为可能的组件吗?我正在使用 “RequestBody”,但似乎无法上传非常大的文件。 - abhiarora
为了处理大文件和 RequestBody,请使用 Okio 库。我在这里提供了一个示例 https://dev59.com/KGAf5IYBdhLWcg3wyVJ1#68481022 - gmanjon

1
在Android中,通常使用Uri。当使用大文件时,如果尝试将整个流读取到字节数组(即内存中的所有内容),容易遇到OutOfMemoryError,或者会创建无用的带有Uri流的文件。这是因为RequestBody不支持从Stream(因为有时需要多次读取它,例如如果您获得30X重定向)或Uri(因为OkHttp不是一个Android库)来创建。
但是,OkHttp提供了库Okio,其中包含方便的类,模拟流(SourceSink)以及更方便的内部使用。
因此,要创建一个BodyRequest,从Uri中避免任何由于大文件而导致的OutOfMemoryError,请按照以下方式创建:
private static final MediaType MULTIPART_FOR_DATA = MediaType.parse("multipart/form-data");

private @NotNull RequestBody getFilePart(Uri largeFileUri) {

        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return MULTIPART_FOR_DATA;
            }

            @Override
            public void writeTo(@NotNull BufferedSink sink) throws IOException {
                try (Source source = Okio.source(context.getContentResolver().openInputStream(mediaUri))) {
                    sink.writeAll(source);
                }
            }
        };
    }

感谢所有在以下GitHub线程https://github.com/square/okhttp/issues/3585中发帖和评论的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接