一、图片内存的占用
Bitmap作为位图,需要读入一张图片每一个像素点的数据,其主要占用内存的地方也正是这些像素数据。对于像素数据总大小,我们可以猜想为:像素总数量 × 每个像素的字节大小,而像素总数量在矩形屏幕表现下,应该是:横向像素数量 × 纵向像素数量,结合得到:
Bitmap内存占用 ≈ 像素数据总大小 = 横向像素数量 × 纵向像素数量 × 每个像素的字节大小
所以通过公式,我们可以看到图片的内存占用,跟像素的数量和每个像素占用字节大小有关
1.1 每像素占用字节大小
单个像素的字节大小由Bitmap的一个可配置的参数Config来决定。
Bitmap中,存在一个枚举类Config,定义了Android中支持的Bitmap配置:
Config | 占用字节大小(byte) | 说明 |
---|---|---|
ALPHA_8 (1) | 1 | 单透明通道 |
RGB_565 (3) | 2 | 简易RGB色调 |
ARGB_8888 | 4 | 24位真彩色(这是Bitmap构建时的默认配置) |
RGBA_F16 | 8 | Android 8.0 新增(更丰富的色彩表现HDR) |
HARDWARE | Special | Android 8.0 新增 (Bitmap直接存储在graphic memory) |
我们根据具体的业务需求,给图片指定config格式:
bitmap.setConfig();
1.2 像素数量
图片的decode过程在JNI中的逻辑大致如下:
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
scale = (float) targetDensity / density; // 这里计算出缩放倍率
}
}
...
int scaledWidth = decoded->width();
int scaledHeight = decoded->height();
if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
...
if (willScale) {
const float sx = scaledWidth / float(decoded->width());
const float sy = scaledHeight / float(decoded->height());
bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
bitmap->allocPixels(&javaAllocator, NULL);
bitmap->eraseColor(0);
SkPaint paint;
paint.setFilterBitmap(true);
SkCanvas canvas(*bitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
}
一张图片的像素数量遵循这个公式:宽 * 高 * (targetDensity/density)
图片的宽高我们是知道的,targetDensity和density两个参数都是BitmapFactory的Options配置出来的。
image.png
- inTargetDensity和inScreenDensity一般都是设备决定
- inDensity(Bitmap位图自身的密度、分辨率):跟我们把图片放在哪个文件夹有关系,同一张图片放在不同的目录下,它的密度是不一样的
density | 0.75 | 1 | 1.5 | 2 | 3 | 3.5 | 4 |
---|---|---|---|---|---|---|---|
densityDpi | 120 | 160 | 240 | 320 | 480 | 560 | 640 |
DpiFolder | ldpi | mdpi | hdpi | xhdpi | xxhdpi | xxxhdpi | xxxxhdpi |
- 图片目标不匹配任何dpi的时候,默认是mdpi
- 图片分辨率越高,解析后的图片越小,内存占用越小
二、图片的内存优化
知道了图片加载机制后,优化也就有了抓手:
- 使用低色彩的解析模式
- 尽可能的使用高分辨率的图片目录
- 图片缩小,减少尺寸
前两种方式需要根据图片的实际情况,选择合理的配置
第三种方式需要对图片尺寸做裁剪:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = true; //只测量边框
BitmapFactory.decodeResource(getResources(), resId,options);
options.inJustDecodeBounds = false;
options.inSampleSize = BitmapUtil.computeSampleSize(options, -1, imageView.getWidth() * imageView.getHeight()); // 比较bitmap尺寸和目标尺寸,计算出缩放倍数
Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), resId, options); //生成缩放后的bitmap