【问题标题】:Android - Rotate Image avoiding OutOfMemoryAndroid - 旋转图像避免 OutOfMemory
【发布时间】:2013-08-24 15:54:50
【问题描述】:

下面的代码会导致 OutOfMemory 吗?我认为它允许超过应用程序内存限制。

Matrix matrix = new Matrix();
matrix.postRotate(orientation);

image = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), matrix, true);

在 Android 中旋转图像的最佳方式是什么?

在新进程的服务中分配它以获得更多堆?

@CommonsWare 在此链接 [1] 中表示,许多开发人员认为更多的堆是低效编码的解决方案。

这个问题也表示大堆[2]。

有没有更简单的解决方案?

[1]Can you start an IntentService on a separate process?

[2]How to avoid OutOfMemory ex while rotating the image?

【问题讨论】:

  • 处理图像一直是一项有风险的业务,我的游戏应用也遇到过问题,如果你还没有,你可能想看看这个,已经developer.android.com/training/displaying-bitmaps/…
  • 对于 3.x 及更高版本的设备,只需采取合理措施,不要保留大量不需要的位图。在 2.x 设备上没有简单的解决方案 - 您必须非常严格地管理您的位图引用和回收。
  • 如果要旋转源自 yuv 格式的图像(例如相机预览或视频帧解码),将节省内存。如果您的来源是 Jpeg(例如来自相机的 takePicture),则可以更快地旋转它。如果您也需要 Jpeg 进行输出,则可以使旋转无损。
  • 在这个位置:" ... Bitmap.createBitmap(..)" 你将同时拥有两个位图图像。原始和新的,所以如果你想保持新图像的质量,我认为最好的解决方案是在清单文件的应用程序标记中添加“android:largeHeap="true""。其他解决方案是使用一些 NDK 库,如果您想保持质量,这些库可能会或可能不会帮助您。

标签: android image-processing bitmap heap-memory


【解决方案1】:

简短的回答是,是的,此代码可能会导致 OutOfMemory。我认为没有比增加应用程序堆大小更简单的解决方案了。我相信@CommonsWare 是对的,而且 OutOfMemory 通常表明编程错误。但是在某些情况下,您需要,嗯,巨大的内存。旋转巨大的图像绝对是其中一种情况。

您可以使用本机代码 (NDK) 而不是要求增加堆大小,但这绝对不是一件容易的事。而且它仍然需要大量内存,因此使用 C++ 没有任何优势(除了它适用于 2.3)。

【讨论】:

  • 我添加了一个 NDK 解决方案。看看我的回答。
  • 使用 NDK/C++ 有一些优势:代码运行速度更快,它可以使用比 SDK 应用程序更多的 RAM。现在的设备可能有 1-2Gb RAM,但 SDK 仍将每个进程的 RAM 使用限制为 16Mb。
【解决方案2】:

当您的位图太大而无法加载到内存中时抛出 OutOfMemoryException。

这里我给你一个解决方案。

BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap=BitmapFactory.decodeStream(is,null,options);

使用 BitmapFactory.Options 类的 inSampleSize 属性。

如果设置的值 > 1,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。样本大小是对应于解码位图中的单个像素的任一维度中的像素数。例如,inSampleSize == 4 返回的图像是原始宽度/高度的 1/4,像素数的 1/16。任何

//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
try {
    //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(new FileInputStream(f),null,o);

    //The new size we want to scale to
    final int REQUIRED_SIZE=70;

    //Find the correct scale value. It should be the power of 2.
    int scale=1;
    while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
        scale*=2;

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize=scale;
    return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}

【讨论】:

  • 此解决方案由于其主要思想而导致质量损失 - 使用较小的图像尺寸而不是原始尺寸以避免OOM。当您需要在旋转时保持原始图像大小和质量时,它就变得毫无用处了。
【解决方案3】:

如果您希望使用基于 NDK 的解决方案,我创建了一个 here,并创建了一个 github 项目 here

这将通过将数据放入本机 C“世界”、回收旧数据并在轮换后返回结果来避免 OOM。

它不需要任何下采样。

【讨论】:

  • 您能否将已编译的本机库添加到您的 github(就像 ImageMagick 开发人员所做的那样)?因为现在编译它确实是一个大问题,因为对于 Windows,它需要 cygwin 并且整个过程对于 SDK 开发人员来说太复杂了。谢谢。
  • @Stan 通过windows编译就可以了,不需要cygwin,只需要NDK。 Cygwin 你可能想要调试,但我不知道如何为 Android 做。我已经准备好有关如何编译项目的说明。对我来说,它适用于 Eclipse。其他人成功地为 Android-Studio 这样做了,并发布了他的说明。
【解决方案4】:

这里:

image = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), matrix, true);

代码操作两个位图,第一个image 是一个Bitmap,您之前已加载/解码到 RAM 中以使用 - 实际旋转。无论您将结果存储到同一个变量中,第二个都将由 Bitmap.createBitmap() 创建。无论如何,在这一行,您需要位图 x2 RAM,这肯定会导致 OOM(说到设备的相机最大可能的照片)。
我认为这里使用 NDK 是最好的解决方案。
请查看my very same question here 以获取其他可能的解决方案 (MappedByteBuffer),它还包含指向 NDK/JNI 解决方案的链接。

【讨论】:

    猜你喜欢
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-26
    • 1970-01-01
    相关资源
    最近更新 更多