在安卓设备上旋转图片,是否有更好的方法?

10

我有一个应用程序需要向用户展示很多图片,但我们一直收到很多OutOfMemoryError异常的错误报告。

目前我们的做法是这样的:

// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
image.setImageBitmap(bmp);

显而易见的问题是,我们必须从内存中的图像重新创建位图并旋转矩阵,这对于内存来说相当昂贵。

我的问题很简单:

有没有更好的方法旋转图像而不会引起OutOfMemoryError


http://www.twintechs.com/2008/06/frame-by-frame-xml-animation-with-google-android/ - Nikunj Patel
抛出异常的代码在哪一行? - ingsaurabh
@Dr.nik,所以您建议我使用动画旋转图像,即使它不应该是动画旋转吗? - Draiken
Bitmap.createBitmap中,有可能超出虚拟机允许的内存限制,从而引发错误。 - Draiken
我已经更新了我的答案。现在它有一个更好的解决方案。 - android developer
3个回答

6

旋转大图像的两种方法:

  1. 使用JNI,例如此帖子中所述。

  2. 使用文件:这是一种非常缓慢的方式(取决于输入和设备,但仍然非常缓慢),它首先将解码后的旋转图像放入磁盘中,而不是放入内存中。

使用文件的代码如下:

private void rotateCw90Degrees()
  {
  Bitmap bitmap=BitmapFactory.decodeResource(getResources(),INPUT_IMAGE_RES_ID);
  // 12 => 7531
  // 34 => 8642
  // 56 =>
  // 78 =>
  final int height=bitmap.getHeight();
  final int width=bitmap.getWidth();
  try
    {
    final DataOutputStream outputStream=new DataOutputStream(new BufferedOutputStream(openFileOutput(ROTATED_IMAGE_FILENAME,Context.MODE_PRIVATE)));
    for(int x=0;x<width;++x)
      for(int y=height-1;y>=0;--y)
        {
        final int pixel=bitmap.getPixel(x,y);
        outputStream.writeInt(pixel);
        }
    outputStream.flush();
    outputStream.close();
    bitmap.recycle();
    final int newWidth=height;
    final int newHeight=width;
    bitmap=Bitmap.createBitmap(newWidth,newHeight,bitmap.getConfig());
    final DataInputStream inputStream=new DataInputStream(new BufferedInputStream(openFileInput(ROTATED_IMAGE_FILENAME)));
    for(int y=0;y<newHeight;++y)
      for(int x=0;x<newWidth;++x)
        {
        final int pixel=inputStream.readInt();
        bitmap.setPixel(x,y,pixel);
        }
    inputStream.close();
    new File(getFilesDir(),ROTATED_IMAGE_FILENAME).delete();
    saveBitmapToFile(bitmap); //for checking the output
    }
  catch(final IOException e)
    {
    e.printStackTrace();
    }
  }

@android_developer,我很喜欢你的文件解决方案,我认为它值得花时间来避免所有的内存问题。 - vvbYWf0ugJOGNA3ACVxp
@PeterFile 我认为你应该采用多种回退方案,从最快(但最危险)到最慢(但最安全):首先尝试使用正常方式(使用堆)。如果失败(内存不足),则使用JNI解决方案。如果失败(内存不足),则使用存储解决方案。存储解决方案也可能失败(存储不足),因为创建的文件是解码后的位图,而不是压缩的位图,但这可能是一个罕见的情况。 - android developer
@androiddeveloper 你的解决方案是否保持原始图像的大小和质量? - Yoann Hercouet
@YoannHercouet 是的,因为我甚至没有将其编码为压缩格式。 - android developer

0

你可以尝试:

image.setImageBitmap(null);
// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
BitmapDrawable bd = new BitmapDrawable(mContext.getResources(), bmp);
bmp.recycle();
bmp = null;
setImageDrawable(bd);
bd = null;

正如问题中的一条评论所述,错误发生在Bitmap.createBitmap行。 - Draiken

0
在处理大量位图时,请确保在它们不再需要时立即调用recycle()。此调用将立即释放与特定位图相关联的内存。
如果您在旋转后不需要原始位图,则可以回收它。例如:
```java if (!needOriginalBitmap) { originalBitmap.recycle(); } ```
Bitmap result = bmp;

// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
    // Rotate it to show as a landscape
    Matrix m = image.getImageMatrix();
    m.postRotate(90);
    result = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
    // rotating done, original not needed => recycle()
    bmp.recycle();
}

image.setImageBitmap(result);

不错,我之前不知道这个。问题是我需要原始位图来旋转它,但错误发生在我可以将其从内存中释放之前 :/ - Draiken
嗯。Bitmap.createBitmap() 应该创建全新的 Bitmap,因此释放原始的应该是安全的。实际上,我在自己的代码中经常使用这种方法,它运行得非常好。你确定你正在回收正确的位图,并且只有在将 createBitmap() 返回的旋转位图分配给其他引用(例如我的示例)之后才回收它吗?如果您已经使用完所有内存,则可能会在 createBitmap() 中抛出错误。但是,如果每次您都使用 recycle() 正确释放它,那么在下一次调用时应该没问题... - dimsuz
当然,如果它在第一次调用时崩溃,那么我的答案就没用了。这将意味着当您使用createBitmap()进行分配时,内存已经几乎满了,无法容纳新对象。我的答案适用于需要多次旋转多个位图的情况-然后回收一定会有帮助 :) - dimsuz
嗯,奇怪,它真的不应该是这样的 - 我甚至检查了Bitmap类的源代码 - 它确实创建了新的Bitmap,在你的情况下没有浅复制。而且你是否使用“result”作为参数调用了setImageBitmap(),而不是“bmp”? :) - dimsuz
当然,在创建新的旋转位图之前,他无法回收旧的位图,因此在某个时候必须加载两个位图,但他可以在之后进行回收,但到那时程序会崩溃。 - Dan Levin
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接