无法使用 LibGDX Net 在 Android 上下载 100mb 文件

Posted

技术标签:

【中文标题】无法使用 LibGDX Net 在 Android 上下载 100mb 文件【英文标题】:Can't download a 100mb file on Android with LibGDX Net 【发布时间】:2017-01-24 17:45:08 【问题描述】:

我有一个简单的代码,它向我的外部服务器发送 Http 请求以下载大小为 100mb 的 .txt 文件。较小的文件(如 40mb)可以正常工作,但较大的文件存在一些问题。让我给你看一些代码:

Net.HttpRequest request = new Net.HttpRequest(Net.HttpMethods.GET);
    request.setTimeOut(2500);
    String assetsUrl = "http://111.111.111.111/100mb.txt";
    request.setUrl(assetsUrl);


    // Send the request, listen for the response
    // Asynchronously
    Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() 
        @Override
        public void handleHttpResponse (Net.HttpResponse httpResponse) 


            InputStream is = httpResponse.getResultAsStream();
            OutputStream os = Gdx.files.local("100mb.txt").write(false);

            byte[] bytes = new byte[1024];
            int count = -1;

            try 

                while ((count = is.read(bytes, 0, bytes.length)) != -1) 
                    os.write(bytes, 0, count);
                

             catch (IOException e) 
                e.printStackTrace();
            


        

还有一些代码可以显示进度,但这并不重要。 问题是文件的大小是 100mb,但是 android 在下载时神奇地分配了 400mb+ 的 RAM 并出现错误:

等待阻塞的 GC Alloc

WaitForGcToComplete 因 Alloc 原因阻塞 12.906 毫秒

启动阻塞 GC Alloc

启动阻塞 GC Alloc

暂停所有线程耗时:35.332ms

Alloc 部分并发标记扫描 GC 释放 214(21KB) AllocSpace 对象,1(200MB) LOS 对象,6% 空闲,216MB/232MB,暂停 1.076ms 总计 130.115ms

暂停所有线程耗时:205.400ms

后台粘性并发标记扫描 GC 释放 364(10KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 416MB/416MB, paused 10.448ms 总计 304.325ms

启动阻塞 GC Alloc 启动阻塞 GC Alloc

Alloc 部分并发标记扫描 GC 释放 113(3KB) AllocSpace objects, 0(0B) LOS objects, 3% free, 416MB/432MB, paused 290us total 17.611ms

启动阻塞 GC Alloc

Alloc 粘性并发标记扫描 GC 释放 31(912B) AllocSpace objects, 0(0B) LOS objects, 3% free, 416MB/432MB, paused 268us total 6.474毫秒

启动阻塞 GC Alloc

Alloc 并发标记扫描 GC 释放了 43(13KB) AllocSpace 对象, 0(0B) 个 LOS 对象,3% 空闲,415MB/431MB,暂停 268us 共 15.008ms

强制为 300MB 分配收集 SoftReferences

启动阻塞 GC Alloc

Alloc 并发标记扫描 GC 释放了 42(1256B) 个 AllocSpace 对象, 0(0B) 个 LOS 对象,3% 空闲,415MB/431MB,暂停 286us 共 12.426ms

抛出 OutOfMemoryError "未能分配 314572860 字节 分配 16770608 个空闲字节和 96MB 直到 OOM"

当我运行下载过程时,我可以在我的设备上看到我分配了 1 GB(按系统)和 600 mb 空闲空间,而应用程序使用 20-30 mb。几秒钟后,我的应用程序开始分配越来越多的内存,我可以看到,它使用了 400mb+ 并且崩溃发生在你在日志中看到的最大值时。

也许我不明白,但它不应该只分配所需的 100mb 的 RAM 来存储数据块吗? 我几乎 100% 确定我的应用程序没有泄漏 - 下载过程正在消耗内存(肯定只调用一次)。

【问题讨论】:

【参考方案1】:

你所做的一切看起来都不奇怪,所以我看了一下 LibGDX,尤其是 getResultAsString;最终你会回到 com.badlogic.gdx.utils.StreamUtils.copyStreamToString

    public static String copyStreamToString (InputStream input, int estimatedSize, String charset) throws IOException 
        InputStreamReader reader = charset == null ? new InputStreamReader(input) : new InputStreamReader(input, charset);
        StringWriter writer = new StringWriter(Math.max(0, estimatedSize));
        char[] buffer = new char[DEFAULT_BUFFER_SIZE];
        int charsRead;
        while ((charsRead = reader.read(buffer)) != -1) 
            writer.write(buffer, 0, charsRead);
        
        return writer.toString();
    

这看起来并不可怕,但我的猜测是 StringWriter 出了问题,导致内部重复重新分配数组。看着this answer 似乎证实了我的怀疑。

StringWriter 在内部写入 StringBuffer。 StringBuffer 基本上是一个 char 数组的包装器。那阵有一定的容量。当该容量不足时,StringBuffer 将分配一个新的更大的 char 数组并复制前一个的内容。最后在 StringWriter 上调用 toString(),它会再次将 char 数组的内容复制到结果 String 的 char 数组中。

因此,简而言之,您可能应该找到另一种下载文件的方法。 This question 可能有一些更强大的解决方案。

【讨论】:

以上是关于无法使用 LibGDX Net 在 Android 上下载 100mb 文件的主要内容,如果未能解决你的问题,请参考以下文章

Android游戏框架Libgdx使用入门

无法在 Android Studio (libGDX) 中运行项目

在 Android 上使用来自 libGDX 的 SQLite

Android 脚本层 - LibGDX

LibGDX - 无法从 Android Studio 运行 destop 版本

Libgdx 桌面版本无法编译