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.