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


115

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

问题是使用createScaledBitmap导致我在调整成组的缩略图图像后遇到很多内存不足错误。

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


7
您的服务器能否发送不正确的大小,以便节省客户的RAM和带宽!?
2015年

2
仅当我拥有服务器资源,它具有可用的计算组件时才有效,并且在所有情况下,它都可以针对尚未看到的宽高比预测图像的确切尺寸。因此,如果您正在从第三方CDN加载资源内容(例如我),则它不起作用:(
Colt McAnlis

Answers:


168

从“有效加载大型位图”中可以总结出此答案 ,该说明说明了如何使用inSampleSize加载缩小的位图版本。

特别是,预缩放位图详细说明了各种方法,如何组合它们以及哪种方法最节省内存。

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

createScaledBitmap API

该API将采用现有位图,并使用您选择的确切尺寸创建新的位图。

从好的方面来说,您可以确切地找到想要的图像大小(无论外观如何)。但是缺点是,此API需要一个现有的位图才能起作用。这意味着必须先加载,解码图像并创建位图,然后才能创建新的较小版本。就获得确切的尺寸而言,这是理想的选择,但在额外的内存开销方面却是可怕的。因此,对于大多数倾向于内存意识的应用程序开发人员来说,这是一个交易突破点

inSampleSize标志

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

从内存的角度来看,使用inSampleSize是一个非常快的操作。实际上,它只会将图像的每个第X个像素解码为最终的位图。inSampleSize尽管存在两个主要问题:

  • 它没有给您确切的分辨率。它只会将位图的大小减小2的幂。

  • 它不会产生最佳的质量调整大小。大多数调整大小的滤镜会通过读取像素块,然后对其进行加权以生成所讨论的调整大小的像素来生成美观的图像。inSampleSize仅读取每几个像素就可以避免所有这些情况。结果是相当不错的性能,并且内存不足,但是质量受到了影响。

如果您只想将图片缩小至pow2大小,而过滤不是问题,那么您找不到比更高的内存效率(或性能效率)方法inSampleSize

inScaled,inDensity,inTargetDensity标志

如果您需要将图像缩放到,这不是等于二的幂的尺寸,那么你需要的inScaledinDensityinTargetDensity旗帜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);

使用此方法将调整图像的大小,并对其应用“调整大小的滤镜”,即最终结果看起来会更好,因为在调整大小步骤中已考虑了一些附加的数学运算。但请注意:该额外的过滤器步骤会占用更多的处理时间,并且会迅速增加大图像,导致调整大小缓慢,并为过滤器本身分配了额外的内存。

通常,将这种技术应用于明显大于所需大小的图像不是一个好主意,因为存在额外的过滤开销。

魔术组合

从内存和性能的角度来看,您可以组合使用这些选项以获得最佳效果。(设置inSampleSizeinScaledinDensityinTargetDensity标志)

inSampleSize首先将其应用于图像,使其达到目标尺寸的下一个2幂。然后,inDensityinTargetDensity用于将结果缩放到所需的确切尺寸,并应用过滤器操作以清理图像。

将这两个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标志来帮助您获取图像的尺寸,而无需实际解码像素数据。

// 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

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


1
如果您能告诉我们dstWidth是什么,那真是太好了?
k0sh

@ k0sh dstWIdth是ImageView的宽度,即它要转到ie destination width或dstWidth的简称
tyczj

@tyczj感谢您的回答,我知道这是什么,但是有些人可能不知道,而且自从柯尔特确实回答了这个问题以来,也许他可以解释这个问题,所以人们不会感到困惑。
k0sh

不错的帖子...以前不知道inScaled,inDensity,inTargetDensity标志...
maveroid

我一直在观看android性能模式系列,并且学到了很多东西!
尼斯,2015年

13

正如这个答案一样好(准确),它也非常复杂。与其重新发明轮子,不如考虑GlidePicassoUILIon或其他许多为您实现这种复杂且容易出错的逻辑的库。

Colt本人甚至建议您在Pre-scaling Bitmaps Performance Patterns Video中查看Glide和Picasso 。

通过使用库,您可以获得Colt的答案中提到的所有效率,但使用的API则简单得多,可在每个Android版本上一致地工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.