Android 从OKHTTP下载二进制文件

Android 从OKHTTP下载二进制文件,android,inputstream,bufferedinputstream,okhttp,Android,Inputstream,Bufferedinputstream,Okhttp,我在android应用程序中使用OKHTTP客户端进行联网 示例显示如何上载二进制文件。我想知道如何使用OKHTTP客户端获取二进制文件下载的inputstream 以下是示例列表: public class InputStreamRequestBody extends RequestBody { private InputStream inputStream; private MediaType mediaType; public static RequestBod

我在android应用程序中使用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
。类似于来自
apachehttp客户端的响应的东西,类似于
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();

从OKHTTP获取ByTestStream

我一直在查阅OkHttp的文档,你需要这样做

使用此方法:

response.body().ByTestStream()将返回一个InputStream

因此,您可以简单地使用BufferedReader或任何其他替代方法


值得一提的是,我建议使用
response.body().source()
from(因为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个可能值中的一个

而且source有一个更强的跳过方法:BufferedSource.skip(long)不会过早返回


这就是我在每次区块下载后发布下载进度时如何使用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);


                }
            });
OkHttpClient=new-OkHttpClient();
Request Request=newrequest.Builder()
.url(“http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback()){
@凌驾
公共void onFailure(调用调用,IOE异常){
e、 printStackTrace();
}
@凌驾
public void onResponse(调用调用、响应响应)引发IOException{
如果(!response.issusccessful())抛出新IOException(“意外代码”+响应);
//Headers responseHeaders=response.Headers();
//对于(int i=0;i
下载的最佳选项(基于源代码“okio”)


基于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()

如果您试图在最新的Android上将下载的字节写入,您的手上应该有
Uri
,而不是
File
实例。下面是如何将
Uri
转换为
OutputStream

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

您在文档中提到的示例适用于具有简单数据或网页的请求,但对于下载二进制文件,我不需要bufferedinputstream吗?因此,我可以获取缓冲区中的字节并将它们写入Fileoutputstream以保存在本地存储中?使用binaryOKHTTP响应的解决方案编辑没有像Apache HTTP客户端那样的getEntity()方法。在问题中进行了编辑,并使用最终答案进行了编辑;)不要对二进制数据使用
BufferedReader
;它将被不必要的字节到字符解码损坏。检查我编辑的答案我正在等待你的反馈很高兴它对你有效男士:作为旁注,如果请求中存在ioexception,并且HttpEngine设置为重试,则InputStreamRequestBody将无法工作。参见第202行,writeTo在while循环中被调用。这将导致一个错误。(我在从
内容://
输入流上传时偶然发现了这一点)这种方法也是最快的,因为不会有任何不必要的响应数据副本。如何处理这种方法的进度?@zella只需使用write(源代码,长字节数)和循环。当然,您需要从正文中获取contentLength,以确保读取的字节数正确。在每个循环中触发一个事件。已解决!工作代码:
while((source.read(fileSink.buffer(),2048))!=-1)
@IgorGanapolsky确实如此,但如果您希望访问归档文件中的文件,则必须实现一个Zip deflater来获取所需的所有文件。要做什么
getdownloadpath from
编写缺少的方法plsi无法获取body.contentLength()…它总是-1
while(read=(source.read(sink.buffer(),DOWNLOAD_CHUNK_SIZE))!=-1{
应该是
while((read=source.read(sink.buffer(),DOWNLOAD_CHUNK_SIZE))!=-1{
如果在
while
循环中不调用
sink.emit();
你将使用大量内存。write downloadFile方法
 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()
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
            }
        }
    }
}
// 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()