大多数内存有效的方式来调整android上的位图?
我正在构build一个图像密集型社交应用程序,将图像从服务器发送到设备。 当设备具有较小的屏幕分辨率时,我需要调整设备上的位图大小以匹配其预期的显示尺寸。
问题是,使用createScaledBitmap会导致我在调整大量的缩略图图像后遇到大量内存不足错误。
什么是最有效的方式来调整Android上的位图?
这个答案总结在载入较大的位图中 ,它解释了如何使用inSampleSize来加载缩小的位图版本。
特别是预缩放位图解释了各种方法的细节,如何组合它们,哪些是最有效的内存。
有三种主要的方式来调整Android的位图具有不同的内存属性:
createScaledBitmap API
此API将采用现有的位图,并创build一个新的位图与您select的确切尺寸。
从好的一面来说,你可以得到你正在寻找的图像大小(不pipe它是怎么样的)。 但缺点是,这个API需要现有的位图才能工作 。 这意味着在创build一个更小的新版本之前,必须先加载,解码和创build一个位图。 就获得您的确切尺寸而言,这是理想的,但在额外的内存开销方面却很糟糕。 因此,对于大多数有记忆意识的应用程序开发人员来说,这是一种交易断路器
inSampleSize标志
BitmapFactory.Options
具有一个以inSampleSize
logging的属性,它将在解码时调整图像的大小,以避免需要解码为临时位图。 此处使用的整数值将以1 / x缩小尺寸加载图像。 例如,将inSampleSize
设置为2将返回大小一半的图像,将其设置为4将返回大小为1/4的图像。 基本上图像大小将永远是比你的源尺寸小一些的幂。
从内存的angular度来看,使用inSampleSize
是一个非常快速的操作。 实际上,它只会将图像的每个第X个像素解码为您的结果位图。 但是inSampleSize
有两个主要的问题:
-
它没有给你确切的决议 。 它只会减less一些2的幂的位图的大小。
-
它不会产生最好的质量调整 。 大多数resize的filter通过读取像素块产生好看的图像,然后对它们进行加权以产生重新resize的像素。
inSampleSize
通过读取每一个像素inSampleSize
避免这一切。 结果是相当高性能,低内存,但质量受到影响。
如果您只处理通过pow2大小缩小图像,并且过滤不是问题,那么您找不到比inSampleSize
更高的内存效率(或性能高效)方法。
inScaled,inDensity,inTargetDensity标志
如果您需要将图像缩放到不等于2的幂的维度,则需要inScaled
, inDensity
和inTargetDensity
标志。 当设置了inScaled
标志时,系统将通过将inTargetDensity
除以inTargetDensity
值来派生缩放值以应用于您的位图。
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);
使用此方法将重新调整图像的大小,并对其应用“resizefilter”,也就是说,最终结果会更好看,因为在resize的步骤中会考虑到一些附加的math运算。 但要注意的是: 额外的过滤步骤,需要额外的处理时间 ,并且可以快速合成大图像,导致调整速度缓慢,并为filter本身分配额外的内存。
由于额外的过滤开销,将此技术应用于比所需大小更大的图像通常不是一个好主意。
魔术组合
从内存和性能的angular度来看,您可以将这些选项结合起来以获得最佳效果。 (设置inSampleSize
, inScaled
, inDensity
和inTargetDensity
标志)
inSampleSize
将首先应用于图像,使其达到比目标尺寸更大的下一个“幂”。 然后,使用inDensity
& inTargetDensity
将结果缩放到所需的精确尺寸,应用滤镜操作来清理图像。
结合这两者是一个更快的操作,因为inSampleSize
步骤将减less所得到的基于密度的步骤将需要应用其resizefilter的像素的数量。
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
标志来帮助您获取图像的尺寸, 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
您可以使用此标志首先解码大小,然后计算适合您的目标分辨率的缩放比例。
像这个答案一样好(而且准确),这也是非常复杂的。 不要重新发明轮子,而要考虑像Glide , Picasso , UIL , Ion或其他任何实现这种复杂且容易出错的逻辑的库。
Colt自己甚至build议在预缩放位图性能模式video中查看Glide和Picasso。
通过使用库,您可以获得Colt答案中提到的每一点效率,但是具有在各个Android版本中始终如一地运行的API非常简单。