从OKHTTP下载二进制文件


78

我在我的Android应用程序中使用OKHTTP客户端进行联网。

示例显示了如何上传二进制文件。我想知道如何使用OKHTTP客户端获取二进制文件下载的输入流。

这是示例的清单:

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

用于简单获取请求的当前代码为:

OkHttpClient client = new OkHttpClient();
request = new Request.Builder().url("URL string here")
                    .addHeader("X-CSRFToken", csrftoken)
                    .addHeader("Content-Type", "application/json")
                    .build();
response = getClient().newCall(request).execute();

现在如何将响应转换为InputStream。类似于Apache HTTP Client以下OkHttp响应的响应:

InputStream is = response.getEntity().getContent();

编辑

从下面接受答案。我修改的代码:

request = new Request.Builder().url(urlString).build();
response = getClient().newCall(request).execute();

InputStream is = response.body().byteStream();

BufferedInputStream input = new BufferedInputStream(is);
OutputStream output = new FileOutputStream(file);

byte[] data = new byte[1024];

long total = 0;

while ((count = input.read(data)) != -1) {
    total += count;
    output.write(data, 0, count);
}

output.flush();
output.close();
input.close();

检查我编辑
好的

很高兴它为您服务:D
Nadir Belhaj 2014年

附带说明,如果请求中有ioexception并且HttpEngine设置为重试,则InputStreamRequestBody无法正常工作。参见github.com/square/okhttp/blob/master/okhttp/src/main/java/com/…第202行,writeTo在while循环中被调用。这将导致错误。(我在从content://
Inputstream

Answers:


38

从OKHTTP获取ByteStream

我一直在研究OkHttp的文档,您需要这样做

使用此方法:

response.body()。byteStream()将返回InputStream

因此您可以简单地使用BufferedReader或任何其他替代方法

OkHttpClient client = new OkHttpClient();
request = new Request.Builder().url("URL string here")
                     .addHeader("X-CSRFToken", csrftoken)
                     .addHeader("Content-Type", "application/json")
                     .build();
response = getClient().newCall(request).execute();

InputStream in = response.body().byteStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String result, line = reader.readLine();
result = line;
while((line = reader.readLine()) != null) {
    result += line;
}
System.out.println(result);
response.body().close();

3
您在文档中提到的示例将适用于具有简单数据或网页的请求,但对于下载二进制文件,我是否不需要bufferedinputstream?这样我就可以将字节存储在缓冲区中并将其写入Fileoutputstream以保存在本地存储中?
pratsJ 2014年

1
使用二进制解决方案进行编辑
Nadir Belhaj 2014年

1
OKHTTP响应没有像Apache HTTP Client这样的getEntity()方法。对问题进行了编辑
Prats

编辑的最终答案;)
Nadir Belhaj 2014年

13
不要使用BufferedReader二进制数据;它会因不必要的字节到字符解码而损坏。
杰西·威尔逊

192

对于它的价值,我会建议response.body().source()奥基奥为了享受更简单的方法来处理下载文件时可以来的数据量大(因为OkHttp已经支持它本身)。

@Override
public void onResponse(Call call, Response response) throws IOException {
    File downloadedFile = new File(context.getCacheDir(), filename);
    BufferedSink sink = Okio.buffer(Okio.sink(downloadedFile));
    sink.writeAll(response.body().source());
    sink.close();
}

与InputStream相比,该文档具有两个优点:

此接口在功能上等效于InputStream。当消耗的数据是异构的时,InputStream需要多层:用于原始值的DataInputStream,用于缓冲的BufferedInputStream和用于字符串的InputStreamReader。此类针对上述所有内容使用BufferedSource。Source避免了无法实现的available()方法。而是,调用者指定所需的字节数。

Source省略了InputStream跟踪的不安全的标记和重置状态。呼叫者只是缓冲他们需要的东西。

在实现源代码时,您不必担心单字节读取方法难以有效实现并且返回257个可能值之一的情况。

源具有更强大的跳过方法:BufferedSource.skip(long)不会过早返回。


22
这种方法也是最快的,因为不会有任何不必要的响应数据副本。
杰西·威尔逊

2
如何处理这种方法的进展?
zella 2015年

