使用OkHttp从资源上传二进制文件

24

我需要使用OkHttp将打包在APK中的二进制文件上传到服务器。使用URLConnection,您可以简单地获取到一个资产的InputStream,然后将其放入请求中。然而,OkHttp只给出了上传字节数组、字符串或文件的选项。由于无法获取APK中捆绑的资产的文件路径,唯一的选择是将文件复制到本地文件目录(我宁愿不这样做),然后将文件提供给OkHttp吗?难道没有直接使用AssetInputStream向Web服务器发出请求的方法吗?

编辑:我使用了被接受的答案,但是我没有创建一个静态的实用程序类,而是子类化了RequestBody。

 public class InputStreamRequestBody extends RequestBody {

private InputStream inputStream;
private MediaType mediaType;

public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {


    return new InputStreamRequestBody(inputStream, mediaType);
}

private InputStreamRequestBody(InputStream inputStream, MediaType mediaType) {
    this.inputStream = inputStream;
    this.mediaType = mediaType;
}

@Override
public MediaType contentType() {
    return mediaType;
}

@Override
public long contentLength() {
    try {
        return inputStream.available();
    } catch (IOException e) {
        return 0;
    }
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
    Source source = null;
    try {
        source = Okio.source(inputStream);
        sink.writeAll(source);
    } finally {
        Util.closeQuietly(source);
    }
 }
}

我对这种方法唯一的担忧是inputstream.available()的不可靠性,这个静态构造函数是为了与okhttp的内部实现相匹配。


你可以像你说的那样将内容放入一个字节数组中。 - greenapps
这是我所做的。不过,我仍然更喜欢直接从资产中获取输入流的解决方案。 - dabluck
3个回答

45

你可能无法直接使用该库来完成此操作,但是你可以创建一个小型实用程序类,它可以为你完成这个操作。然后,你可以在需要的任何地方简单地重新使用它。

public class RequestBodyUtil {

    public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return mediaType;
            }

            @Override
            public long contentLength() {
                try {
                    return inputStream.available();
                } catch (IOException e) {
                    return 0;
                }
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                Source source = null;
                try {
                    source = Okio.source(inputStream);
                    sink.writeAll(source);
                } finally {
                    Util.closeQuietly(source);
                }
            }
        };
    }
}

然后就可以像这样使用它

OkHttpClient client = new OkHttpClient();

MediaType MEDIA_TYPE_MARKDOWN
        = MediaType.parse("text/x-markdown; charset=utf-8");

InputStream inputStream = getAssets().open("README.md");

RequestBody requestBody = RequestBodyUtil.create(MEDIA_TYPE_MARKDOWN, inputStream);
Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

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

Log.d("POST", response.body().string());    

这个示例代码基于这份代码。请用您自己的Assets文件名和MediaType替换它。


2
你是否曾经在使用RequestBody时遇到过连接问题?根据Call在连接问题重试时的处理方式,writeTo可能会被调用多次,这将导致流关闭而崩溃。 - njzk2
@njzk2 我没有使用过这段代码。看了一下Call源代码,我明白你的意思,但我还没有测试过。为了解决这个问题,你可以将整个InputStream读入一个byte[]中,然后简单地调用Requestbody.create(mediaType, content)方法。这样就可以多次调用writeTo而不会出现任何问题。你仍然可以将所有这些内容封装到一个漂亮的工具类中,以便于轻松重复使用。 - Miguel
1
@MiguelLavigne:这实际上是我最终采取的方法。此外,显然正在开发这个功能 https://github.com/square/okhttp/pull/1038 ,我已经看到了使用 inputStream.reset() 允许重试的提交,但仍无法解决不可重置的 inputStreams 问题,而 contentProviders 可以提供(我最终将内容放入了 byte[])。 - njzk2
1
java.lang.OutOfMemoryError:尝试抛出OutOfMemoryError时抛出OutOfMemoryError;没有可用的堆栈跟踪。我该如何解决这个问题? - Sudhir Singh Khanger
1
如果与 body 级别的日志记录一起使用,它就无法正常工作。如果将日志记录设置为 Level.BODY,则流会中断。 - Daniel Hári
显示剩余5条评论

0

-2

使用Snoopy API太容易了:只需要一行代码,如果不包括标识符定义的话 :)

URI uri = ...;
Path fileToUpload = ...;
Snoopy.builder()
      .config(SnoopyConfig.defaults())
      .build()
      .post(uri)
      .followRedirects(true)
      .failIfNotSuccessfulResponse(true)
      .body(fileToUpload)
      .consumeAsString();

https://bitbucket.org/abuwandi/snoopy

目前还没有安卓版本,但很快就会推出...


也许我错过了什么,但据我所知,Snoopy源代码暗示构建器的.body(fileToUpload)方法不支持InputStream - Jasper Siepkes
没错,上面的示例展示了一个路径作为主体,版本0.8.7支持输入流,并且仍然与上面的代码量相同,唯一的小变化在第2行中InputStream fileToUpload = ...; - TheReALDeAL

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