概述
在日常开发中我们经常遇到加载图片报出oom的错误,我们要解决这个问题,首先要明白oom代表out of memory 内存溢出,因为手机内存有限,分给每个应用的内存有限,所以要解决这个问题就是要解决图片占用内存问题 android 中图片是以bitmap的形式存在的,那么bitmap中所占的内存,直接影响到了是否oom,我们了解一下bitmap的占用内存的计算方法
Bitmap到底占多大内存
从本地加载或者从网络加载可以用下面的公式计算
图片的长度 * 图片的宽度 * 一个像素点占用的字节数
如果从资源文件夹加载,会怎么样?
首先把同一张图片放进不同的资源文件夹会发生什么?
- 同一张图片放进不同的文件夹,图片会被压缩
看下源码
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);
}
我们可以看到压缩比例是由下面的公式得出
scale = (float) targetDensity / density;
及缩放的比例和targetDensity,density有关,那么这个俩个变量又代表着什么呢?
- targetDensity:设备屏幕像素密度 dpi
- density:图片对应的文件夹的像素密度 dpi
其中density和Bitmap存放的资源目录有关,不同的资源目录有不同的值
density | 0.75 | 1 | 1.5 | 2 | 3 3.5 | 4 |
---|---|---|---|---|---|---|
densityDpi | 120 | 160 | 240 | 320 | 480 | 560 |
DpiFolder | ldpi | mdpi | hdpi | xhdpi | xxhdpi | xxxhdpi |
可以得出以下结论
- 同一张图片放在不同的资源目录下,其分辨率会有变化
- Bitmap的分辨率越高,其解析后的宽高越小,甚至小于原有的图片(及缩放),从而内存也响应的减少
- 图片不放置任何资源目录时,其使用默认分辨率mdpi:160
- 资源目录分辨率和屏幕分辨率一致时,图片尺寸不会缩放
所以Bitmap在资源目录中的计算方式为
Bitmap内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× (当前设备密度dpi/图片所在文件夹对应的密度dpi)^2 × 每个像素的字节大小