1. 概述
Android 中的图片是以 Bitmap 方式存在的,绘制的时候也是 Bitmap,直接影响到app运行时的内存。通过 ImageView 来显示图片,很多时候 ImageView 并没有原始图片的尺寸那么大,这个时候把整个图片加载进来后再设置给 ImageView,显然是没有必要的,因为 ImageView 根本没办法显示原始图片。可以将图片缩小后再加载进来,这样图片既能在 ImageView 显示出来,又能降低内存占用从而在一定程度上避免OOM,提高了 Bitmap 加载时的性能。在Android,Bitmap 所占用的内存计算公式是:Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存。只要通过改变宽像素、高像素、一个像素所占的内存这3个参数,任意减少一个的值,就达到了压缩的效果。
2. 单个像素点占用的字节数
单个像素的字节大小由 Bitmap 的一个可配置的参数 Config 来决定,Config 是枚举类,定义了 Android中 支持的 Bitmap 配置:
Config | 占用字节大小(byte) | 说明 |
---|---|---|
ALPHA_8 | 1 | 每个像素存储为单个半透明(alpha)通道。 |
RGB_565 | 2 | 简易RGB色调 |
ARGB_4444 | 4 | 已废弃 |
ARGB_8888 | 4 | 默认图片配置 |
RGBA_F16 | 8 | Android 8.0 新增 |
HARDWARE | Special | Android 8.0 新增 (Bitmap直接存储在graphic memory) |
这个是理论结论,在实际使用过程中,如果设置成了其他标准。在很多情况下,是不生效的。Android系统会强行转成使用ARGB_8888标准。
如下所示是不同 Config 下,同一张图片的占用的内存大小:
if (type.equals("alpha_8")) {
mConfig = Bitmap.Config.ALPHA_8;
} else if (type.equals("8888")) {
mConfig = Bitmap.Config.ARGB_8888;
} else if (type.equals("565")) {
mConfig = Bitmap.Config.RGB_565;
} else if (type.equals("f16")) {
mConfig = Bitmap.Config.RGBA_F16;
} else if (type.equals("hardware")) {
mConfig = Bitmap.Config.HARDWARE;
}
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = mConfig;
try {
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
} catch (OutOfMemoryError error) {
mBitmap = null;
}
Log.e("zzw", "config: " + type);
Log.e("zzw", "width: " + mBitmap.getWidth() + " height: " + mBitmap.getHeight());
Log.e("zzw", "size: " + mBitmap.getByteCount());
mImg.setImageBitmap(mBitmap);
log 打印:
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: config: alpha_8
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: config: 8888
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: config: 565
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 1937988
2019-11-07 05:15:39.743 19676-19676/cn.zzw.bitmapdemo E/zzw: config: f16
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 7751952
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: config: hardware
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
3. Bitmap 压缩
3.1 采样率压缩
采样率压缩其原理其实也是缩放 bitamp 的尺寸,通过调节其 inSampleSize 参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4。
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
应用场景: 先获取 Bitmap 的宽高,以及要显示的大小,通过比较来决定 inSampleSize 的具体大小。在获取 Bitmap 宽高的时候,设置 inJustDecodeBounds=true,这样创建的 Bitmap 不占用内存的。
3.2 质量压缩
在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的。bitmap 图片的大小不会改变。
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 50, bos);
byte[] bytes = bos.toByteArray();
mBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
应用场景:用于对图片进行压缩,用于分享图片以及保存为文件用于上传到服务端。
3.3 缩放法压缩
Android 中使用 Matrix 对图像进行缩放、旋转、平移、斜切等变换的。
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
bitmap = null;
应用场景:跟 3.1 中类似,3.1 中宽高同时进行缩放,这里可以分别对宽高进行缩放。
4. Bitmap 的复用
BitmapFactory.Options.inBitmap 字段,设置此字段之后解码方法会尝试复用一张存在的 Bitmap。Bitmap 的内存被复用,避免了内存的回收及申请过程,性能表现更佳。
BitmapFactory.Options opts1 = new BitmapFactory.Options();
opts1.inMutable=true;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.na,opts1);
BitmapFactory.Options opts2 = new BitmapFactory.Options();
opts2.inBitmap=mBitmap;
opts2.inSampleSize = 1;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.timg,opts2);
mImg.setImageBitmap(bitmap2);
用了 inBitmap 这个属性,加载两张,这两张图片会指向同一块内存,而不用开辟两块内存空间。
inBitmap 的限制:
3.0-4.3:复用的图片大小必须相同,编码必须相同;4.4以上:复用的空间大于等于即可,编码不必相同,不支持WebP。
图片复用,这个属性必须设置为true:options.inMutable = true;
5. LruCache 的使用
关于 LruCache 的使用,参考此篇:LruCache 源码解析
附上文章中的例子: