Android中图片大小知识总结

Android中屏幕密度的相关概念

  • density:The logical density of the display. This is a scaling factor for the Density Independent Pixel unit, where one DIP is one pixel on an approximately 160 dpi screen (for example a 240x320, 1.5”x2” screen), providing the baseline of the system’s display. Thus on a 160dpi screen this density value will be 1; on a 120 dpi screen it would be .75; etc.
    This value does not exactly follow the real screen size (as given by xdpi and ydpi, but rather is used to scale the size of the overall UI in steps based on gross changes in the display dpi. For example, a 240x320 screen will have a density of 1 even if its width is 1.8”, 1.3”, etc. However, if the screen resolution is increased to 320x480 but the screen size remained 1.5”x2” then the density would be increased (probably to 1.5).

  • densityDpi:The screen density expressed as dots-per-inch.

简单来说,可以理解为 density 的数值是 1dp=density px;densityDpi 是屏幕每英寸对应多少个点(不是像素点),在 DisplayMetrics 当中,这两个的关系是线性的:

density 1 1.5 2 3 3.5 4
densityDpi 160 240 320 480 560 640

分析源码

  1. 加载一张640 * 427 310KB的png图片,图片放drawable-hdpi目录下。
    模拟器参数
    density: 2.625
    densityDpi: 420
    byteCount: 3346560
    1
    2
    Bitmap qingye = BitmapFactory.decodeResource(getResources(), R.drawable.friend_small);
    Log.e(TAG, "getByteCount= " + qingye.getByteCount());

打印日志,getByteCount= 3346560约等于3M

  1. 查看getByteCount方法
    1
    2
    3
    4
    5
    6
    7
    8
    public final int getByteCount() {
    if (mRecycled) {
    Log.w(TAG, "Called getByteCount() on a recycle()'d bitmap! "
    + "This is undefined behavior!");
    return 0;
    }
    // int result permits bitmaps up to 46,340 x 46,340
    return getRowBytes() * getHeight(); }

可以看出总大小和getRowBytes()以及图片的高度有关
进而查看

1
2
3
4
5
public final int getRowBytes() {
if (mRecycled) {
Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
}
return nativeRowBytes(mNativePtr); }

发现这是一个naive方法
private static native int nativeRowBytes(long nativeBitmap);

SkBitmap.h

1
2
/** Return the number of bytes between subsequent rows of the bitmap. */
size_t rowBytes() const { return fRowBytes; }

SkBitmap.cpp

1
size_t SkBitmap::ComputeRowBytes(Config c, int width) { return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width); }

SkImageInfo.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int SkColorTypeBytesPerPixel(SkColorType ct) {
static const uint8_t gSize[] = {
0, // Unknown
1, // Alpha_8
2, // RGB_565
2, // ARGB_4444
4, // RGBA_8888
4, // BGRA_8888
1, // kIndex_8
};
SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
size_mismatch_with_SkColorType_enum);
SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
return gSize[ct];
}
static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
return width * SkColorTypeBytesPerPixel(ct);
}

可以看到argb_8888格式的一个像素占用4个字节

  1. 求得公式
    公式= 图片长4,对吗?
    640 * 427 * 4 = 1093120 不等于 3346560

BitmapFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;//这里density的值如果对应资源目录为hdpi的话,就是240
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
//这个inTargetDensity是当前屏幕的densityDpi,我的模拟器是420
}
return decodeStream(is, pad, opts); }

我们最应该关心的是什么呢?是 inDensity 和 inTargetDensity,这两个值与下面 cpp 文件里面的 density 和 targetDensity 相对应。重复一下,inDensity 就是原始资源的 density,inTargetDensity 就是屏幕的 density。

BitmapFactory.cppdoDecode方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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;
}
}
...
//这里这个deodingBitmap就是解码出来的bitmap,大小是图片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5 f);
scaledHeight = int(scaledHeight * scale + 0.5 f);
}
...
if (willScale) {
// This is weird so let me explain: we could use the scale parameter
// directly, but for historical reasons this is how the corresponding
// Dalvik code has always behaved. We simply recreate the behavior here.
// The result is slightly different from simply using scale because of
// the 0.5f rounding bias applied when computing the target image size
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
// TODO: avoid copying when scaled size equals decodingBitmap size
SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
outputBitmap - > setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
colorType, decodingBitmap.alphaType()));
if (!outputBitmap - > allocPixels(outputAllocator, NULL)) {
return nullObjectReturn("allocation failed for scaled bitmap");
}
// If outputBitmap's pixels are newly allocated by Java, there is no need
// to erase to 0, since the pixels were initialized to 0.
if (outputAllocator != & javaAllocator) {
outputBitmap - > eraseColor(0);
}
SkPaint paint;
paint.setFilterLevel(SkPaint::kLow_FilterLevel);
SkCanvas canvas( * outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0 f, 0.0 f, & paint);
} else {
outputBitmap - > swap(decodingBitmap);
}

scale = (float) targetDensity / density;
在这个例子中,scale = 420 / 240 = 1.75
所以byteCount = 640 * 427 * 1.75 * 1.75 * 4 = 3347680 约等于3346560
因为scaledWidth还有0.5f的精度,所以加上精度
640 * 1.75 + 0.5 = 1120
427 * 1.75 + 0.5 = 747
1120 * 747 * 4 = 3346560

总结

Android加载一张图片的大小与下列情况有关

  • 色彩格式,如果是 ARGB8888 那么就是一个像素4个字节,如果是 RGB565 那就是2个字节
  • 原始文件存放的资源目录hdpi还是其他
  • 目标屏幕的密度

总结转载自这里