美文网首页
Android-图片占用内存大小

Android-图片占用内存大小

作者: 杨0612 | 来源:发表于2020-08-28 17:44 被阅读0次
    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<SkStreamRewindable> 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<int>(scaledWidth * scale + 0.5f);
            scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
        }
    ...
    }
    

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

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

    相关文章

      网友评论

          本文标题:Android-图片占用内存大小

          本文链接:https://www.haomeiwen.com/subject/ajzksktx.html