OKHTTP에서 바이너리 파일 다운로드


78

내 안드로이드 응용 프로그램에서 네트워킹을 위해 OKHTTP 클라이언트를 사용하고 있습니다.

이 예제는 바이너리 파일을 업로드하는 방법을 보여줍니다. OKHTTP 클라이언트로 다운로드하는 바이너리 파일의 inputstream을 얻는 방법을 알고 싶습니다.

다음은 예제 목록입니다.

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

내 편집 대답을 확인 내가 귀하의 의견을 기다리고 있어요
— 디르 Belhaj

D : 기꺼이 당신을 위해 사람 일
— 디르 Belhaj

참고로, InputStreamRequestBody는 요청에 ioexception이 있고 HttpEngine이 재 시도하도록 설정된 경우 작동하지 않습니다. github.com/square/okhttp/blob/master/okhttp/src/main/java/com/… 202 행을 참조하십시오 . writeTo는 while 루프에서 호출됩니다. 오류가 발생합니다. (입력 content://
— 스트림

답변:


37

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

1
바이너리에 대한 솔루션 편집
— 디르 Belhaj

1
OKHTTP 응답에는 Apache HTTP Client와 같은 getEntity () 메서드가 없습니다. 질문에서 제작 편집
— pratsJ

궁극적 인 답을 수정 됨)
— 디르 Belhaj

13
BufferedReader이진 데이터와 함께 사용하지 마십시오 . 불필요한 바이트 대 문자 디코딩으로 인해 손상됩니다.
— Jesse Wilson

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 () 메서드를 피합니다. 대신 호출자는 필요한 바이트 수를 지정합니다.

Source는 InputStream에 의해 추적되는 unsafe-to-compose 표시 및 재설정 상태를 생략합니다. 대신 호출자는 필요한 것을 버퍼링합니다.

소스를 구현할 때 효율적으로 구현하기 어색하고 257 개의 가능한 값 중 하나를 반환하는 단일 바이트 읽기 메서드에 대해 걱정할 필요가 없습니다.

소스에는 더 강력한 스킵 메소드가 있습니다. BufferedSource.skip (long)은 너무 일찍 반환되지 않습니다.


22
이 접근 방식은 응답 데이터의 불필요한 사본이 없기 때문에 가장 빠릅니다.
— Jesse Wilson

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

downloadFile 방법을 쓰기
— Jemshit Iskenderov에게

나는 그것의 항상 -1 ... body.contentLength ()을 얻을 캔트
— H 라발 (Raval)

2
긍정적 인 것은 아니지만 예외가 여전히 플러시되고 닫히도록하기 위해 flush()및 close()메서드를 finally블록 에 넣고 싶을 것입니다.
— Joshua Pinter

10

다음은 청크 다운로드 후 다운로드 진행률을 게시하는 동안 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);
}

무엇을 getDownloadPathFrom?
— MBehtemam

실종 방법의 PLS 쓰기
— Jemshit Iskenderov에게

1
나는 그것의 항상 -1 ... body.contentLength ()을 얻을 캔트
— H 라발 (Raval)

5
while (read = (source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {해야while ((read = source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {
— 마티유 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);


                }
            });

나는 그것의 항상 -1 ... body.contentLength ()을 얻을 캔트
— H 라발 (Raval)

단순히 서버를 의미한다는 okhttp 문제가되지 않습니다 @HRaval는 "콘텐츠 길이"헤더를 보내지 않았다
— 세르게이 Voitovich

@Joolah 내부 저장소에 파일을 저장하는 방법은 무엇입니까?
— Hardik Parmar

2

kiddouk 답변을 기반으로 한 Kotlin 버전

 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()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.