1
@zella只需使用write(Source source,long byteCount)和一个循环。当然,您需要从主体获取contentLength以确保您读取了正确的字节数。在每个循环中触发一个事件。
kiddouk

5
解决了!工作代码: while ((source.read(fileSink.buffer(), 2048)) != -1)
zella 2015年

1
@IgorGanapolsky可以,但是如果您希望访问归档文件中的文件,则必须实现一个Zip压缩程序以获取您需要的所有文件。
kiddouk

12

最佳下载选项(基于源代码“ okio”)

private void download(@NonNull String url, @NonNull File destFile) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = okHttpClient.newCall(request).execute();
    ResponseBody body = response.body();
    long contentLength = body.contentLength();
    BufferedSource source = body.source();

    BufferedSink sink = Okio.buffer(Okio.sink(destFile));
    Buffer sinkBuffer = sink.buffer();

    long totalBytesRead = 0;
    int bufferSize = 8 * 1024;
    for (long bytesRead; (bytesRead = source.read(sinkBuffer, bufferSize)) != -1; ) {
        sink.emit();
        totalBytesRead += bytesRead;
        int progress = (int) ((totalBytesRead * 100) / contentLength);
        publishProgress(progress);
    }
    sink.flush();
    sink.close();
    source.close();
}

写downloadFile方法
Jemshit Iskenderov

我无法获取body.contentLength()...它始终为-1
H Raval

2
不是肯定的,但您可能希望将flush()andclose()方法放在一个finally块中,以确保所有异常仍将刷新并关闭它们。
约书亚·品特

10

这是我如何使用Okhttp +奥基奥库,而每块下载后发布下载进度:

public static final int DOWNLOAD_CHUNK_SIZE = 2048; //Same as Okio Segment.SIZE

try {
        Request request = new Request.Builder().url(uri.toString()).build();

        Response response = client.newCall(request).execute();
        ResponseBody body = response.body();
        long contentLength = body.contentLength();
        BufferedSource source = body.source();

        File file = new File(getDownloadPathFrom(uri));
        BufferedSink sink = Okio.buffer(Okio.sink(file));

        long totalRead = 0;
        long read = 0;
        while ((read = source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {
            totalRead += read;
            int progress = (int) ((totalRead * 100) / contentLength);
            publishProgress(progress);
        }
        sink.writeAll(source);
        sink.flush();
        sink.close();
        publishProgress(FileInfo.FULL);
} catch (IOException e) {
        publishProgress(FileInfo.CODE_DOWNLOAD_ERROR);
        Logger.reportException(e);
}

怎么 getDownloadPathFrom办?
MBehtemam,2016年

写缺少的方法请
Jemshit Iskenderov

1
我无法获取body.contentLength()...它始终为-1
H Raval

5
while (read = (source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {应该是while ((read = source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {
MatthieuHarlé'Apr

1
如果您不在循环中调用sink.emit();while则会占用大量内存。
manfcas

7

更好的解决方案是使用OkHttpClient作为:

OkHttpClient client = new OkHttpClient();

            Request request = new Request.Builder()
                    .url("http://publicobject.com/helloworld.txt")
                    .build();



            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

//                    Headers responseHeaders = response.headers();
//                    for (int i = 0; i < responseHeaders.size(); i++) {
//                        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
//                    }
//                    System.out.println(response.body().string());

                    InputStream in = response.body().byteStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    String result, line = reader.readLine();
                    result = line;
                    while((line = reader.readLine()) != null) {
                        result += line;
                    }
                    System.out.println(result);


                }
            });

我无法获取body.contentLength()...它始终为-1
H Raval

@HRaval不是okhttp问题,这仅表示服务器未发送“ Content-Length”标头
Sergei Voitovich

@乔拉如何将该文件存储在内部存储中?
Hardik Parmar

2

Kodlin版本基于kiddouk的答案

 val request = Request.Builder().url(url).build()
 val response = OkHttpClient().newCall(request).execute()
 val downloadedFile = File(cacheDir, filename)
 val sink: BufferedSink = downloadedFile.sink().buffer()
 sink.writeAll(response.body!!.source())
 sink.close()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.