排球 - 直接下载到文件(没有内存字节数组)

Gil*_*yof 8 android memory-management fileoutputstream android-volley

我正在使用Volley作为我在Android中正在进行的项目中的网络堆栈.我的部分要求是下载可能非常大的文件并将其保存在文件系统中.

我一直在研究凌空的实现,似乎凌空的唯一方法就是将整个文件下载到一个潜在的大量字节数组中,然后将这个字节数组的处理推迟到一些回调处理程序.

由于这些文件可能非常大,我担心下载过程中出现内存不足错误.

有没有办法告诉volley将http输入流中的所有字节直接处理成文件输出流?或者这需要我实现自己的网络对象?

我在网上找不到任何关于此的资料,所以任何建议都将不胜感激.

Gil*_*yof 4

好的,我想出了一个解决方案,其中涉及编辑 Volley 本身。这是一个演练:

网络响应不能再保存字节数组。它需要保存一个输入流。这样做会立即破坏所有请求实现,因为它们依赖于持有公共字节数组成员的 NetworkResponse。我发现处理此问题的侵入性最小的方法是在 NetworkResponse 中添加一个“toByteArray”方法,然后进行一些重构,使对字节数组的任何引用都使用此方法,而不是删除的字节数组成员。这意味着输入流到字节数组的转换发生在响应解析期间。我不完全确定这会产生什么长期影响,因此一些单元测试/社区输入将在这里提供巨大的帮助。这是代码:

public class NetworkResponse {
    /**
     * Creates a new network response.
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @param notModified True if the server returned a 304 and the data was already in cache
     */
    public NetworkResponse(int statusCode, inputStream data, Map<String, String> headers,
            boolean notModified, ByteArrayPool byteArrayPool, int contentLength) {
        this.statusCode = statusCode;
        this.data = data;
        this.headers = headers;
        this.notModified = notModified;
        this.byteArrayPool = byteArrayPool;
        this.contentLength = contentLength;
    }

    public NetworkResponse(byte[] data) {
        this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false);
    }

    public NetworkResponse(byte[] data, Map<String, String> headers) {
        this(HttpStatus.SC_OK, data, headers, false);
    }

    /** The HTTP status code. */
    public final int statusCode;

    /** Raw data from this response. */
    public final InputStream inputStream;

    /** Response headers. */
    public final Map<String, String> headers;

    /** True if the server returned a 304 (Not Modified). */
    public final boolean notModified;

    public final ByteArrayPool byteArrayPool;
    public final int contentLength;

    // method taken from BasicNetwork with a few small alterations.
    public byte[] toByteArray() throws IOException, ServerError {
        PoolingByteArrayOutputStream bytes =
                new PoolingByteArrayOutputStream(byteArrayPool, contentLength);
        byte[] buffer = null;
        try {

            if (inputStream == null) {
                throw new ServerError();
            }
            buffer = byteArrayPool.getBuf(1024);
            int count;
            while ((count = inputStream.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } finally {
            try {
                // Close the InputStream and release the resources by "consuming the content".
                // Not sure what to do about the entity "consumeContent()"... ideas?
                inputStream.close();
            } catch (IOException e) {
                // This can happen if there was an exception above that left the entity in
                // an invalid state.
                VolleyLog.v("Error occured when calling consumingContent");
            }
            byteArrayPool.returnBuf(buffer);
            bytes.close();
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

然后,为了准备 NetworkResponse,我们需要编辑 BasicNetwork 以正确创建 NetworkResponse(在 内部BasicNetwork.performRequest):

int contentLength = 0;
if (httpResponse.getEntity() != null)
{
     responseContents = httpResponse.getEntity().getContent(); // responseContents is now an InputStream
     contentLength = httpResponse.getEntity().getContentLength();
}

...

return new NetworkResponse(statusCode, responseContents, responseHeaders, false, mPool, contentLength);
Run Code Online (Sandbox Code Playgroud)

就是这样。一旦网络响应中的数据是输入流,我就可以构建自己的请求,该请求可以将其直接解析为文件输出流,该文件输出流仅保存一个小的内存缓冲区。

从一些初步测试来看,这似乎工作正常,不会损害其他组件,但是这样的更改可能需要一些更密集的测试和同行评审,所以我将不将这个答案标记为正确,直到更多人参与进来,或者我认为它足够强大,值得信赖。

请随意对此答案发表评论和/或自己发布答案。这感觉像是 Volley 设计中的一个严重缺陷,如果你看到这个设计的缺陷,或者自己能想到更好的设计,我认为这对每个人都有好处。