Android-图片占用内存大小

1.基本概念
densityDpi?

这是屏幕像素密度,一英寸屏幕有多个像素点。
通过以下方法可以获取

float densityDpi= getResources().getDisplayMetrics().densityDpi;

打印日志,我手机的densityDpi是480。

2020-08-28 11:20:07.242 20647-20647/com.yang.memorytest D/test: densityDpi=480.0
2.图片占用内存大小如何计算?

这里讨论的是占用内存大小,跟存储文件大小不是一个概念。
以下是Bitmap提供的两个获取图片占用内存大小的方法,

public final int getByteCount();
public final int getAllocationByteCount();

(1)getByteCount(),获取图片占用内存大小的理论值;
(2)getAllocationByteCount(),获取图片占用内存大小的实际值;

怎么理解理论值跟实际值呢?

这跟Bitmap内存复用有关。如果图片加载到内存,是存放在新开辟的内存空间里,那么理论值跟实际值相等,如果是复用其他Bitmap的内存空间,而这个空间比图片所需的空间大,那么实际值大于理论值。
有兴趣的,可以自己研究下 BitmapFactory.Options的inBitmap属性。
除了以上在运行时调用方法来获取图片占用大小以外,还可以自己计算。

图片大小=长每个像素占的字节数

ARGB8888的图片,每个像素占4个字节;
RGB565的图片,每个像素占2个字节;
有一张宽640 长1136的图片,存放在drawable-xxhdpi文件下,刚好匹配我手机的480dpi,ARGB8888加载为例,

                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rtoy_splash);
        Log.d("test", "bitmap.getWidth()=" + bitmap.getWidth());
        Log.d("test", "bitmap.getHeight()=" + bitmap.getHeight());
        Log.d("test", "bitmap size=" + bitmap.getWidth() * bitmap.getHeight() * 4);

        Log.d("test", "bitmap.getAllocationByteCount()=" + bitmap.getAllocationByteCount());
        Log.d("test", "bitmap.getByteCount()=" + bitmap.getByteCount());

BitmapFactory.decodeResource默认情况下采用ARGB8888加载,
从日志中可以看出,图片占用内存2908160B,跟getByteCount、getByteCount获取的结果一致。

2020-08-28 14:37:50.529 24368-24368/com.yang.memorytest D/test: bitmap.getWidth()=640
2020-08-28 14:37:50.529 24368-24368/com.yang.memorytest D/test: bitmap.getHeight()=1136
2020-08-28 14:37:50.529 24368-24368/com.yang.memorytest D/test: bitmap size=2908160
2020-08-28 14:37:50.529 24368-24368/com.yang.memorytest D/test: bitmap.getAllocationByteCount()=2908160
2020-08-28 14:37:50.529 24368-24368/com.yang.memorytest D/test: bitmap.getByteCount()=2908160
缩放系数

当图片资源所在文件夹与手机dpi不一致时,图片加载进来会被缩放。

缩放系数=手机dpi/图片资源所在文件夹dpi
序号 文件夹 dpi 匹配dpi区间
1 drawable-mdpi 160 120-160,包含160
2 drawable-hdpi 240 160-240,包含240
3 drawable-xhdpi 320 240-320,包含320
4 drawable-xxhdpi 480 320-480,包含480
5 drawable-xxxhdpi 640 480-640,包含640
6 drawable 160 160

如果手机是480dpi,图片放在drawable-xxhdpi,缩放系数是480/480=1;
如果手机是480dpi,图片放在drawable-xxxhdpi,缩放系数是480/640=0.75;
图片大小计算公式要发生变化:

图片大小=长每个像素占的字节数*缩放系数

同样以加载宽640 长1136的图片,用ARGB8888加载为例,图片放在drawable-xxxhdpi文件夹下(手机是480dpi),
加载代码不变,打印结果如下:
从日志可以看出,图片被缩放了,宽由640缩放成480,系数是0.75。

2020-08-28 14:54:53.132 24371-24371/com.yang.memorytest D/test: bitmap.getWidth()=480
2020-08-28 14:54:53.132 24371-24371/com.yang.memorytest D/test: bitmap.getHeight()=852
2020-08-28 14:54:53.132 24371-24371/com.yang.memorytest D/test: bitmap size=1635840
2020-08-28 14:54:53.132 24371-24371/com.yang.memorytest D/test: bitmap.getAllocationByteCount()=1635840
2020-08-28 14:54:53.132 24371-24371/com.yang.memorytest D/test: bitmap.getByteCount()=1635840
如果图片放在drawable-nodpi、raw、assert文件下,加载时不做任何缩放,原样输出。
2.源码分析

源码分析基于Android-28

BitmapFactory.decodeResource,

主要工作:
(1)构建value获取图片参数,包含图片所在文件夹dpi;
(2)res.openRawResource,加载图片流;
(3)调用res.openRawResource解析图片;

 public static Bitmap decodeResource(Resources res, int id, Options opts) {
        validate(opts);
        Bitmap bm = null;
        InputStream is = null;         
        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
        } 
        ......
        return bm;
    }
BitmapFactory.decodeResourceStream

主要工作:
(1)判断条件density == TypedValue.DENSITY_DEFAULT是否成立,成立,那就是图片在drawable文件夹下,opts.inDensity赋值为160;
(2)判断条件density != TypedValue.DENSITY_NONE是否成立,成立,那就是图片没有在drawable-nodpi文件夹下,opts.inDensity就是文件夹dpi;
(3)当图片在drawable-nodpi文件夹下,opts.inDensity赋值为0;
(4)opts.inTargetDensity赋值为手机dpi。

  public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
            @Nullable InputStream is, @Nullable Rect pad, @Nullable 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);
    }
BitmapFactory.decodeStream->decodeStreamInternal->BitmapFactory.cpp.nativeDecodeStream->doDecode

主要工作:
(1)根据图片所在dpi、手机dpi,计算缩放系数,scale = (float) targetDensity / density(手机dpi除以图片资源所在文件夹dpi);
(2)如果设置了BitmapFactory.Options inSampleSize,根据参数进行缩放;
(3)根据(1)得到的缩放系数进行缩放,结果四舍五入;
(4)注意:当图片放在drawable-nodpi文件夹下,获取到的density 为0,那么scale默认就是1,不进行缩放。

static jobject doDecode(JNIEnv* env, std::unique_ptr stream,
                        jobject padding, jobject options) {
...
//(1)
       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;
            }
        }
...
//(2)
    if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
        willScale = true;
        scaledWidth = codec->getInfo().width() / sampleSize;
        scaledHeight = codec->getInfo().height() / sampleSize;
    }
....
    if (scale != 1.0f) {
        willScale = true;
        scaledWidth = static_cast(scaledWidth * scale + 0.5f);
        scaledHeight = static_cast(scaledHeight * scale + 0.5f);
    }
...
}

总结:
(1)图片大小=长每个像素占的字节数*缩放系数;
(2)缩放系数=手机dpi/图片资源所在文件夹dpi;
(3)如果图片放在drawable-nodpi、raw、assert文件下,加载时不做任何缩放,原样输出。

以上分析有不对的地方,请指出,互相学习,谢谢哦!

你可能感兴趣的:(Android-图片占用内存大小)