【问题标题】:Android: Working with bitmaps without OutOfMemoryErrorAndroid:使用没有 OutOfMemoryError 的位图
【发布时间】:2014-10-26 16:31:12
【问题描述】:

首先是重要的代码行:

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), getDrawableForLvl(drawable));
int []pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
//..
pixels = null; // please gc remove this huge data
System.gc();

所以我正在开发一个具有多个级别的 Android 应用(游戏)。每个级别都是我在级别开始时作为位图加载的图像。然后我必须分析每个像素(第 3 行)。为此,我创建了一个数组(第 2 行)。图像为 1280x800,因此数组的大小超过一百万个整数。这里没有问题,因为它是一个局部变量,它应该在方法返回时被销毁。但它是 java,所以它不是 -.- 取决于设备,垃圾收集器是否及时运行得足够快。因此,当用户非常快速地启动和关闭关卡时,它会在第 2 行产生 java.lang.OutOfMemoryError。我猜是因为旧数组还没有/没有被删除,现在我有多个填充内存。

我可以使数组成为静态成员。因此,它创建一次并且始终可用。那么不可能有它的多个实例。但我认为这是一种糟糕的编码风格,因为它在不需要时也可用 (4 MB)。

我不需要同时使用所有像素。我将图像分割成一百多个矩形,所以我可以使用更小的数组并用像素一个接一个地填充它。但是我对方法参数can someone help me here的理解有问题? 还有一种方法可以只获取one pixel at position x,y,但我想一百万个函数调用很慢。

有人有更好的主意吗?在java中没有办法强制对象超出内存,是吗?


更新1: 正如 vzoha 建议的那样,只获得第一季度:

int []pixels = new int[bitmap.getWidth()/2*bitmap.getHeight()/2];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2);

给出一个 ArrayIndexOutOfBound。我认为函数调用只是获取第一季度的像素,但仍希望完整大小的数组和其他字段保持不变。

Update2:我想我可以逐行进行(第一季度半行半行):

int []pixels = new int[bitmap.getWidth()/2*bitmap.getHeight()/2];
for(int row = 0; row < bitmap.getHeight()/2; ++row)
    bitmap.getPixels(pixels, bitmap.getWidth()/2, bitmap.getWidth(), 0, row, bitmap.getWidth()/2, 1);

但是,当我为 20x10 部分执行此操作时,它并不比单独获取每个像素好多少。好吧,它要好得多,但是该方法仍然应该能够通过一次调用来做到这一点,不是吗?我只是没有得到这个“步幅”参数:“在行之间跳过的像素 [] 中的条目数(必须 >= 位图的宽度)。可以是负数。” >= width 怎么会是负数?

【问题讨论】:

  • 我也遇到了位图和 outOfMemoryError 的问题。尝试观察 DDMS 中的堆是否在增加,或者在不使用位图后减少。对我来说,帮助拨打了bitmap.recycle(); bitmap = null;,但我没有使用getPixels。试试看,让我记下。
  • 好吧,错误在于巨大数组的声明。但我猜 Bitmap 的内存大小应该更大,所以这是一个很好的建议。当一个关卡关闭时,我会尝试回收位图,所以它应该是下一个关卡的更大的可用内存缓冲区。 bitmap.recycle 是我需要的数组,因为我不能依赖垃圾收集器来运行它。
  • 我刚刚注意到:回收不会释放像素数据,它只是允许它被垃圾收集。所以这并没有什么区别,因为我只在放弃保存位图的对象时才使用回收,所以无论我是否调用回收,它都可用于 GC。
  • 好的,对我来说,在某些设备上也有帮助 imageView.setImageDrawable(null);,但这不是你的问题。正如您所说,您可以将bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); 用于部分图像。我认为,第一季度应该是:bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2);,第二季度:bitmap.getPixels(pixels, 0, bitmap.getWidth(), bitmap.getWidth()/2, 0, bitmap.getWidth(), bitmap.getHeight()/2); 等等。
  • 这就是我已经尝试过的,因为它完全有道理,但它不起作用。 (我将其添加到我的问题描述中)

