IM即时通讯开发如何减少Bitmap内存占用?

Bitmap有关的内存占用、OOM等问题一直是新Android程序员心中挥之不去的阴影,本文依据实践经验着重总结如何减少Bitmap的内存占用方法。

正确理解Android上的Jpg和Png

说到这里,肯定会有人会说,我们用 jpg 吧,jpg 格式的图片不应该比 png 小么?这确实是个好问题,因为同样一张图片,jpg 确实比 png 会多少小一些(甚至很多),原因很简单,jpg 是一种有损压缩的图片存储格式,而 png 则是 无损压缩的图片存储格式,显而易见,jpg 会比 png 小,代价也是显而易见的。

可是,这说的是文件存储范畴的事情,它们只存在于文件系统,而非内存或者显存。说得简单一点儿,我有一个极品飞车的免安装硬盘版的压缩包放在我的磁盘里面,这个游戏是不能玩的,我需要先解压,才能玩——jpg 也好,png 也好就是个压缩包的概念,而我们讨论的内存占用则是从使用角度来讨论的。所以,jpg 格式的图片与 png 格式的图片在内存当中不应该有什么不同。

肯定有人有意见,jpg 图片读到内存就是会小,还会给我拿出例子。当然,他说的不一定是错的。因为 jpg 的图片没有 alpha 通道!!所以读到内存的时候如果用 RGB565的格式存到内存,这下大小只有 ARGB8888的一半,能不小么。。。

不过,抛开 Android 这个平台不谈,从出图的角度来看的话,jpg 格式的图片大小也不一定比 png 的小,这要取决于图像信息的内容:JPG 不适用于所含颜色很少、具有大块颜色相近的区域或亮度差异十分明显的较简单的图片。对于需要高保真的较复杂的图像,PNG 虽然能无损压缩,但图片文件较大。

如果仅仅是为了 Bitmap 读到内存中的大小而考虑的话,jpg 也好 png 也好,没有什么实质的差别。但二者的差别主要体现在:

    alpha 你是否真的需要?如果需要 alpha 通道,那么没有别的选择,用 png。

    你的图色值丰富还是单调?就像刚才提到的,如果色值丰富,那么用jpg,如果作为按钮的背景,请用 png。

    对安装包大小的要求是否非常严格?如果你的 app 资源很少,安装包大小问题不是很凸显,看情况选择 jpg 或者 png(不过,我想现在对资源文件没有苛求的应用会很少吧。。)

    目标用户的 cpu 是否强劲?jpg 的图像压缩算法比 png 耗时。这方面还是要酌情选择,前几年做了一段时间 Cocos2dx,由于资源非常多,项目组要求统一使用 png,可能就是出于这方面的考虑。

跑题了,我们其实想说的是怎么减少内存占用的。。。

使用inSampleSize

有些朋友一看到这个肯定就笑了。采样嘛,我以前是学信号处理的,一看到 Sample 就抽抽。哈哈开个玩笑,这个采样其实就跟统计学里面的采样是一样的,在保证最终效果满足要求的前提下减少样本规模,方便后续的数据采集和处理。这个方法主要用在图片资源本身较大,或者适当地采样并不会影响视觉效果的条件下,这时候我们输出地目标可能相对较小,对图片分辨率、大小要求不是非常的严格。

举个例子:

我们现在有个需求,要求将一张图片进行模糊,然后作为 ImageView 的 src 呈现给用户,而我们的原始图片大小为 1080*1920,如果我们直接拿来模糊的话,一方面模糊的过程费时费力,另一方面生成的图片又占用内存,实际上在模糊运算过程中可能会存在输入和输出并存的情况,此时内存将会有一个短暂的峰值。

这时候你一定会想到三个字母在你的脑海里挥之不去,它们就是『OOM』。既然图片最终是要被模糊的,也看不太情况,还不如直接用一张采样后的图片,如果采样率为 2,那么读出来的图片只有原始图片的 1/4 大小,真是何乐而不为呢??

BitmapFactory.Options options = new Options();

options.inSampleSize = 2;

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);

不过,inSampleSize的数值不是随意决定的,具体的计算方法可参见此文:http://www.cnblogs.com/kobe8/p/3877125.html,或百度“Android inSampleSize 计算”。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询

使用矩阵

用到 Bitmap 的地方,总会见到 Matrix。这时候你会想到什么?其实想想,Bitmap 的像素点阵,还不就是个矩阵,真是你中有我,我中有你的交情啊。那么什么时候用矩阵呢?

我信记住一个原则:

