【问题标题】:Upload binary file with okhttp from resources使用 okhttp 从资源上传二进制文件
【发布时间】:2014-10-11 15:17:17
【问题描述】:

我需要使用 okhttp 将捆绑在 apk 中的二进制文件上传到服务器。使用 urlconnection,您可以简单地获取资产的输入流,然后将其放入您的请求中。但是,okhttp 只为您提供了上传字节数组、字符串或文件的选项。由于您无法获取捆绑在 apk 中的资产的文件路径,因此将文件复制到本地文件目录(我宁愿不这样做)然后将文件提供给 okhttp 是唯一的选择吗?有没有办法简单地使用assetinputstream 直接向网络服务器发出请求?

编辑:我使用了公认的答案,但我没有创建静态实用程序类,而是简单地将 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的内部实现

【问题讨论】:

  • 嗯,你可以按照你说的把内容放在一个字节数组中。
  • 我就是这么做的。不过,我仍然更喜欢直接从资产中获取输入流的解决方案。

标签: android okhttp


【解决方案1】:

我知道已经晚了,但总比没有好。

这里的窍门如果你不知道你的数据的大小,你可以跳过 content-length header 并用 Http1.1 transfer-encoding: Chuncked 代替它

更多信息请阅读https://www.oracle.com/technical-resources/articles/javame/chunking.html

【讨论】:

    【解决方案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

    仍然没有 Android 版本,但它即将推出......

    【讨论】:

    • 也许我遗漏了一些东西,但据我所知,史努比来源暗示构建器的 .body(fileToUpload) 方法不支持 InputStream
    • 没错,上面的例子是显示路径为body,0.8.7版本支持输入流,代码量还是和上面一样,一个小改动是在第2行InputStream fileToUpload = ...;
    【解决方案3】:

    您可能无法直接使用该库来执行此操作,但您可以创建一个小实用程序类来为您执行此操作。然后,您可以在任何需要的地方重复使用它。

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

    此示例代码基于this 代码。将 Assets 文件名和 MediaType 替换为您自己的。

    【讨论】:

    • 这实际上非常聪明,谢谢。不知道为什么我没有想到。当我测试它时,我会更新我的最终解决方案,但它将是这个版本。
    • 您在 RequestBody 的实现中是否遇到过任何连接问题?根据Call处理连接问题重试的方式,writeTo可能会被多次调用,会因为流被关闭而崩溃。
    • @MiguelLavigne:这实际上是我最终所做的。此外,这个功能显然正在github.com/square/okhttp/pull/1038 上工作,我看到一个使用inputStream.reset() 允许重试的提交,但这仍然没有考虑到 contentProviders 可以提供的不可重置的 inputStreams(我最终把字节中的内容[])
    • java.lang.OutOfMemoryError: OutOfMemoryError 在尝试抛出 OutOfMemoryError 时抛出;没有可用的堆栈跟踪。我该如何解决这个问题?
    • 它不能与正文级日志记录一起使用。如果日志记录设置为 Level.BODY,则流中断。
    猜你喜欢
    • 2014-11-11
    • 1970-01-01
    • 2015-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-24
    相关资源
    最近更新 更多