【问题标题】:Loading large bitmaps to ImageView in ViewPager - out of memory将大位图加载到 ViewPager 中的 ImageView - 内存不足
【发布时间】:2013-01-18 09:50:03
【问题描述】:

我有 ViewPager 用于显示可缩放图像(使用 ImageViewTouch)。 我需要从 Internet (http) 加载大型位图。大我的意思是2000x1000。图像需要很大,因为它们是可缩放的并且需要显示细节。服务器上的图片是.jpg格式的,不过没问题-我可以改。

如何将这么大的图像加载到 ImageViewTouch (ImageView) 而不会出现内存问题?

现在我只使用这个(AsyncTask):

    ImageView currentView; //ImageView where to place image loaded from Internet
    String ImageUrl; //URL of image to load

    protected Bitmap doInBackground(ArrayList... params) {

            Bitmap bitmap;
            InputStream in = null;
            imageUrl = (String)params[0].get(1);

            try{
                HttpClient httpclient = new DefaultHttpClient();
                HttpResponse response = httpclient.execute(new HttpGet(imageUrl));
                in = response.getEntity().getContent();
            } catch(Exception e){
                e.printStackTrace();
            }
            try {
                bitmapa = BitmapFactory.decodeStream(in);
                in.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            return bitmap;
        }


        @Override
        protected void onPostExecute(Bitmap bitmap) {

            currentView.setImageBitmap(bitmap);
        }

它会导致很多内存问题:

E/dalvikvm-heap(369): 69560740-byte external allocation too large for this process.
E/GraphicsJNI(369): VM won't let us allocate 69560740 bytes

E/AndroidRuntime(369): java.lang.RuntimeException: An error occured while executing doInBackground()
E/AndroidRuntime(369):  at android.os.AsyncTask$3.done(AsyncTask.java:200)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask.run(FutureTask.java:138)
E/AndroidRuntime(369):  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
E/AndroidRuntime(369):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
E/AndroidRuntime(369):  at java.lang.Thread.run(Thread.java:1019)
E/AndroidRuntime(369): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:470)
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:525)
E/AndroidRuntime(369):  at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:78)
E/AndroidRuntime(369):  at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:1)
E/AndroidRuntime(369):  at android.os.AsyncTask$2.call(AsyncTask.java:185)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
E/AndroidRuntime(369):  ... 4 more

【问题讨论】:

标签: android bitmap imageview out-of-memory bitmapfactory


【解决方案1】:

您在哪个版本的操作系统上进行测试?在早期版本中,可用的堆空间远低于更高版本。

我会认真考虑缩小位图的大小以避免这种情况。如果您正在为 mdpi 手机下载 2000x1000 Bitmap,那么这可能是个坏主意。

我还建议阅读this article,了解有关如何根据其尺寸为特定ImageView 准确加载适当图像的更多信息。

最后,您应该始终在 finally 块中关闭 InputStream

try {
        bitmap = BitmapFactory.decodeStream(in);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (in != null) { in.close(); }
    }

您还可以考虑android:largeHeap="true",专为处理大型位图的应用程序而设计,例如照片编辑应用程序。

【讨论】:

  • 感谢您的回答。我的目标 SDK 版本是 15,最低版本是 4。我已经阅读了那篇文章,但它对我没有帮助 - 这里的问题是我无法调整图像大小或缩放图像,因为它必须保持可缩放并显示详细信息放大。
  • 我不会骗你,你不会找到一个可能的答案来适应内存中的 2000x1000 图像。想想看,无论你做什么,你都会超过可用的堆空间。也许你可以找到一种不同的策略,类似于 Chrome 网络浏览器的工作方式,只在内存中渲染部分图像,因为用户在你周围平移时会根据当前视口快速重新加载新窗口。
  • 我想过 - 重新加载图像的缩放部分 - 但它看起来很难实现。你有它的教程吗?谢谢!
  • 您可以查看 Gallery 应用程序源代码并了解它们的作用。那些来自手机摄像头的图像相当大,所以也许这是一个很好的起点!
【解决方案2】:

2000x1000 像素的图像很大,但不是那么大。

我明白了,虽然进程在死前尝试分配 69560740 字节,但大约是 66MByte!

在最坏的情况下,您的 2000x1000 约为 2000x1000x4 = 8MBytes,远小于 66MBytes。其他事情正在发生。

另外,我发现在 AsyncTasks 中使用位图作为返回/结果值存在问题。已完成的 AsyncTask 仍会保留其结果,直到它们被垃圾回收。由于您不控制 AsyncTask 实例的垃圾收集,因此请勿使用 Bitmap 作为返回/结果值。而是将Bitmap放在一个字段中,并在doInBackground即将结束时分配它,并使用该字段在onPostExecute中获取Bitmap并(!)将此字段设置为null:

Bitmap bitmap;
protected Void doInBackground(ArrayList... params) {

        InputStream in = null;
        imageUrl = (String)params[0].get(1);

        try{
            HttpClient httpclient = new DefaultHttpClient();
            HttpResponse response = httpclient.execute(new HttpGet(imageUrl));
            in = response.getEntity().getContent();
        } catch(Exception e){
            e.printStackTrace();
        }
        try {
            bitmap = BitmapFactory.decodeStream(in);
            in.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        return null;
    }


    @Override
    protected void onPostExecute(Void unused) {
        if (bitmap != null) {
            currentView.setImageBitmap(bitmap);
        }

        // And don't forget to null the bitmap field!
        bitmap = null; 
    }

【讨论】:

  • 作为我之前回答的额外评论。我认为您的服务器返回非常非常大的图像,因为 VM 尝试分配 69,500,000 字节。这将是几乎 3000x3000 像素的 ARGB_8888 图像。
  • 这个解决方案确实提高了我的内存使用率。我只是按照你说的做,从“另外,我发现在 AsyncTasks 中使用位图作为返回/结果值存在问题......”我按照你说的修改了我的代码,现在我可以在 x2 左右显示多倍图像而不会崩溃。谢谢
猜你喜欢
  • 2015-07-29
  • 1970-01-01
  • 2013-09-19
  • 2012-10-10
  • 1970-01-01
  • 1970-01-01
  • 2012-07-27
  • 2014-01-11
  • 1970-01-01
相关资源
最近更新 更多