@TryLoveCatch
2022-05-05T08:30:37.000000Z
字数 5552
阅读 3192
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; // 默认truepublic int inDensity; // 无dpi的文件夹下默认160public 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// 解码后图片大小为720x720System.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,340return 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.hstatic int SkColorTypeBytesPerPixel(SkColorType ct) {static const uint8_t gSize[] = {0, // Unknown1, // Alpha_82, // RGB_5652, // ARGB_44444, // RGBA_88884, // BGRA_88881, // 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的时候,是240const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640const 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 sizeSkColorType 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) = 696scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915915 * 696 * 4 = 2547360
完美~~~打完收工
bitmap的大小,由以下三个决定:
1. 单像素点字节数
2. 图片文件存放的drawable文件夹位置
3. 手机屏幕密度
参考:
Android Bitmap面面观
Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
Android屏幕适配知识
安卓分辨率的相关知识
Android屏幕适配知识
详解Android开发中常用的 DPI / DP / SP
支持多种屏幕