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
分析源码
加载一张640 * 427 310KB的png图片,图片放drawable-hdpi目录下。 模拟器参数 density: 2.625 densityDpi: 420 byteCount: 33465601
2
Bitmap qingye = BitmapFactory.decodeResource(getResources(), R.drawable.friend_small);
Log.e(TAG, "getByteCount= " + qingye.getByteCount());
打印日志,getByteCount= 3346560
约等于3M
查看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个字节
求得公式 公式= 图片长宽 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;
}
}
if (opts.inTargetDensity == 0 && res != null ) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts); }
我们最应该关心的是什么呢?是 inDensity 和 inTargetDensity,这两个值与下面 cpp 文件里面的 density 和 targetDensity 相对应。重复一下,inDensity 就是原始资源的 density,inTargetDensity 就是屏幕的 density。
在BitmapFactory.cpp
的doDecode
方法中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还是其他
目标屏幕的密度
总结转载自这里