标签: android arrays bitmap pixel


【解决方案1】:

以像素为单位的大小并不直接转化为图像在内存中占用的内存量。 Android 中的位图(在使用 ART 之前)是出了名的难以大量使用,同时避免了 OOM 异常,足够了,there's a page dedicated to how to use them efficiently。问题通常是实际上有足够的可用内存,但它已变得碎片化,并且没有单个连续块您需要的大小可用。

我的第一个建议(为了快速获胜)是使用特定配置对位图进行解码,通过切换到使用 ALPHA_8 for your bitmap config,您应该只能占用之前使用的内存量的 1/4。这将使用每个像素 1 个字节而不是 4 个(默认为 ARGB_8888)

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8
bitmap = BitmapFactory.decodeResource(getResources(), getDrawableForLvl(drawable), options);

我的下一个建议是先缩放位图,然后将适当的位图放在 hdpi、xhdpi、xxhdpi 文件夹中。

【讨论】:

  • 你的最后一句话是一个很好的建议,因为 OOM 错误很可能发生在没有显示分辨率的低端设备上。但是APK大小..
  • TBH,ARGB_8888 和 ALPHA_8 之间没有太大区别,我会说这是一个不错的首选。也就是说,我没有太多的设计眼光;)
  • 我的大部分 PNG 已经保存为 8 位,具有 Alpha 透明度以减小 APK 大小。但是 getgetPixels 方法给了我 32 位,每个 ARGB 通道一个字节。我需要这些通道来确定透明度有多高以及颜色有多亮或多暗。我不知道这是否适用于 8 位位图。但是感谢您减少位图大小的建议,但是在您回答 5 分钟后,我自己写了一个答案,因为我终于想出了如何只获取一部分像素,所以数组现在非常小,我认为这就足够了,但再次感谢。
  • 好的,很高兴您找到了解决方案。我会把它留在这里,以防它帮助别人。
  • 只是一个简短的提醒。 getPixel 的东西现在就像一个魅力,但在真正滞后的设备上,我仍然在 BitmapFactory.decode 上遇到 OOM 错误。所以我记住了你的建议,并将所有位图切换为在 alpha8 中解码。 getPixel 方法仍然给我 argb32,所以我的算法无需修改即可工作。我猜 getPixel 方法现在需要更多时间将 alpha8 转​​换为 argb32,但以前需要在 decode 方法中完成,因为我的 PNG 保存在 alpha8 中。所以更短的解码但更长的 getPixels == 没有变化。除了我使用内存的四分之一。再次感谢。
【解决方案2】:

事实上,仅获取特定区域的像素非常简单。 But the documentation is wrong about the stride parameter。它说:

public void getPixels(int[] 像素,int offset,int stride,int x,int y,int width,int height)

stride:在行之间跳过的以像素[] 为单位的条目数(必须 >= 位图的宽度)。可以为负数。

但事实是它可以(并且在大多数情况下必须)小于位图的宽度,它必须大于或等于传递的宽度(getPixels 的倒数第二个参数),这意味着我想要像素的区域。

所以要获取位图第一季度的像素:

int []pixels = new int[bitmap.getWidth()>>1 * bitmap.getHeight()>>1]; // quarter of the bitmap size
bitmap.getPixels(pixels, 0, bitmap.getWidth()>>1, 0, 0, bitmap.getWidth()>>1, bitmap.getHeight()>>1);

或者用x,y(左上角)和宽度,高度来获得一个特定的矩形:

int []pixels = new int[width*height];
bitmap.getPixels(pixels, 0, width, x, y, width, height);

非常简单。只是错误的文档让我大吃一惊。

【讨论】:

    猜你喜欢
    • 2023-03-20
    • 2011-10-22
    • 1970-01-01
    • 2014-05-28
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多