Laden Sie die Binärdatei von OKHTTP herunter

78

Ich verwende den OKHTTP-Client für das Netzwerk in meiner Android-Anwendung.

Dieses Beispiel zeigt, wie eine Binärdatei hochgeladen wird. Ich würde gerne wissen, wie man einen Inputstream für das Herunterladen von Binärdateien mit dem OKHTTP-Client erhält.

Hier ist die Auflistung des Beispiels:

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

Der aktuelle Code für eine einfache Abrufanforderung lautet:

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

Wie konvertiere ich nun die Antwort in InputStream. Etwas Ähnliches wie eine Antwort von Apache HTTP Clientdieser für die OkHttpAntwort:

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

BEARBEITEN

Akzeptierte Antwort von unten. Mein geänderter Code:

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();
pratsJ
quelle
Überprüfen Sie meine bearbeitete Antwort Ich warte auf Ihr Feedback
Nadir Belhaj
Nadir Belhaj
Als Randnotiz funktioniert Ihr InputStreamRequestBody nicht, wenn die Anforderung eine ioexception enthält und die HttpEngine so eingestellt ist, dass sie es erneut versucht. Siehe github.com/square/okhttp/blob/master/okhttp/src/main/java/com/… Zeile 202, writeTo wird in einer while-Schleife aufgerufen. Es wird einen Fehler verursachen. (Ich bin beim Hochladen von einem content://Inputstream darauf gestoßen)
njzk2

Antworten:

37

ByteStream von OKHTTP abrufen

Ich habe in der Dokumentation von OkHttp herumgegraben , dass Sie diesen Weg gehen müssen

Verwenden Sie diese Methode:

response.body (). byteStream (), der einen InputStream zurückgibt

Sie können also einfach einen BufferedReader oder eine andere Alternative verwenden

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();
Nadir Belhaj
quelle
3
Das Beispiel, das Sie in der Dokumentation erwähnt haben, funktioniert für eine Anfrage mit einfachen Daten oder Webseiten, aber zum Herunterladen einer Binärdatei würde ich keinen gepufferten Eingabestream benötigen? Damit ich die Bytes im Puffer abrufen und in Fileoutputstream schreiben kann, um sie im lokalen Speicher zu speichern?
PratsJ
1
Bearbeitet mit Lösung für Binär
Nadir Belhaj
1
Die OKHTTP-Antwort verfügt nicht über die Methode getEntity () wie der Apache HTTP Client. Änderungen in der Frage vorgenommen
pratsJ
Bearbeitet mit der endgültigen Antwort;)
Nadir Belhaj
13
Nicht BufferedReadermit Binärdaten verwenden. Es wird durch die unnötige Decodierung von Byte zu Zeichen beschädigt.
Jesse Wilson
193

Für das, was es wert ist, würde ich response.body().source()von okio empfehlen (da OkHttp es bereits nativ unterstützt), um eine einfachere Möglichkeit zu haben, eine große Datenmenge zu bearbeiten, die beim Herunterladen einer Datei auftreten kann.

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

Einige Vorteile aus der Dokumentation im Vergleich zu InputStream:

Diese Schnittstelle entspricht funktional InputStream. InputStream erfordert mehrere Ebenen, wenn verbrauchte Daten heterogen sind: einen DataInputStream für primitive Werte, einen BufferedInputStream für die Pufferung und einen InputStreamReader für Zeichenfolgen. Diese Klasse verwendet BufferedSource für alle oben genannten Zwecke. Source vermeidet die unmöglich zu implementierende Methode available (). Stattdessen geben Anrufer an, wie viele Bytes sie benötigen.

Die Quelle lässt die von InputStream verfolgte Markierung und den Rücksetzstatus für die unsichere Komposition weg. Anrufer puffern stattdessen einfach, was sie brauchen.

Bei der Implementierung einer Quelle müssen Sie sich keine Gedanken über die Einzelbyte-Lesemethode machen, die sich nur schwer effizient implementieren lässt und einen von 257 möglichen Werten zurückgibt.

Und source hat eine stärkere Sprungmethode: BufferedSource.skip (long) wird nicht vorzeitig zurückgegeben.

Kiddouk
quelle
22
Dieser Ansatz ist auch der schnellste, da keine unnötigen Kopien der Antwortdaten vorhanden sind.
Jesse Wilson
2
Wie geht man mit diesem Ansatz mit dem Fortschritt um?
Zella
1
@zella benutze einfach write (Quellquelle, long byteCount) und eine Schleife. Natürlich müssen Sie contentLength vom Body abrufen, um sicherzustellen, dass Sie die richtige Anzahl von Bytes lesen. Starten Sie in jeder Schleife ein Ereignis.
Kiddouk
5
Gelöst! while ((source.read(fileSink.buffer(), 2048)) != -1)
Arbeitscode
1
@IgorGanapolsky tut es, aber Sie müssen einen Zip-Deflater implementieren, um alle Dateien zu erhalten, die Sie später benötigen, wenn Sie Zugriff auf die Dateien im Archiv erwarten.
Kiddouk
12

Die beste Option zum Herunterladen (basierend auf dem Quellcode "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();
}
e.shishkin
quelle
schreiben downloadFile Methode
Jemshit Iskenderov
Ich kann nicht bekommen body.contentLength () ... es ist immer -1
H Raval
2
Nicht positiv, aber Sie möchten wahrscheinlich die Methoden flush()und close()in einen finallyBlock einfügen, um sicherzustellen, dass Ausnahmen weiterhin gelöscht und geschlossen werden.
Joshua Pinter
10

So verwende ich Okhttp + Okio- Bibliotheken, während ich den Download-Fortschritt nach jedem Chunk-Download veröffentliche:

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);
}
Shubham Chaudhary
quelle
was getDownloadPathFromtun
MBehtemam
Schreiben Sie fehlende Methoden pls
Jemshit Iskenderov
1
Ich kann nicht bekommen body.contentLength () ... es ist immer -1
H Raval
5
while (read = (source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {sollte seinwhile ((read = source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {
Matthieu Harlé
1
Wenn Sie sink.emit();den whileZyklus nicht aufrufen, verbrauchen Sie viel Speicher.
Manfcas
7

Bessere Lösung ist die Verwendung von OkHttpClient als:

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


                }
            });
Thiago
quelle
Ich kann nicht bekommen body.contentLength () ... es ist immer -1
H Raval
@HRaval, das ist kein okhttp-Problem, das bedeutet einfach, dass der Server keinen "Content-Length" -Header
gesendet hat
@Joolah, wie man diese Datei im internen Speicher speichert?
Hardik Parmar
2

Kotlin-Version basierend auf Kiddouk-Antwort

 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()
vahid
quelle