大多数内存有效的方法来重新调整android上的位图?

Col*_*lis 114 performance android bitmap out-of-memory

我正在构建一个图像密集型社交应用程序,其中图像从服务器发送到设备.当设备具有较小的屏幕分辨率时,我需要调整设备上的位图大小以匹配其预期的显示大小.

问题是使用createScaledBitmap会导致我在调整大量缩略图图像后遇到大量内存不足错误.

什么是在Android上调整位图大小最有效的内存方式?

Col*_*lis 164

这个答案总结从加载大位图有效地 解释了如何使用inSampleSize来加载缩小的位图版本.

特别是预缩放位图解释了各种方法的细节,如何组合它们,以及哪些是最节省内存的.

在Android上调整具有不同内存属性的位图有三种主要方法:

createScaledBitmap API

此API将接收现有位图,并使用您选择的确切尺寸创建新位图.

从好的方面来说,您可以准确地获得您正在寻找的图像尺寸(无论它看起来如何).但缺点是,此API需要现有的位图才能工作.这意味着在创建新的较小版本之前,必须加载,解码图像并创建位图.这在获得精确尺寸方面是理想的,但在额外的内存开销方面却很糟糕.因此,对于大多数倾向于记忆的应用程序开发人员来说,这是一种交易破坏者

inSampleSize标志

BitmapFactory.Options有一个注明的属性inSampleSize,它将在解码时调整图像大小,以避免解码到临时位图.此处使用的此整数值将以1/x缩小的尺寸加载图像.例如,设置inSampleSize为2将返回一半大小的图像,将其设置为4将返回大小为1/4的图像.基本上,图像尺寸总是比源尺寸小一些.

从内存的角度来看,使用inSampleSize是一种非常快速的操作.实际上,它只会将图像的每个第X个像素解码为生成的位图.但是有两个主要问题inSampleSize:

  • 它没有给你准确的分辨率.它只会将位图的大小减小2倍.

  • 它不会产生最佳质量的调整大小.大多数调整大小的滤镜通过读取像素块产生漂亮的图像,然后对它们进行加权以产生所讨论的调整大小的像素.inSampleSize通过读取每几个像素来避免所有这些.结果非常高效,内存不足,但质量却受到影响.

如果你只是处理缩小你的图像的一些pow2大小,并且过滤不是问题,那么你找不到比内存更有效(或性能更高)的方法inSampleSize.

inScaled,inDensity,inTargetDensity标志

如果您需要将图像缩放到的尺寸,这不是等于2的幂,那么你需要的inScaled,inDensityinTargetDensity旗帜BitmapOptions.当inScaled标志已设置时,系统会得出缩放值除以适用于您的位图inTargetDensityinDensity值.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);
Run Code Online (Sandbox Code Playgroud)

使用此方法将重新调整图像大小,并对其应用"调整大小过滤器",也就是说,最终结果看起来会更好,因为在调整大小步骤期间已考虑了一些额外的数学运算.但要注意:额外的过滤步骤,需要额外的处理时间,并且可以快速添加大图像,导致调整缓慢,并为过滤器本身提供额外的内存分配.

由于额外的过滤开销,将此技术应用于远大于所需大小的图像通常不是一个好主意.

魔术组合

从内存和性能角度来看,您可以组合这些选项以获得最佳结果.(设置inSampleSize,inScaled,inDensityinTargetDensity标志)

inSampleSize将首先应用于图像,使其达到下一个比目标尺寸更大的功率.然后,inDensity&inTargetDensity用于将结果缩放到所需的精确尺寸,应用滤镜操作来清理图像.

将这两者结合起来是一个快得多的操作,因为该inSampleSize步骤将减少基于密度的步骤所需的像素数,以应用它的调整大小过滤器.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
Run Code Online (Sandbox Code Playgroud)

如果您需要将图像适合特定尺寸更好的过滤,那么这种技术是获得正确尺寸的最佳桥梁,但是在快速,低内存占用操作中完成.

获取图像尺寸

在不解码整个图像的情况下获取图像大小为了调整位图大小,您需要知道传入的尺寸.您可以使用该inJustDecodeBounds标志来帮助您获取图像的尺寸,而无需实际解码像素数据.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want
Run Code Online (Sandbox Code Playgroud)

您可以使用此标志首先解码大小,然后计算适当的值以缩放到目标分辨率.


Sam*_*udd 13

尽管这个答案很好(也很准确),但它也很复杂.不要重新发明轮子,而应考虑像Glide,Picasso,UIL,Ion或其他任何为您实现这种复杂且容易出错的逻辑的库.

Colt本人甚至建议在预缩放位图性能模式视频中查看Glide和Picasso .

通过使用库,您可以获得Colt答案中提到的每一点效率,但是使用非常简单的API可以在每个Android版本中保持一致.