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

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

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

什么是最有效的方式来调整Android上的位图?

这个答案总结在载入较大的位图中 ,它解释了如何使用inSampleSize来加载缩小的位图版本。

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

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

createScaledBitmap API

此API将采用现有的位图,并创build一个新的位图与您select的确切尺寸。

从好的一面来说,你可以得到你正在寻找的图像大小(不pipe它是怎么样的)。 但缺点是,这个API需要现有的位图才能工作 。 这意味着在创build一个更小的新版本之前,必须先加载,解码和创build一个位图。 就获得您的确切尺寸而言,这是理想的,但在额外的内存开销方面却很糟糕。 因此,对于大多数有记忆意识的应用程序开发人员来说,这是一种交易断路器

inSampleSize标志

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

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

  • 它没有给你确切的决议 。 它只会减less一些2的幂的位图的大小。

  • 它不会产生最好的质量调整 。 大多数resize的filter通过读取像素块产生好看的图像,然后对它们进行加权以产生重新resize的像素。 inSampleSize通过读取每一个像素inSampleSize避免这一切。 结果是相当高性能,低内存,但质量受到影响。

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

inScaled,inDensity,inTargetDensity标志

如果您需要将图像缩放到不等于2的幂的维度,则需要inScaledinDensityinTargetDensity标志。 当设置了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度来看,您可以将这些选项结合起来以获得最佳效果。 (设置inSampleSizeinScaledinDensityinTargetDensity标志)

inSampleSize将首先应用于图像,使其达到比目标尺寸更大的下一个“幂”。 然后,使用inDensityinTargetDensity将结果缩放到所需的精确尺寸,应用滤镜操作来清理图像。

结合这两者是一个更快的操作,因为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非常简单。