我正在构建一个图像密集型社交应用,其中图像从服务器发送到设备。当设备具有较小的屏幕分辨率时,我需要调整设备上的位图大小以匹配其预期的显示大小。
问题是使用 createScaledBitmap 会导致我在调整大量缩略图图像后遇到大量内存不足错误。
在Android上调整位图大小的最有效内存方法是什么?
答案 0 :(得分:161)
此答案摘自Loading large bitmaps Efficiently 这解释了如何使用inSampleSize加载缩小的位图 版本
特别是Pre-scaling bitmaps解释了各种细节 方法,如何组合它们,哪些是最有效的内存。
在Android上调整具有不同内存属性的位图有三种主要方法:
此API将采用现有位图,并使用您选择的确切尺寸创建新位图。
从好的方面来说,你可以准确地得到你正在寻找的图像尺寸(无论它看起来如何)。但缺点是,是这个API需要现有的位图才能工作。这意味着在创建新的较小版本之前,必须加载,解码图像并创建位图。这在获得精确尺寸方面是理想的,但在额外的内存开销方面却很糟糕。因此,对于大多数倾向于记忆的应用程序开发人员而言,这是一种交易破坏者
<强> inSampleSize flag 强>
BitmapFactory.Options
有一个标记为inSampleSize
的属性会在解码时调整图像大小,以避免需要解码到临时位图。此处使用的此整数值将以1 / x缩小的尺寸加载图像。例如,将inSampleSize
设置为2将返回尺寸减半的图像,将其设置为4将返回尺寸为1/4的图像。基本上,图像尺寸总是比源尺寸小一些。
从内存的角度来看,使用inSampleSize
是一个非常快速的操作。实际上,它只会将图像的每个第X个像素解码为生成的位图。但inSampleSize
存在两个主要问题:
它没有给出确切的分辨率。它只会将位图的大小减小2倍。
它不会产生最佳质量调整大小。大多数调整大小的滤镜通过读取像素块产生漂亮的图像,然后对它们进行加权以产生所讨论的调整大小的像素。 inSampleSize
只需读取每几个像素就可以避免这一切。结果非常高效,内存不足,但质量却受到影响。
如果您只是处理以某种pow2尺寸缩小图像,并且过滤不是问题,那么您找不到比inSampleSize
更高效的内存(或性能效率)方法。< / p>
<强> inScaled, inDensity, inTargetDensity flags 强>
如果您需要将图片缩放到不等于2的幂,那么您需要{{1}的inScaled
,inDensity
和inTargetDensity
标记}}。设置BitmapOptions
标志后,系统会通过将inScaled
除以inTargetDensity
值来派生缩放值以应用于您的位图。
inDensity
使用此方法会重新调整图像大小,并对其应用“调整大小过滤器”,也就是说,最终结果看起来会更好,因为在调整大小步骤期间会考虑一些额外的数学运算。但要注意:额外的过滤步骤,需要额外的处理时间,并且可以快速添加大图像,导致调整缓慢,并为过滤器本身额外的内存分配。
由于额外的过滤开销,将此技术应用于远大于所需大小的图像通常不是一个好主意。
魔术组合
从内存和性能角度来看,您可以将这些选项组合在一起以获得最佳效果。 (设置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);
,inSampleSize
,inScaled
和inDensity
标记)
inTargetDensity
将首先应用于图像,使其达到下一个比目标尺寸更大的两倍。然后,inSampleSize
&amp; 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);
标记来帮助您获取图像的尺寸,而无需实际解码像素数据。
inJustDecodeBounds
您可以使用此标志首先解码大小,然后计算适当的值以缩放到目标分辨率。
答案 1 :(得分:12)
尽管这个答案很好(也很准确),但它也很复杂。而不是重新发明轮子,考虑像Glide,Picasso,UIL,Ion或其他任何为您实现这种复杂且容易出错的逻辑的库。 。
柯尔特本人甚至建议在Pre-scaling Bitmaps Performance Patterns Video中看看格莱德和毕加索。
通过使用库,您可以获得Colt答案中提到的所有效率,但是使用非常简单的API可以在每个Android版本中保持一致。