大图小用用采样,小图大用用矩阵。

还是用前面模糊图片的例子,我们不是采样了么?内存是小了,可是图的尺寸也小了啊,我要用 Canvas 绘制这张图可怎么办?当然是用矩阵了:

1方式一

Matrix matrix = new Matrix();

matrix.preScale(2, 2, 0f, 0f);

//如果使用直接替换矩阵的话,在Nexus6 5.1.1上必须关闭硬件加速

canvas.concat(matrix);

canvas.drawBitmap(bitmap, 0,0, paint);

需要注意的是:在使用搭载 5.1.1 原生系统的 Nexus6 进行测试时发现,如果使用 Canvas 的 setMatrix 方法,可能会导致与矩阵相关的元素的绘制存在问题,本例当中如果使用 setMatrix 方法,bitmap 将不会出现在屏幕上。因此请尽量使用 canvas 的 scale、rotate 这样的方法,或者使用 concat 方法。

2方式二

Matrix matrix = new Matrix();

matrix.preScale(2, 2, 0, 0);

canvas.drawBitmap(bitmap, matrix, paint);

这样,绘制出来的图就是放大以后的效果了,不过占用的内存却仍然是我们采样出来的大小。如果我要把图片放到 ImageView 当中呢?一样可以,请看:

Matrix matrix = new Matrix();

matrix.postScale(2, 2, 0, 0);

imageView.setImageMatrix(matrix);

imageView.setScaleType(ScaleType.MATRIX);

imageView.setImageBitmap(bitmap);

合理选择Bitmap的像素格式

其实前面我们已经多次提到这个问题。ARGB8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte。

我们先看下有多少种格式可选:

格式 描述

ALPHA_8 只有一个alpha通道

ARGB_4444 这个从API 13开始不建议使用,因为质量太差

ARGB_8888 ARGB四个通道,每个通道8bit

RGB_565 每个像素占2Byte,其中红色占5bit,绿色占6bit,蓝色占5bit

这几个当中:

- ALPHA8:没必要用,因为我们随便用个颜色就可以搞定的。

- ARGB4444:虽然占用内存只有 ARGB8888 的一半,不过已经被官方嫌弃,失宠了。。『又要占省内存,又要看着爽,臣妾做不到啊T T』。

- ARGB8888:是最常用的,大家应该最熟悉了。

- RGB565:看到这个,我就看到了资源优化配置无处不在,这个绿色。其实如果不需要 alpha 通道,特别是资源本身为 jpg 格式的情况下,用这个格式比较理想。

如果传入的 dstColorType 是 kRGB_565_SkColorType,同时图片还有 alpha 通道,那么返回 false~~咳咳,那么问题来了,这个dstColorType 是哪儿来的??就是我们在 decode 的时候,传入的 Options 的 inPreferredConfig。下面是实验时间。

准备:

在 assets 目录当中放了一个叫 index.png 的文件,大小192*192,这个文件是通过 PhotoShop 编辑之后生成的索引格式的图片。

代码:

try {

  Options options = new Options();

  options.inPreferredConfig = Config.RGB_565;

Bitmap bitmap = BitmapFactory.decodeStream(getResources().getAssets().open("index.png"), null, options);

  Log.d(TAG, "bitmap.getConfig() = " + bitmap.getConfig());

  Log.d(TAG, "scaled bitmap.getByteCount() = " + bitmap.getByteCount());

  imageView.setImageBitmap(bitmap);

} catch (IOException e) {

    e.printStackTrace();

}

程序运行在 Nexus6上,由于从 assets 中读取不涉及前面讨论到的 scale 的问题,所以这张图片读到内存以后的大小理论值(ARGB8888):192 * 192 *4=147456

好,运行我们的代码,看输出的 Config 和 ByteCount:

D/MainActivity: bitmap.getConfig() = null

D/MainActivity: scaled bitmap.getByteCount() = 36864

先说大小为什么只有 36864,我们知道如果前面的讨论是没有问题的话,那么这次解码出来的 Bitmap 应该是索引格式,那么占用的内存只有 ARGB 8888 的1/4是意料之中的;再说 Config 为什么为 null。官方说:

public final Bitmap.Config getConfig ()

Added in API level 1

If the bitmap’s internal config is in one of the public formats, return that config, otherwise return null.

看来这个法子还真行啊,占用内存一下小很多。不过由于官方并未做出支持,因此这个方法有诸多限制,比如不能在 xml 中直接配置,,生成的 Bitmap 不能用于构建 Canvas 等等。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/125842334