@TryLoveCatch
2022-05-05T16:30:37.000000Z
字数 5552
阅读 2705
Android知识体系
dpi和ppi基本一样
我认为
dpi是android定义的,是一些固定值,用于系统中px/dp转换的。如下图
我们可以通过代码来获取:
DisplayMetrics dm = getResources().getDisplayMetrics();
int dpi = dm.densityDpi;
ppi是实际的dpi,是实际计算出来的,公式如下,然后根据这个值,在上图中找到最接近的dpi。
举个例子,小米pad,7.9英寸,2048*1536像素,我们运用上面的公式,得到ppi为326,然后根据dpi的图,可知,dpi为320。
decodeFile()用于读取SD卡上的图,得到的是图片的原始尺寸
decodeResource()用于读取Res、Raw等资源,得到的是图片的原始尺寸 * 缩放系数
由上可知,decodeResource()比decodeFile()多了一个缩放系数,而这个缩放系数,依赖于屏幕密度和图片缩放的位置,参考上一篇。接下来,我们看一下代码:
// 通过BitmapFactory.Options的这几个参数可以调整缩放系数
public class BitmapFactory {
public static class Options {
public boolean inScaled; // 默认true
public int inDensity; // 无dpi的文件夹下默认160
public int inTargetDensity; // 取决具体屏幕
}
}
注释里面说的很清楚了,我们继续来看。
假设我们有一张720*720的图片。
inScaled属性
如果inScaled设置为false,则不进行缩放,解码后图片大小为720x720; 否则请往下看。
如果inScaled设置为true或者不设置,则根据inDensity和inTargetDensity计算缩放系数。
inDensity属性
取决于图片的位置,默认或drawable下面,inDensity为160,drawable-xhdpi下面,inDensity为320,具体参见下图:
假设这张图片放到drawable目录下,
以1080p的MX4为例子,缩放系数 = inTargetDensity(具体480 / inDensity(默认160)= 3 = density, 解码后图片大小为2160x2160。
假设这张图放到drawable-xhdpi下面,则为480/320 = 1.5,1.5*720 = 1080,所以得到的图片大小为1080*1080
inTargetDensity属性
取决于屏幕密度
借用上面的例子
假设这张图片放到drawable目录下,
以1080p的MX4为例子,缩放系数 = inTargetDensity(具体480 / inDensity(默认160)= 3 = density, 解码后图片大小为2160x2160。
那么,以720p的红米3为例子,缩放系数 = inTargetDensity(具体320 / inDensity(默认160)= 2 = density,解码后图片大小为1440x1440。
手动设置
我们可以通过BitmapFactory.Options手动设置inTargetDensity和inDensity的值,来控制缩放系数。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 1;
options.inDensity = 160;
options.inTargetDensity = 160;
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.origin, options);
// MX4上,虽然density = 3
// 但是通过设置inTargetDensity / inDensity = 160 / 160 = 1
// 解码后图片大小为720x720
System.out.println("w:" + bitmap.getWidth()
+ ", h:" + bitmap.getHeight());
如何得到bitmap的大小呢?Android提供了一个Api,在Bitmap里面:
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
看了getByteCount()的源码,getHeight()我们很熟悉了,返回的就是bitmap高度的像素(px),那么getRowBytes()是什么东西呢?继续看代码:
public final int getrowBytes() {
if (mRecycled) {
Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
}
return nativeRowBytes(mFinalizer.mNativeBitmap);
}
呵呵呵,JNI?!好吧,你赢了。大体代码如下,爱看不看吧
size_t SkBitmap::ComputeRowBytes(Config c, int width) {
return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
}
SkImageInfo.h
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);
}
说明一下,gSize数组,仔细一看,是不是很熟悉?其实就是每一个像素点所占的字节数。所以,我们可以得出来:
看了getByteCount(),返回的是width * 单像素点字节数
所以如果一张ARGB_8888的bitmap的大小如下:
width * height * 4bytes
一张522*686的 PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B。
但是通过我们的方式计算,结果是1432368B。
这个原因,估计大家也都明白了,我们之前包括上一篇也都说过了,就不再冗述了,drawable-xxhdpi是480,而s6是640,所以
width * (640/480) * height * (640/480) * 4bytes = 2546432B
我们发现我们计算的2546432B和通过getByteCount()得到的已经很接近了,但是还是不一样。继续源码:
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
......
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);//对应hdpi的时候,是240
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
scale = (float) targetDensity / density;
}
}
}
const bool willScale = scale != 1.0f;
......
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
return nullObjectReturn("decoder->decode returned false");
}
//这里这个deodingBitmap就是解码出来的bitmap,大小是图片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (willScale) {
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.0f, 0.0f, &paint);
}
......
}
关键代码:
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
所以
scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696
scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915
915 * 696 * 4 = 2547360
完美~~~打完收工
bitmap的大小,由以下三个决定:
1. 单像素点字节数
2. 图片文件存放的drawable文件夹位置
3. 手机屏幕密度
参考:
Android Bitmap面面观
Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
Android屏幕适配知识
安卓分辨率的相关知识
Android屏幕适配知识
详解Android开发中常用的 DPI / DP / SP
支持多种屏幕