在android上调整位图大小的最节省内存的方法?
Posted
技术标签:
【中文标题】在android上调整位图大小的最节省内存的方法?【英文标题】:Most memory efficient way to resize bitmaps on android? 【发布时间】:2015-11-14 06:16:44 【问题描述】:我正在构建一个图像密集型社交应用程序,其中图像从服务器发送到设备。当设备的屏幕分辨率较小时,我需要在设备上调整位图的大小以匹配其预期的显示尺寸。
问题是使用 createScaledBitmap 会导致我在调整大量缩略图图像的大小后遇到很多内存不足错误。
在 android 上调整位图大小最节省内存的方法是什么?
【问题讨论】:
您的服务器不能发送正确的大小,以便您节省客户的 RAM 和带宽!? 这只有在我拥有服务器资源时才有效,它有一个可用的计算组件,并且在所有情况下,它都可以预测图像的确切尺寸,以适应它尚未看到的纵横比。因此,如果您从 3rd 方 CDN 加载资源内容(就像我一样),它不起作用:( 【参考方案1】:这个答案总结自Loading large bitmaps Efficiently 它解释了如何使用 inSampleSize 加载缩小的位图 版本。
特别Pre-scaling bitmaps解释了各种细节 方法,如何组合它们,哪些是最节省内存的。
在 Android 上调整位图大小的三种主要方法具有不同的内存属性:
createScaledBitmap API
此 API 将采用现有位图,并创建一个具有您选择的确切尺寸的新位图。
从好的方面来说,您可以准确地获得所需的图像尺寸(无论其外观如何)。但缺点是,这个 API 需要一个现有的位图才能工作。这意味着必须先加载、解码图像并创建位图,然后才能创建新的较小版本。这在获得精确尺寸方面是理想的,但在额外的内存开销方面却很糟糕。因此,对于大多数注重内存的应用程序开发人员来说,这是一种破坏交易
inSampleSize flag
BitmapFactory.Options
有一个标记为inSampleSize
的属性,它将在解码图像时调整图像大小,以避免需要解码为临时位图。此处使用的这个整数值将以 1/x 的缩小尺寸加载图像。例如,将inSampleSize
设置为 2 会返回一个大小为一半的图像,而将其设置为 4 会返回一个大小为 1/ 4 的图像。基本上图像尺寸总是比你的源尺寸小一些二次方。
从内存的角度来看,使用inSampleSize
是一个非常快速的操作。实际上,它只会将图像的每 X 个像素解码为生成的位图。 inSampleSize
有两个主要问题:
它不会为您提供准确的分辨率。它只会将位图的大小减小 2 的幂。
它不会产生最佳质量的调整大小。大多数调整大小的过滤器通过读取像素块,然后对它们进行加权以产生有问题的调整大小的像素,从而产生好看的图像。 inSampleSize
通过每隔几个像素读取一次来避免这一切。结果非常高效,内存不足,但质量受到影响。
如果您只是将图像缩小一些 pow2 大小,并且过滤不是问题,那么您找不到比 inSampleSize
更节省内存(或性能效率)的方法。
inScaled, inDensity, inTargetDensity flags
如果您需要将图像缩放到不等于 2 的幂的尺寸,那么您将需要 BitmapOptions
的 inScaled
、inDensity
和 inTargetDensity
标志。设置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
将首先应用于图像,使其比您的目标大小大的下一个二次幂。然后,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
标志来帮助您获取图像的尺寸,而无需实际解码像素数据。
// 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
您可以使用此标志先解码尺寸,然后计算适当的值以缩放到您的目标分辨率。
【讨论】:
如果您能告诉我们 dstWidth 是什么,那就太好了? @k0sh dstWIdth 是 ImageView 的宽度,即destination width
或简称 dstWidth
@tyczj 感谢您的回答,我知道它是什么,但有些人可能不知道,因为 Colt 确实回答了这个问题,也许他可以解释一下,这样人们就不会混淆了。
请注意,使用 Bitmap 方法按比例缩小会导致混叠,因为这些方法仅使用双线性插值(没有预过滤)。我写了一个article 来解释这个问题,并提出了一个使用 RenderScript 正确缩小图像的解决方案。
我写了一个post,我将这个方法与基于 RenderScript 的方法进行比较,当按 2 的幂因子或任意大小进行缩减时,我比较了质量和性能。跨度>
【参考方案2】:
这个答案很好(也很准确),但也很复杂。与其重新发明***,不如考虑像Glide、Picasso、UIL、Ion 这样的库,或者为您实现这种复杂且容易出错的逻辑的任何其他库。
柯尔特本人甚至建议在Pre-scaling Bitmaps Performance Patterns Video 中看看 Glide 和 Picasso。
通过使用库,您可以获得 Colt 的回答中提到的每一点效率,但使用非常简单的 API,可以在每个版本的 Android 中始终如一地工作。
【讨论】:
【参考方案3】:最好的方法是使用 coil 异步处理所有事情
由 Kotlin Coroutines 支持的 Android 图像加载库。线圈是:
快速:Coil 执行许多优化,包括内存和磁盘缓存、对内存中的图像进行下采样、重用位图、自动暂停/取消请求等。 轻量级:Coil 为您的 APK 增加了大约 2000 个方法(对于已经使用 OkHttp 和 Coroutines 的应用程序),这与 Picasso 相当,但明显少于 Glide 和 Fresco。 易于使用:Coil 的 API 利用 Kotlin 的语言特性来实现简单性和最少的样板。 现代:Coil 是 Kotlin 优先的,并使用现代库,包括 Coroutines、OkHttp、Okio 和 AndroidX Lifecycles。
Coil 是:Coroutine Image Loader的首字母缩写。
【讨论】:
以上是关于在android上调整位图大小的最节省内存的方法?的主要内容,如果未能解决你的问题,请参考以下文章