Скачать бинарный файл с ОХТТП

Я использую 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);
        }
    }
}

Текущий код для простого запроса get:

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

11 ответов

Решение

Получение 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();

Для чего это стоит, я бы порекомендовал 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 для всего вышеперечисленного. Source избегает невозможного для реализации метода available(). Вместо этого вызывающие указывают, сколько байтов им требуется.

Source опускает небезопасную для компоновки метку и состояние сброса, которые отслеживаются InputStream; абоненты вместо этого просто буферизируют то, что им нужно.

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

И источник имеет более сильный метод пропуска: BufferedSource.skip(long) не будет возвращаться преждевременно.

Лучший вариант для скачивания (на основе исходного кода "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();
}

Вот как я использую библиотеки Okhttp + Okio при публикации прогресса загрузки после каждой загрузки чанка:

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

Лучшее решение - использовать 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);


                }
            });

Версия 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()

Обновление в Котлин , чтобы соответствовать ответ KiddoUK от 2015 ( /questions/2039167/skachat-binarnyij-fajl-s-ohttp/2039175#2039175):

      val sourceBytes = response.body().source()
val sink: BufferedSink = File(downloadLocationFilePath).sink().buffer()
sink.writeAll(sourceBytes)
sink.close()

и добавить мониторинг прогресса:

      val sourceBytes = response.body().source()
val sink: BufferedSink = File(downloadLocationFilePath).sink().buffer()

var totalRead: Long = 0
var lastRead: Long
while (sourceBytes
          .read(sink.buffer, 8L * 1024)
          .also { lastRead = it } != -1L 
) {
    totalRead += lastRead
    sink.emitCompleteSegments()
    // Call your listener/callback here with the totalRead value        
}

sink.writeAll(sourceBytes)
sink.close()

Вот быстрый способ сделать это.

  • client- ОкХттпКлиент
  • url- URL-адрес, который вы хотите загрузить
  • destination- где вы хотите сохранить его
          fun download(url: String, destination: File) = destination.outputStream().use { output ->
        client.newCall(Request.Builder().url(url).build())
            .execute().body!!.byteStream().use { input ->
                input.copyTo(output)
            }
    }

Я добавляю свое решение здесь:

      fun downloadFile(url: String, file: File) : Completable {
    val request = Request.Builder().url(url).build()
    return Completable.fromPublisher<Boolean> { p ->
        OkHttpClient.Builder().build().newCall(request).enqueue(object: Callback {
            override fun onFailure(call: Call, e: IOException) {
                p.onError(e)
            }

            override fun onResponse(call: Call, response: Response) {
                val sink = Okio.buffer(Okio.sink(file))
                sink.writeAll(response.body()!!.source())
                sink.close()
                p.onComplete()
            }

        })
    }
}

Перестроить клиент важно, если вы используете различные промежуточные программы или кодируете данные в json, или находите решение, которое не кодирует ответ в другом формате.

Если вы пытаетесь записать загруженные байты в общее хранилище на последней версии Android, вы должны иметь на руке вместо Fileпример. Вот как конвертировать Uri к :

      fun Uri?.toOutputStream(context: Context)
        : OutputStream? {
    if (this == null) {
        return null
    }

    fun createAssetFileDescriptor() = try {
        context.contentResolver.openAssetFileDescriptor(this, "w")
    } catch (e: FileNotFoundException) {
        null
    }

    fun createParcelFileDescriptor() = try {
        context.contentResolver.openFileDescriptor(this, "w")
    } catch (e: FileNotFoundException) {
        null
    }

    /** scheme://<authority>/<path>/<id> */
    if (scheme.equals(ContentResolver.SCHEME_FILE)) {
        /** - If AssetFileDescriptor is used, it always writes 0B.
         * - (FileOutputStream | ParcelFileDescriptor.AutoCloseOutputStream) works both for app-specific + shared storage
         * - If throws "FileNotFoundException: open failed: EACCES (Permission denied)" on Android 10+, use android:requestLegacyExternalStorage="true" on manifest and turnOff/turnOn "write_external_storage" permission on phone settings. Better use Content Uri on Android 10+ */
        return try {
            FileOutputStream(toFile())
        } catch (e: Throwable) {
            null
        }

    } else if (scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
        // i think you can't write to asset inside apk
        return null

    } else {
        // content URI (MediaStore)
        if (authority == android.provider.MediaStore.AUTHORITY) {
            return try {
                context.contentResolver.openOutputStream(this, "w")
            } catch (e: Throwable) {
                null
            }
        } else {
            // content URI (provider), not tested
            return try {
                val assetFileDescriptor = createAssetFileDescriptor()
                if (assetFileDescriptor != null) {
                    AssetFileDescriptor.AutoCloseOutputStream(assetFileDescriptor)
                } else {
                    val parcelFileDescriptor = createParcelFileDescriptor()
                    if (parcelFileDescriptor != null) {
                        ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
                    } else {
                        null
                    }
                }
            } catch (e: Throwable) {
                null
            }
        }
    }
}

Как только у вас есть OutputStream, отдых аналогичен другим ответам. Подробнее о приемнике / источнике и выбросе / сбросе :

      // create Request
val request = Request.Builder()
            .method("GET", null)
            .url(url)
            .build()

// API call function
fun apiCall(): Response? {
    return try {
        client.newCall(request).execute()
    } catch (error: Throwable) {
        null
    }
}

// execute API call
var response: Response? = apiCall()
// your retry logic if request failed (response==null)

// if failed, return
if (response == null || !response.isSuccessful) {
    return
}

// response.body
val body: ResponseBody? = response!!.body
if (body == null) {
    response.closeQuietly()
    return
}

// outputStream
val outputStream = destinationUri.toOutputStream(appContext)
if (outputStream == null) {
    response.closeQuietly() // calls body.close
    return
}
val bufferedSink: BufferedSink = outputStream!!.sink().buffer()
val outputBuffer: Buffer = bufferedSink.buffer

// inputStream
val bufferedSource = body!!.source()
val contentLength = body.contentLength()

// write
var totalBytesRead: Long = 0
var toBeFlushedBytesRead: Long = 0
val BUFFER_SIZE = 8 * 1024L // KB
val FLUSH_THRESHOLD = 200 * 1024L // KB
var bytesRead: Long = bufferedSource.read(outputBuffer, BUFFER_SIZE)
var lastProgress: Int = -1
while (bytesRead != -1L) {
    // emit/flush
    totalBytesRead += bytesRead
    toBeFlushedBytesRead += bytesRead
    bufferedSink.emitCompleteSegments()
    if (toBeFlushedBytesRead >= FLUSH_THRESHOLD) {
        toBeFlushedBytesRead = 0L
        bufferedSink.flush()
    }

    // write
    bytesRead = bufferedSource.read(outputBuffer, BUFFER_SIZE)

    // progress
    if (contentLength != -1L) {
        val progress = (totalBytesRead * 100 / contentLength).toInt()
        if (progress != lastProgress) {
            lastProgress = progress
            // update UI (using Flow/Observable/Callback)
        }
    }
}

bufferedSink.flush()

// close resources
outputStream.closeQuietly()
bufferedSink.closeQuietly()
bufferedSource.closeQuietly()
body.closeQuietly()
          val request = Request.Builder().url(URL).build()
    val response = OkHttpClient().newCall(request).execute()

    val `is`: InputStream = response.body!!.byteStream()

    val PATH = (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        .toString() + "/Folder")
    val file = File(PATH)

    if (!file.exists()) file.mkdir()  
  
        val outputFile = File(file, getFileName(URL))

        val input = BufferedInputStream(`is`)
        val output: OutputStream = FileOutputStream(outputFile)

        val data = ByteArray(1024)

        var total: Long = 0

         while (`is`.read(data).also { total = it.toLong() } != -1) {
             output.write(data, 0, total.toInt())
         }

        output.flush()
        output.close()
        input.close()
Другие вопросы по тегам