Завантажте двійковий файл з OKHTTP


78

Я використовую клієнт OKHTTP для роботи в мережі в моєму додатку для Android.

Цей приклад показує, як завантажити двійковий файл. Я хотів би знати, як отримати вхідний потік завантаження двійкових файлів за допомогою клієнта 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
Надір Белхай

як допоміжна примітка, ваш InputStreamRequestBody не буде працювати, якщо в запиті є виняток ioexception, а HttpEngine встановлено повторно Див. Github.com/square/okhttp/blob/master/okhttp/src/main/java/com/… рядок 202, writeTo викликається у циклі while. Це спричинить помилку. (Я натрапив на це під час завантаження з content://вхідного потоку)
njzk2

Відповіді:


37

Отримання ByteStream з OKHTTP

Я копався в Документації 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
Приклад, який ви згадали з документації, буде працювати для запиту з простими даними або веб-сторінкою, але для завантаження двійкового файлу, чи не потрібен мені буферизований потік вводу? Так що я можу отримати байти в буфері і записати їх у Fileoutputstream для збереження в локальному сховищі?
pratsJ

1
Відредаговано з рішенням для двійкових файлів
Nadir Belhaj

1
Відповідь OKHTTP не має методу getEntity (), як клієнт Apache HTTP. Зміни у запитанні
pratsJ

Відредаговано з остаточною відповіддю;)
Nadir Belhaj,

13
Не використовувати BufferedReaderз двійковими даними; він буде пошкоджений непотрібним декодуванням байтів до символів.
Джессі Вілсон,

193

Для того, що це варто, я б рекомендував response.body().source()від okio (оскільки 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 для всього вищезазначеного. Джерело уникає неможливого для реалізації методу available (). Натомість абоненти вказують, скільки байт їм потрібно.

Джерело опускає небезпечну для складання позначку та стан скидання, що відстежується InputStream; натомість абоненти просто буферизують те, що їм потрібно.

При реалізації джерела вам не потрібно турбуватися про однобайтовий метод читання, який незручно ефективно реалізовувати і який повертає одне з 257 можливих значень.

І джерело має сильніший метод пропуску: BufferedSource.skip (long) не повернеться передчасно.


22
Цей підхід є також найшвидшим, оскільки не буде зайвих копій даних відповідей.
Джессі Вілсон,

2
Як керувати прогресом за допомогою цього підходу?
zella

1
@zella просто використовує write (Source source, long byteCount) і цикл. Звичайно, вам потрібно отримати contentLength з тіла, щоб переконатися, що ви прочитали правильну кількість байтів. Запустіть подію в кожному циклі.
kiddouk

5
Вирішено! Робочий код: while ((source.read(fileSink.buffer(), 2048)) != -1)
zella

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

написати метод завантаженняФайл
Джемшит Іскендеров

я не можу отримати body.contentLength () ... його завжди -1
H Raval

2
Не є позитивним, але ви, мабуть, захочете помістити методи flush()і close()в 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

напишіть відсутні методи pls
Джемшит Іскендеров

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) {
Matthieu Harlé

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"
Сергій Войтович

@Joolah як зберігати цей файл у внутрішній пам’яті?
Хардік Пармар,

2

Версія Kotlin на основі відповіді 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.